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;
};
// 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
const INITIAL_MIGRATION: Migration = {
version: 1,
@ -357,24 +382,10 @@ const INITIAL_MIGRATION: Migration = {
const MIGRATIONS: Migration[] = [
INITIAL_MIGRATION,
{
version: 1,
name: 'initial_schema',
description: 'Initial database schema with accounts, secret, settings, contacts, logs, and temp tables',
version: 2,
name: '002_secret_and_settings',
description: 'Add secret, settings, contacts, logs, and temp tables',
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
-- Note: This is a temporary solution until better secure storage is implemented
CREATE TABLE IF NOT EXISTS secret (
@ -447,7 +458,6 @@ const MIGRATIONS: Migration[] = [
);
`,
rollback: `
DROP TABLE IF EXISTS accounts;
DROP TABLE IF EXISTS secret;
DROP TABLE IF EXISTS settings;
DROP TABLE IF EXISTS contacts;
@ -457,6 +467,9 @@ const MIGRATIONS: Migration[] = [
}
];
// Validate migrations before export
validateMigrationVersions(MIGRATIONS);
// Helper functions
const verifyPluginState = async (plugin: any): Promise<boolean> => {
try {
@ -1004,6 +1017,9 @@ export async function runMigrations(
): Promise<MigrationResult[]> {
logger.info('Starting migration process');
// Validate migrations before running
validateMigrationVersions(MIGRATIONS);
// Verify plugin is available
if (!await verifyPluginState(plugin)) {
throw new Error('SQLite plugin not available');

Loading…
Cancel
Save