Browse Source

fix: resolve migration 004 transaction and executeSet errors

- Remove explicit transaction wrapping in migration service that caused
  "cannot start a transaction within a transaction" errors
- Fix executeSet method call format to include both statement and values
  properties as required by Capacitor SQLite plugin
- Update CapacitorPlatformService to properly handle multi-statement SQL
  using executeSet for migration SQL blocks
- Ensure migration 004 (active_identity_management) executes atomically
  without nested transaction conflicts
- Remove unnecessary try/catch wrapper

Fixes iOS simulator migration failures where:
- Migration 004 would fail with transaction errors
- executeSet would fail with "Must provide a set as Array of {statement,values}"
- Database initialization would fail after migration errors

Tested on iOS simulator with successful migration completion and
active_identity table creation with proper data migration.
Jose Olarte III 1 month ago
parent
commit
1790a6c5d6
  1. 70
      src/services/migrationService.ts
  2. 21
      src/services/platforms/CapacitorPlatformService.ts

70
src/services/migrationService.ts

@ -687,58 +687,48 @@ export async function runMigrations<T>(
} }
try { try {
// Execute the migration SQL as single atomic operation with transaction // Execute the migration SQL as single atomic operation
if (isDevelopment) { if (isDevelopment) {
migrationLog(`🔧 [Migration] Executing SQL for: ${migration.name}`); migrationLog(`🔧 [Migration] Executing SQL for: ${migration.name}`);
migrationLog(`🔧 [Migration] SQL content: ${migration.sql}`); migrationLog(`🔧 [Migration] SQL content: ${migration.sql}`);
} }
// Begin transaction for atomic execution // Execute the migration SQL directly - it should be atomic
await sqlExec("BEGIN IMMEDIATE"); // The SQL itself should handle any necessary transactions
const execResult = await sqlExec(migration.sql);
try { if (isDevelopment) {
const execResult = await sqlExec(migration.sql); migrationLog(
`🔧 [Migration] SQL execution result: ${JSON.stringify(execResult)}`,
if (isDevelopment) { );
migrationLog( }
`🔧 [Migration] SQL execution result: ${JSON.stringify(execResult)}`,
);
}
// Validate the migration was applied correctly (only in development) // Validate the migration was applied correctly (only in development)
if (isDevelopment) { if (isDevelopment) {
const validation = await validateMigrationApplication( const validation = await validateMigrationApplication(
migration, migration,
sqlQuery, sqlQuery,
);
if (!validation.isValid) {
logger.warn(
`⚠️ [Migration] Validation failed for ${migration.name}:`,
validation.errors,
); );
if (!validation.isValid) {
logger.warn(
`⚠️ [Migration] Validation failed for ${migration.name}:`,
validation.errors,
);
}
} }
}
// Record that the migration was applied // Record that the migration was applied
await sqlExec("INSERT INTO migrations (name) VALUES (?)", [ await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
migration.name, migration.name,
]); ]);
// Commit transaction
await sqlExec("COMMIT");
// Only log success in development // Only log success in development
if (isDevelopment) { if (isDevelopment) {
migrationLog( migrationLog(
`🎉 [Migration] Successfully applied: ${migration.name}`, `🎉 [Migration] Successfully applied: ${migration.name}`,
); );
}
appliedCount++;
} catch (error) {
// Rollback transaction on any error
await sqlExec("ROLLBACK");
throw error;
} }
appliedCount++;
} catch (error) { } catch (error) {
logger.error(`❌ [Migration] Error applying ${migration.name}:`, error); logger.error(`❌ [Migration] Error applying ${migration.name}:`, error);

21
src/services/platforms/CapacitorPlatformService.ts

@ -508,9 +508,24 @@ export class CapacitorPlatformService implements PlatformService {
// This is essential for proper parameter binding and SQL injection prevention // This is essential for proper parameter binding and SQL injection prevention
await this.db!.run(sql, params); await this.db!.run(sql, params);
} else { } else {
// Use execute method for non-parameterized queries // For multi-statement SQL (like migrations), use executeSet method
// This is more efficient for simple DDL statements // This handles multiple statements properly
await this.db!.execute(sql); if (
sql.includes(";") &&
sql.split(";").filter((s) => s.trim()).length > 1
) {
// Multi-statement SQL - use executeSet for proper handling
const statements = sql.split(";").filter((s) => s.trim());
await this.db!.executeSet(
statements.map((stmt) => ({
statement: stmt.trim(),
values: [], // Empty values array for non-parameterized statements
})),
);
} else {
// Single statement - use execute method
await this.db!.execute(sql);
}
} }
}; };

Loading…
Cancel
Save