Room veritabanınızı taşıyın

Uygulamanıza özellik ekledikçe ve özellikleri değiştirdikçe bu değişiklikleri yansıtmak için Room varlığı sınıflarınızı ve temel veritabanı tablolarınızı değiştirmeniz gerekir. Bir uygulama güncellemesi veritabanı şemasını değiştirdiğinde cihazdaki veritabanında bulunan kullanıcı verilerinin korunması önemlidir.

Room, artımlı taşıma için hem otomatik hem de manuel seçenekleri destekler. Otomatik taşıma işlemleri, şemadaki çoğu temel değişiklik için çalışır ancak daha karmaşık değişiklikler için taşıma yollarını manuel olarak tanımlamanız gerekebilir.

Otomatik taşıma işlemleri

İki veritabanı sürümü arasında otomatik taşıma bildirmek için @Database içindeki autoMigrations özelliğine @AutoMigration ekleyin:

Kotlin

// Database class before the version update.
@Database(
  version = 1,
  entities = [User::class]
)
abstract class AppDatabase : RoomDatabase() {
  ...
}

// Database class after the version update.
@Database(
  version = 2,
  entities = [User::class],
  autoMigrations = [
    AutoMigration (from = 1, to = 2)
  ]
)
abstract class AppDatabase : RoomDatabase() {
  ...
}

Java

// Database class before the version update.
@Database(
  version = 1,
  entities = {User.class}
)
public abstract class AppDatabase extends RoomDatabase {
  ...
}

// Database class after the version update.
@Database(
  version = 2,
  entities = {User.class},
  autoMigrations = {
    @AutoMigration (from = 1, to = 2)
  }
)
public abstract class AppDatabase extends RoomDatabase {
  ...
}

Otomatik taşıma özellikleri

Room, belirsiz şema değişiklikleri algılarsa ve daha fazla giriş olmadan taşıma planı oluşturamazsa derleme zamanı hatası verir ve sizden AutoMigrationSpec uygulamanızı ister. Bu durum en sık olarak, taşıma işlemi aşağıdakilerden birini içerdiğinde ortaya çıkar:

  • Tabloyu silme veya yeniden adlandırma
  • Sütun silme veya yeniden adlandırma

Room'a taşıma yollarını doğru şekilde oluşturması için gereken ek bilgileri vermek üzere AutoMigrationSpec kullanabilirsiniz. AutoMigrationSpec sınıfınızda RoomDatabase uygulayan ve aşağıdaki öğelerden biri veya daha fazlasıyla açıklama eklenmiş statik bir sınıf tanımlayın:

Otomatik taşıma için AutoMigrationSpec uygulamasını kullanmak üzere ilgili @AutoMigration açıklamasında spec özelliğini ayarlayın:

Kotlin

@Database(
  version = 2,
  entities = [User::class],
  autoMigrations = [
    AutoMigration (
      from = 1,
      to = 2,
      spec = AppDatabase.MyAutoMigration::class
    )
  ]
)
abstract class AppDatabase : RoomDatabase() {
  @RenameTable(fromTableName = "User", toTableName = "AppUser")
  class MyAutoMigration : AutoMigrationSpec
  ...
}

Java

@Database(
  version = 2,
  entities = {AppUser.class},
  autoMigrations = {
    @AutoMigration (
      from = 1,
      to = 2,
      spec = AppDatabase.MyAutoMigration.class
    )
  }
)
public abstract class AppDatabase extends RoomDatabase {
  @RenameTable(fromTableName = "User", toTableName = "AppUser")
  static class MyAutoMigration implements AutoMigrationSpec { }
  ...
}

Uygulamanızın otomatik taşıma işlemi tamamlandıktan sonra daha fazla işlem yapması gerekiyorsa onPostMigrate() uygulayabilirsiniz. Bu yöntemi AutoMigrationSpec içinde uygularsanız Room, otomatik taşıma işlemi tamamlandıktan sonra bu yöntemi çağırır.

Manuel taşıma işlemleri

Taşıma işleminin karmaşık şema değişikliklerini içerdiği durumlarda Room, uygun bir taşıma yolu oluşturamayabilir. Örneğin, bir tablodaki verileri iki tabloya bölmeye karar verirseniz Room bu bölme işleminin nasıl yapılacağını bilemez. Bu gibi durumlarda, Migration sınıfı uygulayarak taşıma yolunu manuel olarak tanımlamanız gerekir.

Bir Migration sınıfı, Migration.migrate() yöntemini geçersiz kılarak startVersion ile endVersion arasında açıkça bir taşıma yolu tanımlar. addMigrations() yöntemini kullanarak Migration sınıflarınızı veritabanı oluşturucunuza ekleyin:

Kotlin

val MIGRATION_1_2 = object : Migration(1, 2) {
  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
      "PRIMARY KEY(`id`))")
  }
}

val MIGRATION_2_3 = object : Migration(2, 3) {
  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
  }
}

Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
  .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()

Java

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
  @Override
  public void migrate(SupportSQLiteDatabase database) {
    database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
      + "`name` TEXT, PRIMARY KEY(`id`))");
  }
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
  @Override
  public void migrate(SupportSQLiteDatabase database) {
    database.execSQL("ALTER TABLE Book "
      + " ADD COLUMN pub_year INTEGER");
  }
};

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
  .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

Taşıma yollarınızı tanımlarken bazı sürümler için otomatik taşımaları, diğerleri için ise manuel taşımaları kullanabilirsiniz. Aynı sürüm için hem otomatik hem de manuel taşıma tanımlarsanız Room, manuel taşımayı kullanır.

Test taşıma işlemleri

Taşıma işlemleri genellikle karmaşıktır ve yanlış tanımlanmış bir taşıma işlemi, uygulamanızın kilitlenmesine neden olabilir. Uygulamanızın kararlılığını korumak için geçişlerinizi test edin. Room, hem otomatik hem de manuel taşımalar için test sürecine yardımcı olacak bir room-testing Maven yapısı sağlar. Bu yapının çalışması için önce veritabanınızın şemasını dışa aktarmanız gerekir.

Dışa aktarma şemaları

Room, derleme zamanında veritabanınızın şema bilgilerini bir JSON dosyasına aktarabilir. Dışa aktarılan JSON dosyaları, veritabanınızın şema geçmişini temsil eder. Bu dosyaları sürüm kontrol sisteminizde saklayın. Böylece Room, test amacıyla veritabanının daha düşük sürümlerini oluşturabilir ve otomatik taşıma oluşturma özelliğini etkinleştirebilir.

Room Gradle eklentisini kullanarak şema konumunu ayarlama

Room'un 2.6.0 veya daha yeni bir sürümünü kullanıyorsanız Room Gradle eklentisini uygulayabilir ve şema dizinini belirtmek için room uzantısını kullanabilirsiniz.

Groovy

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

room {
  schemaDirectory("$projectDir/schemas")
}

Veritabanı şemanız varyanta, lezzete veya derleme türüne göre farklılık gösteriyorsa schemaDirectory() yapılandırmasını birden çok kez kullanarak farklı konumlar belirtmeniz gerekir. Her yapılandırmada ilk bağımsız değişken olarak variantMatchName kullanılmalıdır. Her yapılandırma, varyant adıyla yapılan basit karşılaştırmaya göre bir veya daha fazla varyantla eşleşebilir.

Bunların kapsamlı olduğundan ve tüm varyantları kapsadığından emin olun. Diğer yapılandırmaların hiçbiriyle eşleşmeyen varyantları işlemek için schemaDirectory() içermeyen bir variantMatchName de ekleyebilirsiniz. Örneğin, iki derleme çeşidi (demo ve full) ve iki derleme türü (debug ve release) içeren bir uygulamada aşağıdaki yapılandırmalar geçerlidir:

Groovy

room {
  // Applies to 'demoDebug' only
  schemaDirectory "demoDebug", "$projectDir/schemas/demoDebug"

  // Applies to 'demoDebug' and 'demoRelease'
  schemaDirectory "demo", "$projectDir/schemas/demo"

  // Applies to 'demoDebug' and 'fullDebug'
  schemaDirectory "debug", "$projectDir/schemas/debug"

  // Applies to variants that aren't matched by other configurations.
  schemaDirectory "$projectDir/schemas"
}

Kotlin

room {
  // Applies to 'demoDebug' only
  schemaDirectory("demoDebug", "$projectDir/schemas/demoDebug")

  // Applies to 'demoDebug' and 'demoRelease'
  schemaDirectory("demo", "$projectDir/schemas/demo")

  // Applies to 'demoDebug' and 'fullDebug'
  schemaDirectory("debug", "$projectDir/schemas/debug")

  // Applies to variants that aren't matched by other configurations.
  schemaDirectory("$projectDir/schemas")
}

Not işlemcisi seçeneğini kullanarak şema konumunu ayarlama

Room'un 2.5.2 veya daha eski bir sürümünü kullanıyorsanız ya da Room Gradle eklentisini kullanmıyorsanız şema konumunu room.schemaLocation annotation processor seçeneğini kullanarak ayarlayın.

Bu dizindeki dosyalar, bazı Gradle görevleri için giriş ve çıkış olarak kullanılır. Artımlı ve önbelleğe alınmış derlemelerin doğruluğu ve performansı için Gradle'ı bu dizin hakkında bilgilendirmek üzere Gradle'ın CommandLineArgumentProvider kullanmanız gerekir.

Öncelikle, aşağıdaki RoomSchemaArgProvider sınıfını modülünüzün Gradle derleme dosyasına kopyalayın. Örnek sınıftaki asArguments() yöntemi, room.schemaLocation=${schemaDir.path} değerini KSP'ye iletir. KAPT ve javac kullanıyorsanız bu değeri -Aroom.schemaLocation=${schemaDir.path} olarak değiştirin.

Groovy

class RoomSchemaArgProvider implements CommandLineArgumentProvider {

  @InputDirectory
  @PathSensitive(PathSensitivity.RELATIVE)
  File schemaDir

  RoomSchemaArgProvider(File schemaDir) {
    this.schemaDir = schemaDir
  }

  @Override
  Iterable<String> asArguments() {
    // Note: If you're using KAPT and javac, change the line below to
    // return ["-Aroom.schemaLocation=${schemaDir.path}".toString()].
    return ["room.schemaLocation=${schemaDir.path}".toString()]
  }
}

Kotlin

class RoomSchemaArgProvider(
  @get:InputDirectory
  @get:PathSensitive(PathSensitivity.RELATIVE)
  val schemaDir: File
) : CommandLineArgumentProvider {

  override fun asArguments(): Iterable<String> {
    // Note: If you're using KAPT and javac, change the line below to
    // return listOf("-Aroom.schemaLocation=${schemaDir.path}").
    return listOf("room.schemaLocation=${schemaDir.path}")
  }
}

Ardından, derleme seçeneklerini RoomSchemaArgProvider ile belirtilen şema dizinini kullanacak şekilde yapılandırın:

Groovy

// For KSP, configure using KSP extension:
ksp {
  arg(new RoomSchemaArgProvider(new File(projectDir, "schemas")))
}

// For javac or KAPT, configure using android DSL:
android {
  ...
  defaultConfig {
    javaCompileOptions {
      annotationProcessorOptions {
        compilerArgumentProviders(
          new RoomSchemaArgProvider(new File(projectDir, "schemas"))
        )
      }
    }
  }
}

Kotlin

// For KSP, configure using KSP extension:
ksp {
  arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
}

// For javac or KAPT, configure using android DSL:
android {
  ...
  defaultConfig {
    javaCompileOptions {
      annotationProcessorOptions {
        compilerArgumentProviders(
          RoomSchemaArgProvider(File(projectDir, "schemas"))
        )
      }
    }
  }
}

Tek bir taşıma işlemini test etme

Taşımalarınızı test edebilmek için Room'dan androidx.room:room-testing Maven yapısını test bağımlılıklarınıza ekleyin ve dışa aktarılan şemanın konumunu bir öğe klasörü olarak ekleyin:

build.gradle

Groovy

android {
    ...
    sourceSets {
        // Adds exported schema location as test app assets.
        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
    }
}

dependencies {
    ...
    androidTestImplementation "androidx.room:room-testing:2.7.2"
}

Kotlin

android {
    ...
    sourceSets {
        // Adds exported schema location as test app assets.
        getByName("androidTest").assets.srcDir("$projectDir/schemas")
    }
}

dependencies {
    ...
    testImplementation("androidx.room:room-testing:2.7.2")
}

Test paketi, dışa aktarılan şema dosyalarını okuyabilen bir MigrationTestHelper sınıfı sağlar. Paket ayrıca, oluşturulan veritabanlarını yönetebilmesi için JUnit4 TestRule arayüzünü de uygular.

Aşağıdaki örnekte tek bir taşıma için test gösterilmektedir:

Kotlin

@RunWith(AndroidJUnit4::class)
class MigrationTest {
    private val TEST_DB = "migration-test"

    @get:Rule
    val helper: MigrationTestHelper = MigrationTestHelper(
            InstrumentationRegistry.getInstrumentation(),
            MigrationDb::class.java.canonicalName,
            FrameworkSQLiteOpenHelperFactory()
    )

    @Test
    @Throws(IOException::class)
    fun migrate1To2() {
        var db = helper.createDatabase(TEST_DB, 1).apply {
            // Database has schema version 1. Insert some data using SQL queries.
            // You can't use DAO classes because they expect the latest schema.
            execSQL(...)

            // Prepare for the next version.
            close()
        }

        // Re-open the database with version 2 and provide
        // MIGRATION_1_2 as the migration process.
        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)

        // MigrationTestHelper automatically verifies the schema changes,
        // but you need to validate that the data was migrated properly.
    }
}

Java

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
    private static final String TEST_DB = "migration-test";

    @Rule
    public MigrationTestHelper helper;

    public MigrationTest() {
        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
                MigrationDb.class.getCanonicalName(),
                new FrameworkSQLiteOpenHelperFactory());
    }

    @Test
    public void migrate1To2() throws IOException {
        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);

        // Database has schema version 1. Insert some data using SQL queries.
        // You can't use DAO classes because they expect the latest schema.
        db.execSQL(...);

        // Prepare for the next version.
        db.close();

        // Re-open the database with version 2 and provide
        // MIGRATION_1_2 as the migration process.
        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);

        // MigrationTestHelper automatically verifies the schema changes,
        // but you need to validate that the data was migrated properly.
    }
}

Tüm taşıma işlemlerini test etme

Tek bir artımlı taşıma işlemini test etmek mümkün olsa da uygulamanızın veritabanı için tanımlanan tüm taşıma işlemlerini kapsayan bir test eklemenizi öneririz. Bu sayede, kısa süre önce oluşturulan bir veritabanı örneği ile tanımlanan taşıma yollarını izleyen daha eski bir örnek arasında tutarsızlık olmaması sağlanır.

Aşağıdaki örnekte, tanımlanan tüm taşımalar için bir test gösterilmektedir:

Kotlin

@RunWith(AndroidJUnit4::class)
class MigrationTest {
    private val TEST_DB = "migration-test"

    // Array of all migrations.
    private val ALL_MIGRATIONS = arrayOf(
            MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)

    @get:Rule
    val helper: MigrationTestHelper = MigrationTestHelper(
            InstrumentationRegistry.getInstrumentation(),
            AppDatabase::class.java.canonicalName,
            FrameworkSQLiteOpenHelperFactory()
    )

    @Test
    @Throws(IOException::class)
    fun migrateAll() {
        // Create earliest version of the database.
        helper.createDatabase(TEST_DB, 1).apply {
            close()
        }

        // Open latest version of the database. Room validates the schema
        // once all migrations execute.
        Room.databaseBuilder(
            InstrumentationRegistry.getInstrumentation().targetContext,
            AppDatabase::class.java,
            TEST_DB
        ).addMigrations(*ALL_MIGRATIONS).build().apply {
            openHelper.writableDatabase.close()
        }
    }
}

Java

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
    private static final String TEST_DB = "migration-test";

    @Rule
    public MigrationTestHelper helper;

    public MigrationTest() {
        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
                AppDatabase.class.getCanonicalName(),
                new FrameworkSQLiteOpenHelperFactory());
    }

    @Test
    public void migrateAll() throws IOException {
        // Create earliest version of the database.
        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
        db.close();

        // Open latest version of the database. Room validates the schema
        // once all migrations execute.
        AppDatabase appDb = Room.databaseBuilder(
                InstrumentationRegistry.getInstrumentation().getTargetContext(),
                AppDatabase.class,
                TEST_DB)
                .addMigrations(ALL_MIGRATIONS).build();
        appDb.getOpenHelper().getWritableDatabase();
        appDb.close();
    }

    // Array of all migrations.
    private static final Migration[] ALL_MIGRATIONS = new Migration[]{
            MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4};
}

Eksik taşıma yollarını sorunsuz bir şekilde işleme

Room, bir cihazdaki mevcut veritabanını güncel sürüme yükseltmek için taşıma yolu bulamazsa IllegalStateException hatası oluşur. Bir taşıma yolu eksik olduğunda mevcut verilerin kaybolması kabul edilebilir bir durumsa veri tabanını oluştururken fallbackToDestructiveMigration() oluşturucu yöntemini çağırın:

Kotlin

Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
        .fallbackToDestructiveMigration()
        .build()

Java

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .fallbackToDestructiveMigration()
        .build();

Bu yöntem, artımlı bir taşıma işlemi yapması gerektiğinde ve tanımlanmış bir taşıma yolu olmadığında Room'a uygulamanızın veritabanındaki tabloları yıkıcı bir şekilde yeniden oluşturmasını söyler.

Room'un yalnızca belirli durumlarda yıkıcı yeniden oluşturmaya geri dönmesini istiyorsanız fallbackToDestructiveMigration() yerine kullanabileceğiniz birkaç alternatif vardır:

Room 2.2.0'a yükseltirken sütun varsayılan değerlerini işleme

Room 2.2.0 ve sonraki sürümlerde, @ColumnInfo(defaultValue = "...") notunu kullanarak bir sütun için varsayılan değer tanımlayabilirsiniz. 2.2.0'dan önceki sürümlerde bir sütun için varsayılan değer tanımlamanın tek yolu, bunu doğrudan yürütülen bir SQL ifadesinde tanımlamaktır. Bu da Room'un bilmediği bir varsayılan değer oluşturur. Bu, bir veritabanı başlangıçta Room'un 2.2.0'dan önceki bir sürümüyle oluşturulduysa uygulamanızı Room 2.2.0'ı kullanacak şekilde yükseltmek için Room API'lerini kullanmadan tanımladığınız mevcut varsayılan değerler için özel bir taşıma yolu sağlamanız gerekebileceği anlamına gelir.

Örneğin, bir veritabanının 1. sürümünün Song öğesini tanımladığını varsayalım:

Kotlin

// Song entity, database version 1, Room 2.1.0.
@Entity
data class Song(
    @PrimaryKey
    val id: Long,
    val title: String
)

Java

// Song entity, database version 1, Room 2.1.0.
@Entity
public class Song {
    @PrimaryKey
    final long id;
    final String title;
}

Aynı veritabanının 2. sürümünde yeni bir NOT NULL sütunu eklendiğini ve 1. sürümden 2. sürüme bir taşıma yolu tanımlandığını da varsayalım:

Kotlin

// Song entity, database version 2, Room 2.1.0.
@Entity
data class Song(
    @PrimaryKey
    val id: Long,
    val title: String,
    val tag: String // Added in version 2.
)

// Migration from 1 to 2, Room 2.1.0.
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(
            "ALTER TABLE Song ADD COLUMN tag TEXT NOT NULL DEFAULT ''")
    }
}

Java

// Song entity, database version 2, Room 2.1.0.
@Entity
public class Song {
    @PrimaryKey
    final long id;
    final String title;
    @NonNull
    final String tag; // Added in version 2.
}


// Migration from 1 to 2, Room 2.1.0.
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL(
            "ALTER TABLE Song ADD COLUMN tag TEXT NOT NULL DEFAULT ''");
    }
};

Bu durum, temel tablodaki güncellemeler ile uygulamanın yeni yüklemeleri arasında bir tutarsızlığa neden olur. tag sütununun varsayılan değeri yalnızca sürüm 1'den sürüm 2'ye geçiş yolunda bildirildiğinden, uygulamayı sürüm 2'den itibaren yükleyen kullanıcıların veritabanı şemasında tag için varsayılan değer bulunmaz.

Room'un 2.2.0'dan eski sürümlerinde bu tutarsızlık zararsızdır. Ancak uygulama daha sonra Room 2.2.0 veya daha yeni bir sürümü kullanacak şekilde yükseltilirse ve Song öğe sınıfını @ColumnInfo ek açıklamasını kullanarak tag için varsayılan bir değer içerecek şekilde değiştirirse Room bu tutarsızlığı görebilir. Bu durum, şema doğrulamasının başarısız olmasına neden olur.

Sütun varsayılan değerleri önceki taşıma yollarınızda bildirildiğinde veritabanı şemasının tüm kullanıcılarda tutarlı olmasını sağlamak için uygulamanızı Room 2.2.0 veya daha yeni bir sürümü kullanacak şekilde ilk kez yükselttiğinizde aşağıdakileri yapın:

  1. @ColumnInfo ek açıklamasını kullanarak sütun varsayılan değerlerini ilgili öğe sınıflarında bildirin.
  2. Veritabanı sürüm numarasını 1 artırın.
  3. Mevcut sütunlara gerekli varsayılan değerleri eklemek için bırakma ve yeniden oluşturma stratejisini uygulayan yeni sürüme yönelik bir taşıma yolu tanımlayın.

Bu işlem aşağıdaki örnekte gösterilmektedir:

Kotlin

// Migration from 2 to 3, Room 2.2.0.
val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("""
                CREATE TABLE new_Song (
                    id INTEGER PRIMARY KEY NOT NULL,
                    name TEXT,
                    tag TEXT NOT NULL DEFAULT ''
                )
                """.trimIndent())
        database.execSQL("""
                INSERT INTO new_Song (id, name, tag)
                SELECT id, name, tag FROM Song
                """.trimIndent())
        database.execSQL("DROP TABLE Song")
        database.execSQL("ALTER TABLE new_Song RENAME TO Song")
    }
}

Java

// Migration from 2 to 3, Room 2.2.0.
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE new_Song (" +
                "id INTEGER PRIMARY KEY NOT NULL," +
                "name TEXT," +
                "tag TEXT NOT NULL DEFAULT '')");
        database.execSQL("INSERT INTO new_Song (id, name, tag) " +
                "SELECT id, name, tag FROM Song");
        database.execSQL("DROP TABLE Song");
        database.execSQL("ALTER TABLE new_Song RENAME TO Song");
    }
};