Browse Source

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
pull/136/head
Matthew Raymer 6 days ago
parent
commit
b7b6be5831
  1. 52
      electron/src/rt/sqlite-migrations.ts

52
electron/src/rt/sqlite-migrations.ts

@ -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');

Loading…
Cancel
Save