migrate method
Initialize or update the database.
Throws MigrationError if the database cannot be migrated.
Implementation
Future<void> migrate(SqliteConnection db) async {
  _validateCreateDatabase();
  await db.writeTransaction((tx) async {
    await tx.execute(
        'CREATE TABLE IF NOT EXISTS $migrationTable(id INTEGER PRIMARY KEY, down_migrations TEXT)');
    int currentVersion = await getCurrentVersion(tx);
    if (currentVersion == version) {
      return;
    }
    // Handle down migrations
    while (currentVersion > version) {
      final migrationRow = await tx.getOptional(
          'SELECT id, down_migrations FROM $migrationTable WHERE id > ? ORDER BY id DESC LIMIT 1',
          [version]);
      if (migrationRow == null || migrationRow['down_migrations'] == null) {
        throw MigrationError(
            'No down migration found from $currentVersion to $version');
      }
      final migrations = jsonDecode(migrationRow['down_migrations']);
      for (var migration in migrations) {
        await tx.execute(migration['sql'], migration['params']);
      }
      // Refresh version
      int prevVersion = currentVersion;
      currentVersion = await getCurrentVersion(tx);
      if (prevVersion == currentVersion) {
        throw MigrationError(
            'Database down from version $currentVersion to $version failed - version not updated after dow migration');
      }
    }
    // Clean setup
    if (currentVersion == 0 && createDatabase != null) {
      await createDatabase!.fn(tx);
      // Still need to persist the migrations
      for (var migration in migrations) {
        if (migration.toVersion <= createDatabase!.toVersion) {
          await _persistMigration(migration, tx, migrationTable);
        }
      }
      currentVersion = await getCurrentVersion(tx);
    }
    // Up migrations
    for (var migration in migrations) {
      if (migration.toVersion > currentVersion) {
        await migration.fn(tx);
        await _persistMigration(migration, tx, migrationTable);
      }
    }
  });
}