From 578dbe6177f7ee47095e6cd365272cabeb155384 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Fri, 5 Sep 2025 17:48:15 +0800 Subject: [PATCH] fix: simplify active_identity migration to resolve iOS SQLite failures The complex table rewrite approach in migration 003_active_did_separation was failing on iOS SQLite, causing "no such table: active_identity" errors. The migration was being marked as applied despite validation failures. Changes: - Simplify migration SQL to only create active_identity table and migrate data - Remove complex table rewrite that was failing on iOS SQLite versions - Remove foreign key constraint that could cause compatibility issues - Update validation logic to focus on active_identity table existence only - Remove validation check for activeDid column removal from settings table This approach is more reliable across different SQLite versions and platforms while maintaining the core functionality of separating activeDid into its own table for better database architecture. Fixes iOS build database errors and ensures migration completes successfully. --- src/db-sql/migration.ts | 47 ++++++------------------ src/services/migrationService.ts | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/db-sql/migration.ts b/src/db-sql/migration.ts index eda0a7e81..457b9f79a 100644 --- a/src/db-sql/migration.ts +++ b/src/db-sql/migration.ts @@ -133,16 +133,15 @@ const MIGRATIONS = [ { name: "003_active_did_separation", sql: ` - -- CONSOLIDATED MIGRATION: Combines original migrations 003, 004, 005, and 006 - -- This migration handles the complete separation of activeDid from settings - -- and establishes proper data integrity constraints + -- SIMPLIFIED MIGRATION: Create active_identity table and migrate data + -- This migration handles the separation of activeDid from settings + -- using a simpler, more reliable approach that works on all SQLite versions -- Create new active_identity table with proper constraints CREATE TABLE IF NOT EXISTS active_identity ( id INTEGER PRIMARY KEY CHECK (id = 1), - activeDid TEXT NOT NULL, - lastUpdated TEXT NOT NULL DEFAULT (datetime('now')), - FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE CASCADE + activeDid TEXT NOT NULL DEFAULT '', + lastUpdated TEXT NOT NULL DEFAULT (datetime('now')) ); -- Add performance indexes @@ -154,36 +153,14 @@ const MIGRATIONS = [ -- MIGRATE EXISTING DATA: Copy activeDid from settings to active_identity -- This prevents data loss when migration runs on existing databases + -- Use a more robust approach that handles missing columns gracefully UPDATE active_identity - SET activeDid = (SELECT activeDid FROM settings WHERE id = 1), - lastUpdated = datetime('now') - WHERE id = 1 - AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != ''); - - -- Remove activeDid column from settings table (moved to active_identity) - -- Note: SQLite doesn't support DROP COLUMN in older versions - -- This migration will be skipped if DROP COLUMN is not supported - -- The activeDid column will remain but won't be used by the application - ALTER TABLE settings DROP COLUMN activeDid; - - -- Eliminate MASTER_SETTINGS_KEY concept - remove confusing id=1 row - -- This creates clean separation: active_identity for current identity, settings for identity config - DELETE FROM settings WHERE id = 1 AND accountDid IS NULL; - - -- Reset auto-increment to start from 1 again - DELETE FROM sqlite_sequence WHERE name = 'settings'; - - -- Add unique constraint to prevent duplicate accountDid values - -- This ensures data integrity: each identity can only have one settings record - DELETE FROM settings - WHERE id NOT IN ( - SELECT MAX(id) - FROM settings - WHERE accountDid IS NOT NULL - GROUP BY accountDid - ) AND accountDid IS NOT NULL; - - CREATE UNIQUE INDEX IF NOT EXISTS idx_settings_accountDid_unique ON settings(accountDid); + SET activeDid = COALESCE( + (SELECT activeDid FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != ''), + '' + ), + lastUpdated = datetime('now') + WHERE id = 1; `, }, ]; diff --git a/src/services/migrationService.ts b/src/services/migrationService.ts index 93e769f47..9869512d1 100644 --- a/src/services/migrationService.ts +++ b/src/services/migrationService.ts @@ -280,6 +280,40 @@ async function validateMigrationApplication( error, ); } + } else if (migration.name === "003_active_did_separation") { + // Validate active_identity table exists and has correct structure + try { + // Check that active_identity table exists + const activeIdentityResult = await sqlQuery( + `SELECT name FROM sqlite_master WHERE type='table' AND name='active_identity'`, + ); + const hasActiveIdentityTable = + (activeIdentityResult as unknown as { values: unknown[][] })?.values?.length > 0 || + (Array.isArray(activeIdentityResult) && activeIdentityResult.length > 0); + + if (!hasActiveIdentityTable) { + validation.isValid = false; + validation.errors.push(`Table active_identity missing`); + } + + // Check that active_identity has the expected structure + try { + await sqlQuery(`SELECT id, activeDid, lastUpdated FROM active_identity LIMIT 1`); + validation.hasExpectedColumns = true; + } catch (error) { + validation.isValid = false; + validation.errors.push(`active_identity table missing expected columns`); + } + + validation.tableExists = hasActiveIdentityTable; + } catch (error) { + validation.isValid = false; + validation.errors.push(`Validation error for active_did_separation: ${error}`); + logger.error( + `❌ [Migration-Validation] Validation failed for ${migration.name}:`, + error, + ); + } } // Add validation for future migrations here @@ -343,6 +377,35 @@ async function isSchemaAlreadyPresent( // Reduced logging - only log on error return false; } + } else if (migration.name === "003_active_did_separation") { + // Check if active_identity table exists and has correct structure + try { + // Check that active_identity table exists + const activeIdentityResult = await sqlQuery( + `SELECT name FROM sqlite_master WHERE type='table' AND name='active_identity'`, + ); + const hasActiveIdentityTable = + (activeIdentityResult as unknown as { values: unknown[][] })?.values?.length > 0 || + (Array.isArray(activeIdentityResult) && activeIdentityResult.length > 0); + + if (!hasActiveIdentityTable) { + return false; + } + + // Check that active_identity has the expected structure + try { + await sqlQuery(`SELECT id, activeDid, lastUpdated FROM active_identity LIMIT 1`); + return true; + } catch (error) { + return false; + } + } catch (error) { + logger.error( + `🔍 [Migration-Schema] Schema check failed for ${migration.name}, assuming not present:`, + error, + ); + return false; + } } // Add schema checks for future migrations here