fix(db): detect duplicate-column from nested migration error cause
This commit is contained in:
@@ -125,4 +125,65 @@ describe('sqlite migrate bootstrap', () => {
|
||||
|
||||
sqlite.close();
|
||||
});
|
||||
|
||||
it('recovers when duplicate-column message appears only in error cause', async () => {
|
||||
const dataDir = mkdtempSync(join(tmpdir(), 'metapi-migrate-recover-cause-'));
|
||||
process.env.DATA_DIR = dataDir;
|
||||
vi.resetModules();
|
||||
|
||||
const migrateModule = await import('./migrate.js');
|
||||
const { __migrateTestUtils } = migrateModule;
|
||||
|
||||
const sqlite = new Database(':memory:');
|
||||
sqlite.exec(`
|
||||
CREATE TABLE account_tokens (
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
token_group text
|
||||
);
|
||||
`);
|
||||
|
||||
const tempMigrationsDir = mkdtempSync(join(tmpdir(), 'metapi-migration-files-cause-'));
|
||||
mkdirSync(join(tempMigrationsDir, 'meta'), { recursive: true });
|
||||
|
||||
writeFileSync(
|
||||
join(tempMigrationsDir, 'meta', '_journal.json'),
|
||||
JSON.stringify({
|
||||
entries: [
|
||||
{
|
||||
tag: '0007_account_token_group',
|
||||
when: 1772500000001,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
join(tempMigrationsDir, '0007_account_token_group.sql'),
|
||||
'ALTER TABLE `account_tokens` ADD `token_group` text;\n',
|
||||
);
|
||||
|
||||
const drizzleLikeError = {
|
||||
message: "DrizzleError: Failed to run the query 'ALTER TABLE `account_tokens` ADD `token_group` text;\n'",
|
||||
cause: {
|
||||
message: 'SqliteError: duplicate column name: token_group',
|
||||
},
|
||||
};
|
||||
|
||||
const recovered = __migrateTestUtils.tryRecoverDuplicateColumnMigrationError(
|
||||
sqlite,
|
||||
tempMigrationsDir,
|
||||
drizzleLikeError,
|
||||
);
|
||||
|
||||
expect(recovered).toBe(true);
|
||||
|
||||
const applied = sqlite
|
||||
.prepare('SELECT hash, created_at FROM __drizzle_migrations')
|
||||
.all() as Array<{ hash: string; created_at: number }>;
|
||||
|
||||
expect(applied).toHaveLength(1);
|
||||
expect(Number(applied[0].created_at)).toBe(1772500000001);
|
||||
|
||||
sqlite.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,9 +197,31 @@ function markMigrationRecordIfMissing(sqlite: Database.Database, record: Migrati
|
||||
}
|
||||
|
||||
function normalizeSchemaErrorMessage(error: unknown): string {
|
||||
if (typeof error === 'object' && error && 'message' in error) {
|
||||
return String((error as { message?: unknown }).message || '');
|
||||
if (!error || typeof error !== 'object') {
|
||||
return String(error || '');
|
||||
}
|
||||
|
||||
const collected: string[] = [];
|
||||
let cursor: unknown = error;
|
||||
let depth = 0;
|
||||
|
||||
while (cursor && typeof cursor === 'object' && depth < 8) {
|
||||
const current = cursor as { message?: unknown; cause?: unknown };
|
||||
if (current.message !== undefined && current.message !== null) {
|
||||
const text = String(current.message).trim();
|
||||
if (text.length > 0) {
|
||||
collected.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
cursor = current.cause;
|
||||
depth += 1;
|
||||
}
|
||||
|
||||
if (collected.length > 0) {
|
||||
return collected.join(' | ');
|
||||
}
|
||||
|
||||
return String(error || '');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user