Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/db/migration-column-extraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ export function extractSchemaEvents(rawStatement: string): SchemaEvent[] {
const dropTableMatch = /^\s*DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?(\w+)/i.exec(statement);
if (dropTableMatch) return [{ type: "drop_table", table: dropTableMatch[1]!.toLowerCase() }];

const renameColumnMatch = /\bALTER\s+TABLE\s+(\w+)\s+RENAME\s+COLUMN\s+(\w+)\s+TO\s+(\w+)/i.exec(statement);
// SQLite's ALTER TABLE grammar makes COLUMN optional for RENAME/DROP/ADD (#8368) -- `ALTER TABLE t ADD x
// INTEGER`, `DROP x`, and `RENAME x TO y` are all valid without it, so each regex tolerates the absent
// keyword instead of silently producing zero SchemaEvents for the terser (equally valid) form.
const renameColumnMatch = /\bALTER\s+TABLE\s+(\w+)\s+RENAME\s+(?:COLUMN\s+)?(\w+)\s+TO\s+(\w+)/i.exec(statement);
if (renameColumnMatch) {
const table = renameColumnMatch[1]!.toLowerCase();
return [
Expand All @@ -172,10 +175,10 @@ export function extractSchemaEvents(rawStatement: string): SchemaEvent[] {
];
}

const dropColumnMatch = /\bALTER\s+TABLE\s+(\w+)\s+DROP\s+COLUMN\s+(\w+)/i.exec(statement);
const dropColumnMatch = /\bALTER\s+TABLE\s+(\w+)\s+DROP\s+(?:COLUMN\s+)?(\w+)/i.exec(statement);
if (dropColumnMatch) return [{ type: "remove_column", table: dropColumnMatch[1]!.toLowerCase(), column: dropColumnMatch[2]!.toLowerCase() }];

const addColumnMatch = /\bALTER\s+TABLE\s+(\w+)\s+ADD\s+COLUMN\s+(\w+)/i.exec(statement);
const addColumnMatch = /\bALTER\s+TABLE\s+(\w+)\s+ADD\s+(?:COLUMN\s+)?(\w+)/i.exec(statement);
if (addColumnMatch) return [{ type: "define_column", table: addColumnMatch[1]!.toLowerCase(), column: addColumnMatch[2]!.toLowerCase() }];

const createTableMatch = /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s*\(([\s\S]*)\)[^)]*$/i.exec(statement);
Expand Down
15 changes: 15 additions & 0 deletions test/unit/migration-column-extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ describe("extractSchemaEvents (#2551)", () => {
]);
});

it("extracts ADD without the optional COLUMN keyword (#8368: valid terser SQLite syntax)", () => {
expect(extractSchemaEvents("ALTER TABLE widgets ADD color TEXT;")).toEqual([{ type: "define_column", table: "widgets", column: "color" }]);
});

it("extracts DROP without the optional COLUMN keyword (#8368: valid terser SQLite syntax)", () => {
expect(extractSchemaEvents("ALTER TABLE widgets DROP color;")).toEqual([{ type: "remove_column", table: "widgets", column: "color" }]);
});

it("extracts RENAME without the optional COLUMN keyword (#8368: valid terser SQLite syntax)", () => {
expect(extractSchemaEvents("ALTER TABLE widgets RENAME color TO hue;")).toEqual([
{ type: "remove_column", table: "widgets", column: "color" },
{ type: "define_column", table: "widgets", column: "hue" },
]);
});

it("returns [] for a statement with no schema-shape effect (CREATE INDEX, INSERT)", () => {
expect(extractSchemaEvents("CREATE INDEX widgets_name_idx ON widgets (name);")).toEqual([]);
expect(extractSchemaEvents("INSERT INTO widgets (name) VALUES ('x');")).toEqual([]);
Expand Down