FairEmail/app/src/main/java/androidx/room/RoomOpenHelper.java

278 lines
9.9 KiB
Java

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.room;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import java.util.List;
/**
* An open helper that holds a reference to the configuration until the database is opened.
*
* @hide
*/
@SuppressWarnings("unused")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
@Nullable
private DatabaseConfiguration mConfiguration;
@NonNull
private final Delegate mDelegate;
@NonNull
private final String mIdentityHash;
/**
* Room v1 had a bug where the hash was not consistent if fields are reordered.
* The new has fixes it but we still need to accept the legacy hash.
*/
@NonNull // b/64290754
private final String mLegacyHash;
public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
@NonNull String identityHash, @NonNull String legacyHash) {
super(delegate.version);
mConfiguration = configuration;
mDelegate = delegate;
mIdentityHash = identityHash;
mLegacyHash = legacyHash;
}
public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
@NonNull String legacyHash) {
this(configuration, delegate, "", legacyHash);
}
@Override
public void onConfigure(SupportSQLiteDatabase db) {
super.onConfigure(db);
}
@Override
public void onCreate(SupportSQLiteDatabase db) {
boolean isEmptyDatabase = hasEmptySchema(db);
mDelegate.createAllTables(db);
if (!isEmptyDatabase) {
// A 0 version pre-populated database goes through the create path because the
// framework's SQLiteOpenHelper thinks the database was just created from scratch. If we
// find the database not to be empty, then it is a pre-populated, we must validate it to
// see if its suitable for usage.
ValidationResult result = mDelegate.onValidateSchema(db);
if (!result.isValid) {
throw new IllegalStateException("Pre-packaged database has an invalid schema: "
+ result.expectedFoundMsg);
}
}
updateIdentity(db);
mDelegate.onCreate(db);
}
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
boolean migrated = false;
if (mConfiguration != null) {
List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
oldVersion, newVersion);
if (migrations != null) {
mDelegate.onPreMigrate(db);
for (Migration migration : migrations) {
migration.migrate(db);
}
ValidationResult result = mDelegate.onValidateSchema(db);
if (!result.isValid) {
throw new IllegalStateException("Migration didn't properly handle: "
+ result.expectedFoundMsg);
}
mDelegate.onPostMigrate(db);
updateIdentity(db);
migrated = true;
}
}
if (!migrated) {
if (mConfiguration != null
&& !mConfiguration.isMigrationRequired(oldVersion, newVersion)) {
mDelegate.dropAllTables(db);
mDelegate.createAllTables(db);
} else {
throw new IllegalStateException("A migration from " + oldVersion + " to "
+ newVersion + " was required but not found. Please provide the "
+ "necessary Migration path via "
+ "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
+ "destructive migrations via one of the "
+ "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
}
}
}
@Override
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onOpen(SupportSQLiteDatabase db) {
super.onOpen(db);
checkIdentity(db);
mDelegate.onOpen(db);
// there might be too many configurations etc, just clear it.
mConfiguration = null;
}
private void checkIdentity(SupportSQLiteDatabase db) {
if (hasRoomMasterTable(db)) {
String identityHash = null;
Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
//noinspection TryFinallyCanBeTryWithResources
try {
if (cursor.moveToFirst()) {
identityHash = cursor.getString(0);
}
} finally {
cursor.close();
}
if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
+ " you've changed schema but forgot to update the version number. You can"
+ " simply fix this by increasing the version number.");
}
} else {
// No room_master_table, this might an a pre-populated DB, we must validate to see if
// its suitable for usage.
ValidationResult result = mDelegate.onValidateSchema(db);
if (!result.isValid) {
throw new IllegalStateException("Pre-packaged database has an invalid schema: "
+ result.expectedFoundMsg);
}
mDelegate.onPostMigrate(db);
updateIdentity(db);
}
}
private void updateIdentity(SupportSQLiteDatabase db) {
createMasterTableIfNotExists(db);
db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
}
private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
db.execSQL(RoomMasterTable.CREATE_QUERY);
}
private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
+ RoomMasterTable.TABLE_NAME + "'");
//noinspection TryFinallyCanBeTryWithResources
try {
return cursor.moveToFirst() && cursor.getInt(0) != 0;
} finally {
cursor.close();
}
}
private static boolean hasEmptySchema(SupportSQLiteDatabase db) {
Cursor cursor = db.query(
"SELECT count(*) FROM sqlite_master WHERE name != 'android_metadata'");
//noinspection TryFinallyCanBeTryWithResources
try {
return cursor.moveToFirst() && cursor.getInt(0) == 0;
} finally {
cursor.close();
}
}
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public abstract static class Delegate {
public final int version;
public Delegate(int version) {
this.version = version;
}
protected abstract void dropAllTables(SupportSQLiteDatabase database);
protected abstract void createAllTables(SupportSQLiteDatabase database);
protected abstract void onOpen(SupportSQLiteDatabase database);
protected abstract void onCreate(SupportSQLiteDatabase database);
/**
* Called after a migration run to validate database integrity.
*
* @param db The SQLite database.
*
* @deprecated Use {@link #onValidateSchema(SupportSQLiteDatabase)}
*/
@Deprecated
protected void validateMigration(SupportSQLiteDatabase db) {
throw new UnsupportedOperationException("validateMigration is deprecated");
}
/**
* Called after a migration run or pre-package database copy to validate database integrity.
*
* @param db The SQLite database.
*/
@SuppressWarnings("deprecation")
@NonNull
protected ValidationResult onValidateSchema(@NonNull SupportSQLiteDatabase db) {
validateMigration(db);
return new ValidationResult(true, null);
}
/**
* Called before migrations execute to perform preliminary work.
* @param database The SQLite database.
*/
protected void onPreMigrate(SupportSQLiteDatabase database) {
}
/**
* Called after migrations execute to perform additional work.
* @param database The SQLite database.
*/
protected void onPostMigrate(SupportSQLiteDatabase database) {
}
}
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public static class ValidationResult {
public final boolean isValid;
@Nullable
public final String expectedFoundMsg;
public ValidationResult(boolean isValid, @Nullable String expectedFoundMsg) {
this.isValid = isValid;
this.expectedFoundMsg = expectedFoundMsg;
}
}
}