כשמוסיפים תכונות לאפליקציה ומבצעים בה שינויים, צריך לשנות את הכיתות של ישויות Room ואת טבלאות מסדי הנתונים הבסיסיים כדי לשקף את השינויים האלה. חשוב לשמור את נתוני המשתמשים שכבר נמצאים במסד הנתונים במכשיר כשמתבצע עדכון לאפליקציה שמשנה את הסכימה של מסד הנתונים.
Room תומך באפשרויות אוטומטיות וידניות להעברה מצטברת. העברות אוטומטיות פועלות ברוב השינויים הבסיסיים בסכימה, אבל יכול להיות שתצטרכו להגדיר באופן ידני את נתיבי ההעברה לשינויים מורכבים יותר.
העברות אוטומטיות
כדי להצהיר על העברה אוטומטית בין שתי גרסאות של מסד נתונים, מוסיפים הערה @AutoMigration
למאפיין autoMigrations
ב-@Database
:
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 { ... }
מפרטי העברה אוטומטית
אם Room מזהה שינויים לא ברורים בסכימה ולא יכול ליצור תוכנית העברה בלי קלט נוסף, הוא יוצר שגיאה בזמן הידור ומבקש להטמיע AutoMigrationSpec
.
בדרך כלל, המצב הזה מתרחש כשהעברה כוללת את אחד מהמצבים הבאים:
- מחיקה או שינוי שם של טבלה.
- מחיקה או שינוי שם של עמודה.
אפשר להשתמש ב-AutoMigrationSpec
כדי לספק ל-Room את המידע הנוסף שנחוץ לו כדי ליצור נתיבי העברה בצורה נכונה. מגדירים כיתה סטטית שמטמיעה את AutoMigrationSpec
בכיתה RoomDatabase
ומוסיפים לה הערה אחת או יותר מהאפשרויות הבאות:
כדי להשתמש בהטמעה של AutoMigrationSpec
להעברה אוטומטית, מגדירים את המאפיין spec
בהערה המתאימה של @AutoMigration
:
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 { } ... }
אם האפליקציה שלכם צריכה לבצע פעולות נוספות אחרי שהמיגרציה האוטומטית תושלם, תוכלו להטמיע את onPostMigrate()
.
אם מטמיעים את השיטה הזו ב-AutoMigrationSpec
, Room קוראת לה אחרי שהמיגרציה האוטומטית מסתיימת.
העברות ידניות
במקרים שבהם ההעברה כוללת שינויים מורכבים בסכימה, יכול להיות ש-Room לא יוכל ליצור נתיב העברה מתאים באופן אוטומטי. לדוגמה, אם תחליטו לפצל את הנתונים בטבלה לשתי טבלאות, ל-Room אין אפשרות לקבוע איך לבצע את הפיצול הזה. במקרים כאלה, צריך להגדיר ידנית את נתיב ההעברה באמצעות הטמעת הכיתה Migration
.
בכיתה Migration
מגדירים במפורש את נתיב ההעברה בין startVersion
ל-endVersion
על ידי שינוי מברירת המחדל של השיטה Migration.migrate()
. מוסיפים את הכיתות של Migration
לכלי ליצירת מסדי נתונים באמצעות השיטה addMigrations()
:
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();
כשמגדירים את נתיבי ההעברה, אפשר להשתמש בהעברות אוטומטיות לגרסאות מסוימות ובהעברות ידניות לגרסאות אחרות. אם תגדירו העברה אוטומטית והעברה ידנית לאותה גרסה, Room ישתמש בהעברה הידנית.
בדיקת העברות
העברות הן לעיתים קרובות מורכבות, והגדרה שגויה של העברה עלולה לגרום לקריסה של האפליקציה. כדי לשמור על יציבות האפליקציה, צריך לבדוק את ההעברות. ב-Room יש פריט room-testing
Maven שעוזר בתהליך הבדיקה של העברות אוטומטיות וידניות. כדי שהארטיפקט הזה יפעל, קודם צריך לייצא את הסכימה של מסד הנתונים.
ייצוא סכימות
Room יכול לייצא את פרטי הסכימה של מסד הנתונים לקובץ JSON בזמן הידור. קובצי ה-JSON המיוצאים מייצגים את היסטוריית הסכימות של מסד הנתונים. כדאי לאחסן את הקבצים האלה במערכת בקרת הגרסאות שלכם, כדי ש-Room יוכל ליצור גרסאות ישנות יותר של מסד הנתונים למטרות בדיקה ולאפשר יצירת העברה אוטומטית.
הגדרת מיקום הסכימה באמצעות הפלאגין של Room Gradle
אם אתם משתמשים ב-Room בגרסה 2.6.0 ואילך, תוכלו להשתמש בPlugin של Gradle ל-Room ובתוסף room
כדי לציין את ספריית הסכימה.
Groovy
plugins {
id 'androidx.room'
}
room {
schemaDirectory "$projectDir/schemas"
}
Kotlin
plugins {
id("androidx.room")
}
room {
schemaDirectory("$projectDir/schemas")
}
אם הסכימה של מסד הנתונים משתנה בהתאם לוריאנט, לסוג הטעם או לסוג ה-build, צריך לציין מיקומים שונים באמצעות ההגדרה schemaDirectory()
כמה פעמים, כאשר בכל פעם variantMatchName
הוא הארגומנט הראשון. כל הגדרה יכולה להתאים לווריאנט אחד או יותר על סמך השוואה פשוטה לשם הווריאנט.
חשוב לוודא שהן מקיפות ומכסות את כל הווריאציות. אפשר גם לכלול schemaDirectory()
ללא variantMatchName
כדי לטפל בוריאנטים שלא תואמים לאף אחת מההגדרות האחרות. לדוגמה, באפליקציה עם שני סוגים של גרסאות build, demo
ו-full
, ושני סוגים של גרסאות build, debug
ו-release
, ההגדרות הבאות תקינות:
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")
}
הגדרת מיקום הסכימה באמצעות האפשרות של מעבד האנוטציות
אם אתם משתמשים בגרסת Room 2.5.2 ואילך, או אם אתם לא משתמשים בפלאגין של Room ל-Gradle, מגדירים את מיקום הסכימה באמצעות האפשרות room.schemaLocation
של מעבד התווית.
הקבצים בספרייה הזו משמשים כקלט ופלט למשימות מסוימות ב-Gradle.
כדי לשמור על הדיוק והביצועים של גרסאות build מצטברות וגרסאות build שנשמרו במטמון, צריך להשתמש ב-CommandLineArgumentProvider
של Gradle כדי להודיע ל-Gradle על הספרייה הזו.
קודם מעתיקים את הכיתה RoomSchemaArgProvider
שמופיעה בהמשך לקובץ ה-build של המודול ב-Gradle. השיטה asArguments()
במחלקה לדוגמה מעבירה את room.schemaLocation=${schemaDir.path}
אל KSP
. אם אתם משתמשים ב-KAPT
וב-javac
, צריך לשנות את הערך הזה ל--Aroom.schemaLocation=${schemaDir.path}
במקום זאת.
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}")
}
}
לאחר מכן מגדירים את אפשרויות הידור הקוד כך שישתמשו ב-RoomSchemaArgProvider
עם ספריית הסכימה שצוינה:
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"))
)
}
}
}
}
בדיקה של העברה אחת
כדי שתוכלו לבדוק את ההעברות, צריך להוסיף את הארטיפקט androidx.room:room-testing
של Maven מ-Room ליחסי התלות של הבדיקות ולהוסיף את המיקום של הסכימה המיוצאת כספריית נכסים:
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.6.1" }
Kotlin
android { ... sourceSets { // Adds exported schema location as test app assets. getByName("androidTest").assets.srcDir("$projectDir/schemas") } } dependencies { ... testImplementation("androidx.room:room-testing:2.6.1") }
חבילת הבדיקה מספקת את הכיתה MigrationTestHelper
, שיכולה לקרוא קובצי סכימה שיוצאו. החבילה גם מיישמת את הממשק TestRule
של JUnit4, כדי שתוכל לנהל מסדי נתונים שנוצרו.
הדוגמה הבאה ממחישה בדיקה של העברה יחידה:
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. } }
בדיקת כל המיגרציות
אפשר לבדוק העברה מצטברת אחת, אבל מומלץ לכלול בדיקה שכוללת את כל ההעברות שמוגדרות למסד הנתונים של האפליקציה. כך אפשר לוודא שאין אי-התאמה בין מכונה של מסד נתונים שנוצרה לאחרונה לבין מכונה ישנה יותר שעברה את נתיבי ההעברה שהוגדרו.
הדוגמה הבאה ממחישה בדיקה של כל ההעברות שהוגדרו:
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}; }
טיפול מושלם בנתיבי העברה חסרים
אם מערכת Room לא מצליחה למצוא נתיב העברה לשדרוג של מסד נתונים קיים במכשיר לגרסה הנוכחית, מתרחשת הודעת השגיאה IllegalStateException
. אם אתם לא מתנגדים לאבד נתונים קיימים אם נתיב ההעברה חסר, תוכלו להפעיל את שיטת ה-builder fallbackToDestructiveMigration()
כשיוצרים את מסד הנתונים:
Kotlin
Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name") .fallbackToDestructiveMigration() .build()
Java
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name") .fallbackToDestructiveMigration() .build();
השיטה הזו מורה ל-Room ליצור מחדש את הטבלאות במסד הנתונים של האפליקציה באופן הרסני, כשצריך לבצע העברה מצטברת ואין נתיב העברה מוגדר.
אם אתם רוצים ש-Room יעבור ליצירה מחדש של נתונים באופן הרסני רק במצבים מסוימים, יש כמה חלופות ל-fallbackToDestructiveMigration()
:
- אם גרסאות ספציפיות של היסטוריית הסכימה גורמות לשגיאות שלא ניתן לפתור באמצעות נתיבי העברה, תוכלו להשתמש במקום זאת ב-
fallbackToDestructiveMigrationFrom()
. השיטה הזו מציינת שרוצים ש-Room יחזור ליצירה הרסנית רק במעבר מגרסאות ספציפיות. - אם רוצים ש-Room יעבור ליצירה מחדש הרסנית רק כשעוברים מגרסת מסד נתונים חדשה יותר לגרסה ישנה יותר, צריך להשתמש במקום זאת ב-
fallbackToDestructiveMigrationOnDowngrade()
.
טיפול בערכי ברירת המחדל של עמודות כשמשדרגים ל-Room 2.2.0
בגרסה 2.2.0 ואילך של Room, אפשר להגדיר ערך ברירת מחדל לעמודה באמצעות ההערה @ColumnInfo(defaultValue = "...")
.
בגרסאות ישנות יותר מ-2.2.0, הדרך היחידה להגדיר ערך ברירת מחדל לעמודה היא להגדיר אותו ישירות במשפט SQL שמתבצע, וכך ייווצר ערך ברירת מחדל ש-Room לא יודע עליו. המשמעות היא שאם מסד נתונים נוצר במקור בגרסה של Room שקטנה מ-2.2.0, יכול להיות שדרוג האפליקציה לשימוש ב-Room 2.2.0 ידרוש מכם לספק נתיב העברה מיוחד לערכים קיימים שמוגדרים כברירת מחדל בלי להשתמש בממשקי ה-API של Room.
לדוגמה, נניח שגרסה 1 של מסד נתונים מגדירה ישות Song
:
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; }
נניח גם שגרסה 2 של אותו מסד נתונים מוסיפה עמודה חדשה NOT NULL
ומגדירה נתיב העברה מגרסה 1 לגרסה 2:
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 ''"); } };
כתוצאה מכך, יש אי-התאמה בטבלה הבסיסית בין עדכונים להתקנות חדשות של האפליקציה. מאחר שערך ברירת המחדל של העמודה tag
מוגדר רק בנתיב ההעברה מגרסה 1 לגרסה 2, למשתמשים שמתקינים את האפליקציה החל מגרסה 2 אין את ערך ברירת המחדל של tag
בסכימת מסד הנתונים שלהם.
בגרסאות של Room שקודמות ל-2.2.0, אי ההתאמה הזו לא מזיקה. עם זאת, אם האפליקציה תשודרג מאוחר יותר לשימוש ב-Room 2.2.0 ואילך, ותשנה את סיווג הישות Song
כך שיכלול ערך ברירת מחדל ל-tag
באמצעות ההערה @ColumnInfo
, מערכת Room תוכל לראות את אי ההתאמה הזו. כתוצאה מכך, תהליכי אימות הסכימה נכשלים.
כדי להבטיח שסכימה של מסד הנתונים תהיה עקבית בכל המשתמשים כשמגדירים ערכי ברירת מחדל של עמודות בנתיבים הקודמים להעברה, צריך לבצע את הפעולות הבאות בפעם הראשונה שמשדרגים את האפליקציה לשימוש ב-Room 2.2.0 ואילך:
- מגדירים את ערכי ברירת המחדל של העמודות בקטגוריות הישות הרלוונטיות באמצעות ההערה
@ColumnInfo
. - מוסיפים 1 למספר הגרסה של מסד הנתונים.
- מגדירים נתיב העברה לגרסה החדשה שמטמיע את האסטרטגיה של השמטה ויצירה מחדש כדי להוסיף את ערכי ברירת המחדל הנדרשים לעמודות הקיימות.
הדוגמה הבאה מדגימה את התהליך:
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"); } };