fix(sqlite): resolve duplicate table creation in migrations
Split initial schema into two sequential migrations to prevent duplicate table creation and improve migration clarity. Changes: - Separate initial schema into two distinct migrations: * 001_initial_accounts (v1): Create accounts table & index * 002_secret_and_settings (v2): Create remaining tables (secret, settings, contacts, logs, temp) - Add version conflict detection to prevent duplicate migration versions - Ensure migrations are sequential (no gaps) - Update rollback scripts to only drop relevant tables Technical Details: - Add validateMigrationVersions() to check for: * Duplicate version numbers * Sequential version ordering * Gaps in version numbers - Validate migrations both at definition time and runtime - Update schema_version tracking to reflect new versioning Testing: - Verified no duplicate table creation - Confirmed migrations run in correct order - Validated rollback procedures - Checked version conflict detection
This commit is contained in:
@@ -323,6 +323,31 @@ const parseSQL = (sql: string): ParsedSQL => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add version conflict detection
|
||||||
|
const validateMigrationVersions = (migrations: Migration[]): void => {
|
||||||
|
const versions = new Set<number>();
|
||||||
|
const duplicates = new Set<number>();
|
||||||
|
|
||||||
|
migrations.forEach(migration => {
|
||||||
|
if (versions.has(migration.version)) {
|
||||||
|
duplicates.add(migration.version);
|
||||||
|
}
|
||||||
|
versions.add(migration.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duplicates.size > 0) {
|
||||||
|
throw new Error(`Duplicate migration versions found: ${Array.from(duplicates).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify versions are sequential
|
||||||
|
const sortedVersions = Array.from(versions).sort((a, b) => a - b);
|
||||||
|
for (let i = 0; i < sortedVersions.length; i++) {
|
||||||
|
if (sortedVersions[i] !== i + 1) {
|
||||||
|
throw new Error(`Migration versions must be sequential. Found gap at version ${i + 1}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Initial migration for accounts table
|
// Initial migration for accounts table
|
||||||
const INITIAL_MIGRATION: Migration = {
|
const INITIAL_MIGRATION: Migration = {
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -357,24 +382,10 @@ const INITIAL_MIGRATION: Migration = {
|
|||||||
const MIGRATIONS: Migration[] = [
|
const MIGRATIONS: Migration[] = [
|
||||||
INITIAL_MIGRATION,
|
INITIAL_MIGRATION,
|
||||||
{
|
{
|
||||||
version: 1,
|
version: 2,
|
||||||
name: 'initial_schema',
|
name: '002_secret_and_settings',
|
||||||
description: 'Initial database schema with accounts, secret, settings, contacts, logs, and temp tables',
|
description: 'Add secret, settings, contacts, logs, and temp tables',
|
||||||
sql: `
|
sql: `
|
||||||
-- Accounts table for user identities
|
|
||||||
CREATE TABLE IF NOT EXISTS accounts (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
dateCreated TEXT NOT NULL,
|
|
||||||
derivationPath TEXT,
|
|
||||||
did TEXT NOT NULL,
|
|
||||||
identityEncrBase64 TEXT, -- encrypted & base64-encoded
|
|
||||||
mnemonicEncrBase64 TEXT, -- encrypted & base64-encoded
|
|
||||||
passkeyCredIdHex TEXT,
|
|
||||||
publicKeyHex TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did);
|
|
||||||
|
|
||||||
-- Secret table for storing encryption keys
|
-- Secret table for storing encryption keys
|
||||||
-- Note: This is a temporary solution until better secure storage is implemented
|
-- Note: This is a temporary solution until better secure storage is implemented
|
||||||
CREATE TABLE IF NOT EXISTS secret (
|
CREATE TABLE IF NOT EXISTS secret (
|
||||||
@@ -447,7 +458,6 @@ const MIGRATIONS: Migration[] = [
|
|||||||
);
|
);
|
||||||
`,
|
`,
|
||||||
rollback: `
|
rollback: `
|
||||||
DROP TABLE IF EXISTS accounts;
|
|
||||||
DROP TABLE IF EXISTS secret;
|
DROP TABLE IF EXISTS secret;
|
||||||
DROP TABLE IF EXISTS settings;
|
DROP TABLE IF EXISTS settings;
|
||||||
DROP TABLE IF EXISTS contacts;
|
DROP TABLE IF EXISTS contacts;
|
||||||
@@ -457,6 +467,9 @@ const MIGRATIONS: Migration[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Validate migrations before export
|
||||||
|
validateMigrationVersions(MIGRATIONS);
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
const verifyPluginState = async (plugin: any): Promise<boolean> => {
|
const verifyPluginState = async (plugin: any): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
@@ -1004,6 +1017,9 @@ export async function runMigrations(
|
|||||||
): Promise<MigrationResult[]> {
|
): Promise<MigrationResult[]> {
|
||||||
logger.info('Starting migration process');
|
logger.info('Starting migration process');
|
||||||
|
|
||||||
|
// Validate migrations before running
|
||||||
|
validateMigrationVersions(MIGRATIONS);
|
||||||
|
|
||||||
// Verify plugin is available
|
// Verify plugin is available
|
||||||
if (!await verifyPluginState(plugin)) {
|
if (!await verifyPluginState(plugin)) {
|
||||||
throw new Error('SQLite plugin not available');
|
throw new Error('SQLite plugin not available');
|
||||||
|
|||||||
Reference in New Issue
Block a user