/**
* `scripts/local-db/20261029_error_summaries.sql` を ★ローカルの dwatch_local にだけ★ 流す実行部。
* facility_error_summaries / customer_error_summaries の error_detail を消し、error_list_id(error_lists への外部キー)を足す
* (出所: ステージングの information_schema 2026-10-29。先方の DDL 変更に合わせる)。
*
* ★実行方法: `npx tsx scripts/local-db/apply-error-summaries.ts`★(先に `apply-error-lists.ts` を流して error_lists があること)
* ★DATABASE_URL がローカル(localhost / 127.0.0.1)の dwatch_local を指していなければ、接続する前に止まる★
* (`lib/db/local-guard.ts`。シードと同じ歯止め)。`.env.local` の DATABASE_URL がステージングを指している間は、
* 実行するシェルでだけ DATABASE_URL をローカルに向けて渡すこと(process.env は .env.local より優先される)。
* ★ステージング・本番には絶対に流さない★ — スキーマは TeamWorks の領域(CLAUDE.md §2 / §7)。
*
* ★何度流しても同じ★(SQL 側が information_schema の分岐で書いてある)。
* ★接続後にも `DATABASE()` が dwatch_local であることを確かめてから流す★(URL の検査だけに頼らない)。
* ★流す前後で行数が変わらないことを確かめる★(ALTER だけなので行は増減しない)。
* ★パスワード・接続文字列は出力しない★(CLAUDE.md §9.3)。
*
* `mariadb` は `@prisma/adapter-mariadb` の依存として入っているドライバ(直接の依存ではない)。
* 複数文をまとめて流すため Prisma ではなくこれを使う(apply-error-lists.ts と同じ)。
*/
import dotenv from "dotenv";
dotenv.config({ path: ".env.local" });
dotenv.config();
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import mariadb from "mariadb";
import { assertLocalDwatchDatabaseUrl } from "../../lib/db/local-guard";
const LOCAL_DATABASE_NAME = "dwatch_local";
const SQL_FILE = join(dirname(fileURLToPath(import.meta.url)), "20261029_error_summaries.sql");
const TABLES = ["facility_error_summaries", "customer_error_summaries"] as const;
async function countRows(connection: mariadb.Connection): Promise<Record<string, number>> {
const counts: Record<string, number> = {};
for (const table of TABLES) {
// 表名は上の定数だけ(外から来た値は入らない)
const rows = await connection.query<{ n: bigint | number }[]>(`SELECT COUNT(*) AS n FROM ${table}`);
counts[table] = Number(rows[0]?.n ?? 0);
}
return counts;
}
async function main(): Promise<void> {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error("[apply-error-summaries] DATABASE_URL が未設定です(値はこのエラーには出しません)。");
}
assertLocalDwatchDatabaseUrl(databaseUrl);
const url = new URL(databaseUrl);
const connection = await mariadb.createConnection({
host: url.hostname,
port: Number(url.port || "3306"),
user: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
database: LOCAL_DATABASE_NAME,
multipleStatements: true,
});
try {
const [current] = await connection.query<{ db: string | null }[]>("SELECT DATABASE() AS db");
if (current?.db !== LOCAL_DATABASE_NAME) {
throw new Error(`[apply-error-summaries] 接続先の DB が ${LOCAL_DATABASE_NAME} ではありません。中断しました。`);
}
const before = await countRows(connection);
await connection.query(readFileSync(SQL_FILE, "utf8"));
const after = await countRows(connection);
for (const table of TABLES) {
const columns = await connection.query<{ COLUMN_NAME: string; COLUMN_TYPE: string; IS_NULLABLE: string }[]>(
"SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME IN ('error_list_id', 'error_detail')",
[LOCAL_DATABASE_NAME, table]
);
const foreignKeys = await connection.query<{ n: bigint | number }[]>(
"SELECT COUNT(*) AS n FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = ? AND TABLE_NAME = ? AND CONSTRAINT_NAME = ? AND REFERENCED_TABLE_NAME = 'error_lists'",
[LOCAL_DATABASE_NAME, table, `fk_${table}_error_list_id`]
);
const listId = columns.find((c) => c.COLUMN_NAME === "error_list_id");
const hasDetail = columns.some((c) => c.COLUMN_NAME === "error_detail");
if (!listId || listId.IS_NULLABLE !== "NO" || hasDetail || Number(foreignKeys[0]?.n ?? 0) !== 1) {
throw new Error(`[apply-error-summaries] 流したあとの ${table} の形が想定と違います(error_list_id / error_detail / 外部キー)。`);
}
if (before[table] !== after[table]) {
throw new Error(`[apply-error-summaries] ${table} の行数が変わりました(${before[table]} → ${after[table]})。`);
}
console.log(
`[apply-error-summaries] ${LOCAL_DATABASE_NAME}.${table}: error_list_id = ${listId.COLUMN_TYPE}(NULL 可: ${listId.IS_NULLABLE})/ error_detail なし / 外部キーあり / 行数 ${after[table]}`
);
}
} finally {
await connection.end();
}
}
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
});