Home | History | Annotate | Download | only in room
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.room;
     18 
     19 import android.database.Cursor;
     20 
     21 import androidx.annotation.NonNull;
     22 import androidx.annotation.Nullable;
     23 import androidx.annotation.RestrictTo;
     24 import androidx.room.migration.Migration;
     25 import androidx.sqlite.db.SimpleSQLiteQuery;
     26 import androidx.sqlite.db.SupportSQLiteDatabase;
     27 import androidx.sqlite.db.SupportSQLiteOpenHelper;
     28 
     29 import java.util.List;
     30 
     31 /**
     32  * An open helper that holds a reference to the configuration until the database is opened.
     33  *
     34  * @hide
     35  */
     36 @SuppressWarnings("unused")
     37 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     38 public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
     39     @Nullable
     40     private DatabaseConfiguration mConfiguration;
     41     @NonNull
     42     private final Delegate mDelegate;
     43     @NonNull
     44     private final String mIdentityHash;
     45     /**
     46      * Room v1 had a bug where the hash was not consistent if fields are reordered.
     47      * The new has fixes it but we still need to accept the legacy hash.
     48      */
     49     @NonNull // b/64290754
     50     private final String mLegacyHash;
     51 
     52     public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
     53             @NonNull String identityHash, @NonNull String legacyHash) {
     54         super(delegate.version);
     55         mConfiguration = configuration;
     56         mDelegate = delegate;
     57         mIdentityHash = identityHash;
     58         mLegacyHash = legacyHash;
     59     }
     60 
     61     public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
     62             @NonNull String legacyHash) {
     63         this(configuration, delegate, null, legacyHash);
     64     }
     65 
     66     @Override
     67     public void onConfigure(SupportSQLiteDatabase db) {
     68         super.onConfigure(db);
     69     }
     70 
     71     @Override
     72     public void onCreate(SupportSQLiteDatabase db) {
     73         updateIdentity(db);
     74         mDelegate.createAllTables(db);
     75         mDelegate.onCreate(db);
     76     }
     77 
     78     @Override
     79     public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
     80         boolean migrated = false;
     81         if (mConfiguration != null) {
     82             List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
     83                     oldVersion, newVersion);
     84             if (migrations != null) {
     85                 for (Migration migration : migrations) {
     86                     migration.migrate(db);
     87                 }
     88                 mDelegate.validateMigration(db);
     89                 updateIdentity(db);
     90                 migrated = true;
     91             }
     92         }
     93         if (!migrated) {
     94             if (mConfiguration != null && !mConfiguration.isMigrationRequiredFrom(oldVersion)) {
     95                 mDelegate.dropAllTables(db);
     96                 mDelegate.createAllTables(db);
     97             } else {
     98                 throw new IllegalStateException("A migration from " + oldVersion + " to "
     99                         + newVersion + " was required but not found. Please provide the "
    100                         + "necessary Migration path via "
    101                         + "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
    102                         + "destructive migrations via one of the "
    103                         + "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
    104             }
    105         }
    106     }
    107 
    108     @Override
    109     public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
    110         onUpgrade(db, oldVersion, newVersion);
    111     }
    112 
    113     @Override
    114     public void onOpen(SupportSQLiteDatabase db) {
    115         super.onOpen(db);
    116         checkIdentity(db);
    117         mDelegate.onOpen(db);
    118         // there might be too many configurations etc, just clear it.
    119         mConfiguration = null;
    120     }
    121 
    122     private void checkIdentity(SupportSQLiteDatabase db) {
    123         String identityHash = null;
    124         if (hasRoomMasterTable(db)) {
    125             Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
    126             //noinspection TryFinallyCanBeTryWithResources
    127             try {
    128                 if (cursor.moveToFirst()) {
    129                     identityHash = cursor.getString(0);
    130                 }
    131             } finally {
    132                 cursor.close();
    133             }
    134         }
    135         if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
    136             throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
    137                     + " you've changed schema but forgot to update the version number. You can"
    138                     + " simply fix this by increasing the version number.");
    139         }
    140     }
    141 
    142     private void updateIdentity(SupportSQLiteDatabase db) {
    143         createMasterTableIfNotExists(db);
    144         db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
    145     }
    146 
    147     private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
    148         db.execSQL(RoomMasterTable.CREATE_QUERY);
    149     }
    150 
    151     private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
    152         Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
    153                 + RoomMasterTable.TABLE_NAME + "'");
    154         //noinspection TryFinallyCanBeTryWithResources
    155         try {
    156             return cursor.moveToFirst() && cursor.getInt(0) != 0;
    157         } finally {
    158             cursor.close();
    159         }
    160     }
    161 
    162     /**
    163      * @hide
    164      */
    165     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    166     public abstract static class Delegate {
    167         public final int version;
    168 
    169         public Delegate(int version) {
    170             this.version = version;
    171         }
    172 
    173         protected abstract void dropAllTables(SupportSQLiteDatabase database);
    174 
    175         protected abstract void createAllTables(SupportSQLiteDatabase database);
    176 
    177         protected abstract void onOpen(SupportSQLiteDatabase database);
    178 
    179         protected abstract void onCreate(SupportSQLiteDatabase database);
    180 
    181         /**
    182          * Called after a migration run to validate database integrity.
    183          *
    184          * @param db The SQLite database.
    185          */
    186         protected abstract void validateMigration(SupportSQLiteDatabase db);
    187     }
    188 
    189 }
    190