postgres-query について
テスト、デバッグ、性能分析向けに PostgreSQL へ一時的なクエリを実行するスキルです。接続先を明示でき、実際の接続情報を毎回表示します。
主な機能
- 01本番、開発、DataPacket、通知DBの選択
- 02EXPLAIN ANALYZE による性能確認
- 03ファイルからのクエリ読み込みと JSON 出力
- 04読み取り専用が既定の書き込み制限
活用シーン
- 01アプリの動作をデバッグする際のレコード確認
- 02クエリプランや実行性能の比較
- 03EXPLAIN ANALYZE によるインデックス利用確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ civitai/ civitai --skill postgres-queryインストール後、Claude Code で/postgres-queryを入力して読み込まれたか確認します。
同梱ファイル.env.examplequery.mjs
詳細
PostgreSQL クエリのテスト
このスキルを使って、テスト、デバッグ、パフォーマンス分析のために PostgreSQL クエリをアドホックに実行します。
クエリの実行
付属のクエリスクリプトを使い、対象を指定してください。
node .claude/skills/postgres-query/query.mjs --prod "SELECT * FROM \"User\" LIMIT 5"
node .claude/skills/postgres-query/query.mjs --dev "SELECT * FROM \"User\" LIMIT 5"
対象
対象を必ず1つだけ選んでください。1回の呼び出しで対象フラグを2つ指定するとエラーになります。優先順位が暗黙に決まることはありません。また、認識されないオプション(--devv、--data_packet)は無視されず、拒否されます。入力ミスが見過ごされると、デフォルトの対象である本番環境にフォールスルーしてしまうためです。
| フラグ | 接続文字列 | 使用するケース |
|---|---|---|
--prod(デフォルト) | DATABASE_REPLICA_URL、または --writable を指定した DATABASE_URL | 本番のメインデータベース |
--dev | DEV_DATABASE_URL | 開発用 cnpg データベース。SSH トンネルが必要です |
--data-packet | DATABASE_DATA_PACKET_URL | DataPacket レプリカ(読み取り専用) |
--notifications | NOTIFICATION_DB_REPLICA_URL | notifications-db(読み取り専用)。SSH トンネルが必要です |
対象を省略しても本番環境に対して実行されるため、古いコマンドも引き続き動作します。ただし、実行時に No target flag given, defaulted to --prod と表示されます。フラグを指定してください。
実行のたびに、応答したデータベースが表示されます
接続する前に、スクリプトは標準エラー出力に、対象、アクセスモード、pg 自身が解決した user@host:port/database、および接続元の環境変数を示す行を出力します。
Target: PROD (read-only) -> <user>@<host>:25061/civitai [DATABASE_REPLICA_URL, timeout 30s]
--quiet と --json の場合も表示されます。また、データベースに到達するすべての終了経路の前に表示されます。ブロックされた書き込みの場合も含まれるため、直前に DELETE の対象にしたデータベースを必ず確認できます。標準エラー出力への表示なので、ファイルにパイプした --json の出力は引き続きクリーンな JSON です。
ホストとポートは、接続文字列を再解析した値ではなく、クライアント自身が解決した接続パラメーターから取得されます。そのため、この行の内容が実際のクエリの接続先と食い違うことはありません。
🔴 結果を信頼する前に、この行を確認してください。 このスキルの .env とアプリのルートにある .env はどちらも DATABASE_URL を定義していますが、それぞれ異なるデータベースを指しています。このスキルのものは本番環境、ルートのものは開発サーバーが使う開発用スナップショットです。このスキルの .env が優先されます。先に読み込まれ、loadEnv はまだ存在していないキーだけを補完するためです(空の値も存在するとみなされるので、このファイルに空の DATABASE_REPLICA_URL= が残っていても、そのキーがルートの .env に引き渡されることはありません。代わりに対象はこのファイルの DATABASE_URL にフォールバックし、バナーには read-only, but pointed at the primary と表示されます)。そのため、単独で query.mjs を実行すると本番環境から応答が返る一方、開発サーバーからは開発環境の応答が返ります。ここで読み取った値を、実行中のアプリから読み取った値と比較する場合、両方の行に同じホストとポートが表示されていなければ、異なるデータベースを比較していることになります。この違いが原因で1時間が無駄になり、実際には存在しない設定バグを根本原因だと確信して報告する結果になりました。2026-08-16 のことです。アプリのデータベースからは limit: 8、このスキルからは limit: 40 が返っていました。
オプション
| フラグ | 説明 |
|---|---|
--explain | クエリに対して EXPLAIN ANALYZE を実行します |
--writable | 読み取りレプリカの代わりにプライマリ接続を使います(ユーザー権限が必要です) |
--timeout <s>、-t | クエリのタイムアウト(秒)。デフォルトは30です |
--file、-f | ファイルからクエリを読み込みます |
--json | 結果を JSON として出力します |
--quiet、-q | 結果のみを最小限の出力で表示します |
--writable を --data-packet または --notifications と組み合わせるとエラーになります。どちらも読み取り専用のレプリカなので、この組み合わせは誤りを意味します。
例
# Simple query
node .claude/skills/postgres-query/query.mjs --prod "SELECT id, username FROM \"User\" LIMIT 5"
# Same query against the dev database
node .claude/skills/postgres-query/query.mjs --dev "SELECT id, username FROM \"User\" LIMIT 5"
# Check query performance
node .claude/skills/postgres-query/query.mjs --prod --explain "SELECT * FROM \"Model\" WHERE id = 1"
# Override default 30s timeout for longer queries
node .claude/skills/postgres-query/query.mjs --prod --timeout 60 "SELECT ... (complex query)"
# Query the notifications-db
node .claude/skills/postgres-query/query.mjs --notifications "SELECT count(*) FROM \"Notification\""
# Query from file
node .claude/skills/postgres-query/query.mjs --dev -f my-query.sql
# JSON output for processing (banner goes to stderr, stdout stays valid JSON)
node .claude/skills/postgres-query/query.mjs --prod --json "SELECT id, username FROM \"User\" LIMIT 3"
同じ名前のスキルがもう1つあります
apps/event-engine/.claude/skills/postgres-query/ は、独自の .env を持つ別のコピーで、上記の機能はどれもありません。対象フラグもバナーもありません。スキルはディレクトリごとに適用されるため、apps/event-engine 以下の作業ではそちらが使われます。出力を信頼する前に、パスでどのスクリプトを実行しているか確認してください。
dev データベース(cnpg)へのクエリ
dev データベースには直接接続できません。内部ホストへの SSH トンネルが必要です。接続手順については、インフラ担当者に確認してください。このリポジトリは公開されているため、詳細はここには記載していません(CLAUDE.md のセキュリティセクションを参照)。
トンネルを確立したら、.claude/skills/postgres-query/.env の DEV_DATABASE_URL を設定し、ローカルの転送ポートを指定してください。
dev クエリの実行
# Read-only (default — writes are blocked client-side)
node .claude/skills/postgres-query/query.mjs --dev "SELECT count(*) FROM \"User\""
# Writable DML (needs user permission)
node .claude/skills/postgres-query/query.mjs --dev --writable "UPDATE ..."
--dev は、どちらの場合も DEV_DATABASE_URL を使用します。--prod と異なり、別のレプリカ認証情報はないため、--writable はクライアント側のガードを解除するだけです。書き込みが実際に反映されるかどうかは、その URL のロールによって決まります。
この認証情報では DDL も実行できます。2026-08-17 に、dev と prod のプライマリの両方でロール civitai として検証済みです。ALTER TABLE ... ADD COLUMN、ADD CONSTRAINT、CREATE INDEX、および CREATE TABLE/DROP TABLE の一連の操作はすべて成功しました。マイグレーションは --writable を指定して適用できます。
以前、このセクションには反対の内容が記載されていました。ddl_command_end イベントトリガーによってオブジェクトの所有権が再割り当てされるため、CREATE TABLE は必ず must be able to SET ROLE "<schema owner>" でロールバックされる、という説明です。そのような事象は確認されておらず、上記の実行結果とも矛盾しています。不要なインフラ担当者へのエスカレーションが発生しました。所有権エラーが発生した場合は、実行した内容を記録し、このセクションを再度修正してください。以前の一律な主張に戻してはいけません。
⚠️ 複数ステートメントのスクリプトでは、最初のステートメントが無害な場合でも --writable が必要です。 クライアント側の書き込みガードは、各ステートメントの先頭のキーワードを確認します。そのため、SET lock_timeout/BEGIN で始まるマイグレーションも、後続に ALTER が含まれるため拒否されます。これは意図的な動作です。
notifications-db へのクエリ
notifications データベースには直接接続できません。内部ホストへの SSH トンネルが必要で、事前にアクセス権を付与してもらう必要があります。
アクセス権と接続手順については、インフラ担当者に確認してください。 このリポジトリは公開されているため、踏み台ホスト、転送先、認証情報の保管場所は意図的にここに記載していません。CLAUDE.md のセキュリティセクションを参照してください。
トンネルを開いたら、.claude/skills/postgres-query/.env の NOTIFICATION_DB_REPLICA_URL を設定し、ローカルの転送ポートを指定してください。
クエリの実行
# With the tunnel open in another terminal:
node .claude/skills/postgres-query/query.mjs --notifications \
"SELECT count(*) FROM \"Notification\""
node .claude/skills/postgres-query/query.mjs --notifications --explain \
"SELECT * FROM \"UserNotification\" WHERE \"userId\" = 12345 ORDER BY \"createdAt\" DESC LIMIT 50"
利用可能なテーブル(読み取り専用)
Notification— 通知の正規データUserNotification— ユーザーごとのファンアウト(最大のテーブル)PendingNotification— 処理キュー(空の場合が多い)
ロール notifications_readonly に許可されているのは SELECT のみです。プールャーのレベルでも書き込みは拒否されます(レプリカへのルーティング)。
安全機能
- デフォルトで読み取り専用:
--prodはDATABASE_REPLICA_URLを使用し、誤った書き込みを防止します - 書き込み保護:
--writableフラグが使用されない限り、INSERT/UPDATE/DELETE/DROP をブロックします - レプリカの接続先では
--writableを拒否:--notificationsと--data-packetではこのフラグを指定するとエラーになり、ロールやプールャーでも書き込みが拒否されます - 明示的な許可が必要:
--writableを使用する前に、必ずユーザーに許可を求めてください - 実行のたびに接続先を表示:誤って別のデータベースに結果を適用することはありません
⚠️ 書き込みガードは誤記の検出用であり、サンドボックスではありません。 クエリの先頭のキーワード(先頭のコメントを除去した後)と、先頭の WITH 内にある (INSERT|UPDATE|DELETE|… を照合します。それより深い位置にある書き込みは、クライアントを通過します。書き込みを実際に阻止するのは、接続に使用するロールです。そのため、--prod はデフォルトで読み取り専用のレプリカ認証情報を使用します。--writable を指定していないことを、クエリが書き込めない証拠とみなさないでください。
--writable を使用する場合
--writable フラグは、次の場合にのみ使用してください。
- ユーザーが書き込みアクセスを明示的に要求している
- 書き込み操作をテストする必要がある
- トランザクションの動作を検証している
重要:--writable を指定して実行する前に、必ずユーザーに許可を求めてください。
クエリのパフォーマンスを比較する
2 つのクエリアプローチを比較する場合は、どちらの実行でも同じ対象を指定してください。そうしないと、比較に意味がありません。
# Run first approach
node .claude/skills/postgres-query/query.mjs --prod --explain "SELECT ... (approach 1)"
# Run second approach
node .claude/skills/postgres-query/query.mjs --prod --explain "SELECT ... (approach 2)"
# Compare actual results
node .claude/skills/postgres-query/query.mjs --prod --json "SELECT ... (approach 1)" > /tmp/q1.json
node .claude/skills/postgres-query/query.mjs --prod --json "SELECT ... (approach 2)" > /tmp/q2.json
インデックスの使用状況を確認する
--explain を付けて実行し、次の項目を確認します。
- 良好:「Index Scan」、「Bitmap Index Scan」、「Index Only Scan」
- 不良:大きなテーブルでの「Seq Scan」(インデックスが存在しないか、使用されていないことを示します)
node .claude/skills/postgres-query/query.mjs --prod --explain "SELECT * FROM \"Account\" WHERE provider = 'discord'"