forked from trent_larson/crowd-funder-for-time-pwa
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.
This commit is contained in:
@@ -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)
|
|
||||||
if (isDevelopment) {
|
|
||||||
const validation = await validateMigrationApplication(
|
|
||||||
migration,
|
|
||||||
sqlQuery,
|
|
||||||
);
|
|
||||||
if (!validation.isValid) {
|
|
||||||
logger.warn(
|
|
||||||
`⚠️ [Migration] Validation failed for ${migration.name}:`,
|
|
||||||
validation.errors,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record that the migration was applied
|
|
||||||
await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
|
||||||
migration.name,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Commit transaction
|
|
||||||
await sqlExec("COMMIT");
|
|
||||||
|
|
||||||
// Only log success in development
|
|
||||||
if (isDevelopment) {
|
|
||||||
migrationLog(
|
|
||||||
`🎉 [Migration] Successfully applied: ${migration.name}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
appliedCount++;
|
|
||||||
} catch (error) {
|
|
||||||
// Rollback transaction on any error
|
|
||||||
await sqlExec("ROLLBACK");
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the migration was applied correctly (only in development)
|
||||||
|
if (isDevelopment) {
|
||||||
|
const validation = await validateMigrationApplication(
|
||||||
|
migration,
|
||||||
|
sqlQuery,
|
||||||
|
);
|
||||||
|
if (!validation.isValid) {
|
||||||
|
logger.warn(
|
||||||
|
`⚠️ [Migration] Validation failed for ${migration.name}:`,
|
||||||
|
validation.errors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record that the migration was applied
|
||||||
|
await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
||||||
|
migration.name,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Only log success in development
|
||||||
|
if (isDevelopment) {
|
||||||
|
migrationLog(
|
||||||
|
`🎉 [Migration] Successfully applied: ${migration.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
appliedCount++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`❌ [Migration] Error applying ${migration.name}:`, error);
|
logger.error(`❌ [Migration] Error applying ${migration.name}:`, error);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user