1a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar/*
2a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * Copyright (C) 2017 The Android Open Source Project
3a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar *
4a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * you may not use this file except in compliance with the License.
6a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * You may obtain a copy of the License at
7a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar *
8a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar *
10a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * Unless required by applicable law or agreed to in writing, software
11a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * See the License for the specific language governing permissions and
14a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * limitations under the License.
15a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar */
16a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
17ba069d50913c3fb250bb60ec310439db36895337Alan Viverettepackage androidx.room;
18a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
19a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyarimport android.database.Cursor;
20ba069d50913c3fb250bb60ec310439db36895337Alan Viverette
21ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.NonNull;
22ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.Nullable;
23ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.annotation.RestrictTo;
24ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.room.migration.Migration;
25ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.sqlite.db.SimpleSQLiteQuery;
26ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.sqlite.db.SupportSQLiteDatabase;
27ba069d50913c3fb250bb60ec310439db36895337Alan Viveretteimport androidx.sqlite.db.SupportSQLiteOpenHelper;
28a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
29a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyarimport java.util.List;
30a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
31a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar/**
32a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * An open helper that holds a reference to the configuration until the database is opened.
33a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar *
34a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar * @hide
35a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar */
36a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar@SuppressWarnings("unused")
37a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
38a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyarpublic class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
39a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @Nullable
40a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    private DatabaseConfiguration mConfiguration;
41a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @NonNull
42a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    private final Delegate mDelegate;
43a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @NonNull
44a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    private final String mIdentityHash;
45e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar    /**
46e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar     * Room v1 had a bug where the hash was not consistent if fields are reordered.
47e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar     * The new has fixes it but we still need to accept the legacy hash.
48e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar     */
49e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar    @NonNull // b/64290754
50e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar    private final String mLegacyHash;
51a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
52a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
53e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar            @NonNull String identityHash, @NonNull String legacyHash) {
542b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar        super(delegate.version);
55a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        mConfiguration = configuration;
56a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        mDelegate = delegate;
57a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        mIdentityHash = identityHash;
58e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar        mLegacyHash = legacyHash;
59a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
60a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
610bf1c211603a37b9cbc2431999d58a8ea085ff5aSergey Vasilinets    public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
620bf1c211603a37b9cbc2431999d58a8ea085ff5aSergey Vasilinets            @NonNull String legacyHash) {
630bf1c211603a37b9cbc2431999d58a8ea085ff5aSergey Vasilinets        this(configuration, delegate, null, legacyHash);
640bf1c211603a37b9cbc2431999d58a8ea085ff5aSergey Vasilinets    }
650bf1c211603a37b9cbc2431999d58a8ea085ff5aSergey Vasilinets
66a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @Override
67a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public void onConfigure(SupportSQLiteDatabase db) {
68a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        super.onConfigure(db);
69a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
70a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
71a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @Override
72a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public void onCreate(SupportSQLiteDatabase db) {
73a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        updateIdentity(db);
74a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        mDelegate.createAllTables(db);
75cf4d34906517d8ced296a96e50339c926a7dfdcdYuichi Araki        mDelegate.onCreate(db);
76a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
77a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
78a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @Override
79a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
80a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        boolean migrated = false;
81a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        if (mConfiguration != null) {
82a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar            List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
83a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    oldVersion, newVersion);
84a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar            if (migrations != null) {
85a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                for (Migration migration : migrations) {
86a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    migration.migrate(db);
87a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                }
883a433f7ddbffa6131883cc3b23fc80edf54add19Yigit Boyar                mDelegate.validateMigration(db);
893a433f7ddbffa6131883cc3b23fc80edf54add19Yigit Boyar                updateIdentity(db);
90a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                migrated = true;
91a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar            }
92a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        }
93a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        if (!migrated) {
94d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard            if (mConfiguration != null && !mConfiguration.isMigrationRequiredFrom(oldVersion)) {
95d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                mDelegate.dropAllTables(db);
96d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                mDelegate.createAllTables(db);
97d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard            } else {
98234f073e2227fcb62a9ed8285c79724de1f0fa92Yigit Boyar                throw new IllegalStateException("A migration from " + oldVersion + " to "
99d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                        + newVersion + " was required but not found. Please provide the "
100d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                        + "necessary Migration path via "
101d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                        + "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
102d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                        + "destructive migrations via one of the "
103d2658c69a5eb56c5fa7f09bd361974aa2452b320shepshapard                        + "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
104234f073e2227fcb62a9ed8285c79724de1f0fa92Yigit Boyar            }
105a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        }
106a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
107a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
108a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @Override
109a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
110a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        onUpgrade(db, oldVersion, newVersion);
111a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
112a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
113a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @Override
114a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public void onOpen(SupportSQLiteDatabase db) {
115a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        super.onOpen(db);
1167ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        checkIdentity(db);
117a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        mDelegate.onOpen(db);
118a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        // there might be too many configurations etc, just clear it.
119a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        mConfiguration = null;
120a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
121a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
1227ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki    private void checkIdentity(SupportSQLiteDatabase db) {
1230bf1c211603a37b9cbc2431999d58a8ea085ff5aSergey Vasilinets        String identityHash = null;
1247ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        if (hasRoomMasterTable(db)) {
1257ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki            Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
1267ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki            //noinspection TryFinallyCanBeTryWithResources
1272eb8c31eb44ee4bc4138136b8866847b6e49e5b2Yuichi Araki            try {
1287ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki                if (cursor.moveToFirst()) {
1297ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki                    identityHash = cursor.getString(0);
1307ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki                }
1312eb8c31eb44ee4bc4138136b8866847b6e49e5b2Yuichi Araki            } finally {
1327ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki                cursor.close();
133a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar            }
134a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        }
135e5ed537fe6f14f0bbb43ddef605ed22f09714142Yigit Boyar        if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
136a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar            throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
137a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    + " you've changed schema but forgot to update the version number. You can"
138a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    + " simply fix this by increasing the version number.");
139a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        }
140a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
141a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
142a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    private void updateIdentity(SupportSQLiteDatabase db) {
143a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        createMasterTableIfNotExists(db);
144a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
145a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
146a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
147a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
148a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        db.execSQL(RoomMasterTable.CREATE_QUERY);
149a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
150a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
1517ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki    private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
1527ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
1537ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki                + RoomMasterTable.TABLE_NAME + "'");
1547ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        //noinspection TryFinallyCanBeTryWithResources
1557ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        try {
1567ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki            return cursor.moveToFirst() && cursor.getInt(0) != 0;
1577ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        } finally {
1587ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki            cursor.close();
1597ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki        }
1607ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki    }
1617ce96deb4511a8106d475e7cf8dfa13d2b05a3b6Yuichi Araki
162a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    /**
163a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar     * @hide
164a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar     */
165a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
166a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    public abstract static class Delegate {
1672b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar        public final int version;
1682b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar
1692b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar        public Delegate(int version) {
1702b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar            this.version = version;
1712b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar        }
1722b4a201ee53be9d5e3995e62c76f83c07a1ddfddYigit Boyar
173a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        protected abstract void dropAllTables(SupportSQLiteDatabase database);
174a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
175a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        protected abstract void createAllTables(SupportSQLiteDatabase database);
176a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
177a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        protected abstract void onOpen(SupportSQLiteDatabase database);
178a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
179cf4d34906517d8ced296a96e50339c926a7dfdcdYuichi Araki        protected abstract void onCreate(SupportSQLiteDatabase database);
180cf4d34906517d8ced296a96e50339c926a7dfdcdYuichi Araki
181a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        /**
182a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar         * Called after a migration run to validate database integrity.
183a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar         *
184a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar         * @param db The SQLite database.
185a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar         */
1863a433f7ddbffa6131883cc3b23fc80edf54add19Yigit Boyar        protected abstract void validateMigration(SupportSQLiteDatabase db);
187a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
188a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
189a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar}
190