release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import type { Migration } from "./migrate.ts";
|
||||
|
||||
export interface MigrationSafetyIssue {
|
||||
code:
|
||||
| "WRN-DB-DROP-TABLE"
|
||||
| "WRN-DB-DROP-COLUMN"
|
||||
| "WRN-DB-RENAME"
|
||||
| "WRN-DB-TYPE-CHANGE"
|
||||
| "WRN-DB-SET-NOT-NULL"
|
||||
| "WRN-DB-ADD-REQUIRED"
|
||||
| "WRN-DB-BLOCKING-INDEX";
|
||||
severity: "error" | "warning";
|
||||
migration: string;
|
||||
statement: string;
|
||||
recommendation: string;
|
||||
}
|
||||
|
||||
function statements(sql: string): string[] {
|
||||
return sql
|
||||
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
||||
.split(";")
|
||||
.map((statement) =>
|
||||
statement
|
||||
.replace(/--[^\r\n]*/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim(),
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function analyzeMigrationSafety(migration: Migration): MigrationSafetyIssue[] {
|
||||
const issues: MigrationSafetyIssue[] = [];
|
||||
const add = (
|
||||
code: MigrationSafetyIssue["code"],
|
||||
severity: MigrationSafetyIssue["severity"],
|
||||
statement: string,
|
||||
recommendation: string,
|
||||
): void => {
|
||||
issues.push({ code, severity, migration: migration.name, statement, recommendation });
|
||||
};
|
||||
for (const statement of statements(migration.up)) {
|
||||
if (/\bDROP\s+TABLE\b/i.test(statement))
|
||||
add(
|
||||
"WRN-DB-DROP-TABLE",
|
||||
"error",
|
||||
statement,
|
||||
"Deprecate reads/writes first; drop in a later contract release.",
|
||||
);
|
||||
if (
|
||||
/\bDROP\s+(?:COLUMN\s+)?[A-Za-z_][\w$]*/i.test(statement) &&
|
||||
/\bALTER\s+TABLE\b/i.test(statement)
|
||||
)
|
||||
add(
|
||||
"WRN-DB-DROP-COLUMN",
|
||||
"error",
|
||||
statement,
|
||||
"Stop all old-version reads before a separate contract migration.",
|
||||
);
|
||||
if (/\bRENAME\s+(?:COLUMN\s+)?\b|\bRENAME\s+TO\b/i.test(statement))
|
||||
add(
|
||||
"WRN-DB-RENAME",
|
||||
"error",
|
||||
statement,
|
||||
"Add the new name, dual-write/backfill, switch readers, then remove the old name.",
|
||||
);
|
||||
if (
|
||||
/\bALTER\s+(?:COLUMN\s+)?[A-Za-z_][\w$]*\s+(?:TYPE|SET\s+DATA\s+TYPE)\b|\bMODIFY\s+(?:COLUMN\s+)?[A-Za-z_]/i.test(
|
||||
statement,
|
||||
)
|
||||
)
|
||||
add(
|
||||
"WRN-DB-TYPE-CHANGE",
|
||||
"error",
|
||||
statement,
|
||||
"Add a compatible column and backfill before switching readers.",
|
||||
);
|
||||
if (/\bALTER\s+(?:COLUMN\s+)?[A-Za-z_][\w$]*\s+SET\s+NOT\s+NULL\b/i.test(statement))
|
||||
add(
|
||||
"WRN-DB-SET-NOT-NULL",
|
||||
"error",
|
||||
statement,
|
||||
"Backfill and validate existing rows before enforcing NOT NULL.",
|
||||
);
|
||||
if (
|
||||
/\bADD\s+(?:COLUMN\s+)?[A-Za-z_][\w$]*[\s\S]*\bNOT\s+NULL\b/i.test(statement) &&
|
||||
!/\bDEFAULT\b/i.test(statement)
|
||||
)
|
||||
add(
|
||||
"WRN-DB-ADD-REQUIRED",
|
||||
"error",
|
||||
statement,
|
||||
"Add nullable, backfill in batches, then enforce the constraint.",
|
||||
);
|
||||
if (/^CREATE\s+(?:UNIQUE\s+)?INDEX\b/i.test(statement) && !/\bCONCURRENTLY\b/i.test(statement))
|
||||
add(
|
||||
"WRN-DB-BLOCKING-INDEX",
|
||||
"warning",
|
||||
statement,
|
||||
"Use an online/concurrent index operation when the database supports it.",
|
||||
);
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function analyzeMigrations(migrations: readonly Migration[]): MigrationSafetyIssue[] {
|
||||
return migrations.flatMap(analyzeMigrationSafety);
|
||||
}
|
||||
Reference in New Issue
Block a user