1/*
2 * Copyright (C) 2016 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
17package androidx.room;
18
19import android.annotation.SuppressLint;
20import android.app.ActivityManager;
21import android.content.Context;
22import android.database.Cursor;
23import android.os.Build;
24import android.util.Log;
25
26import androidx.annotation.CallSuper;
27import androidx.annotation.NonNull;
28import androidx.annotation.Nullable;
29import androidx.annotation.RequiresApi;
30import androidx.annotation.RestrictTo;
31import androidx.annotation.WorkerThread;
32import androidx.collection.SparseArrayCompat;
33import androidx.core.app.ActivityManagerCompat;
34import androidx.arch.core.executor.ArchTaskExecutor;
35import androidx.room.migration.Migration;
36import androidx.sqlite.db.SimpleSQLiteQuery;
37import androidx.sqlite.db.SupportSQLiteDatabase;
38import androidx.sqlite.db.SupportSQLiteOpenHelper;
39import androidx.sqlite.db.SupportSQLiteQuery;
40import androidx.sqlite.db.SupportSQLiteStatement;
41import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
42
43import java.util.ArrayList;
44import java.util.Collections;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Set;
48import java.util.concurrent.Callable;
49import java.util.concurrent.locks.Lock;
50import java.util.concurrent.locks.ReentrantLock;
51
52/**
53 * Base class for all Room databases. All classes that are annotated with {@link Database} must
54 * extend this class.
55 * <p>
56 * RoomDatabase provides direct access to the underlying database implementation but you should
57 * prefer using {@link Dao} classes.
58 *
59 * @see Database
60 */
61//@SuppressWarnings({"unused", "WeakerAccess"})
62public abstract class RoomDatabase {
63    private static final String DB_IMPL_SUFFIX = "_Impl";
64    /**
65     * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
66     *
67     * @hide
68     */
69    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
70    public static final int MAX_BIND_PARAMETER_CNT = 999;
71    // set by the generated open helper.
72    protected volatile SupportSQLiteDatabase mDatabase;
73    private SupportSQLiteOpenHelper mOpenHelper;
74    private final InvalidationTracker mInvalidationTracker;
75    private boolean mAllowMainThreadQueries;
76    boolean mWriteAheadLoggingEnabled;
77
78    @Nullable
79    protected List<Callback> mCallbacks;
80
81    private final ReentrantLock mCloseLock = new ReentrantLock();
82
83    /**
84     * {@link InvalidationTracker} uses this lock to prevent the database from closing while it is
85     * querying database updates.
86     *
87     * @return The lock for {@link #close()}.
88     */
89    Lock getCloseLock() {
90        return mCloseLock;
91    }
92
93    /**
94     * Creates a RoomDatabase.
95     * <p>
96     * You cannot create an instance of a database, instead, you should acquire it via
97     * {@link Room#databaseBuilder(Context, Class, String)} or
98     * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
99     */
100    public RoomDatabase() {
101        mInvalidationTracker = createInvalidationTracker();
102    }
103
104    /**
105     * Called by {@link Room} when it is initialized.
106     *
107     * @param configuration The database configuration.
108     */
109    @CallSuper
110    public void init(@NonNull DatabaseConfiguration configuration) {
111        mOpenHelper = createOpenHelper(configuration);
112        boolean wal = false;
113        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
114            wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
115            mOpenHelper.setWriteAheadLoggingEnabled(wal);
116        }
117        mCallbacks = configuration.callbacks;
118        mAllowMainThreadQueries = configuration.allowMainThreadQueries;
119        mWriteAheadLoggingEnabled = wal;
120    }
121
122    /**
123     * Returns the SQLite open helper used by this database.
124     *
125     * @return The SQLite open helper used by this database.
126     */
127    @NonNull
128    public SupportSQLiteOpenHelper getOpenHelper() {
129        return mOpenHelper;
130    }
131
132    /**
133     * Creates the open helper to access the database. Generated class already implements this
134     * method.
135     * Note that this method is called when the RoomDatabase is initialized.
136     *
137     * @param config The configuration of the Room database.
138     * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
139     */
140    @NonNull
141    protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
142
143    /**
144     * Called when the RoomDatabase is created.
145     * <p>
146     * This is already implemented by the generated code.
147     *
148     * @return Creates a new InvalidationTracker.
149     */
150    @NonNull
151    protected abstract InvalidationTracker createInvalidationTracker();
152
153    /**
154     * Deletes all rows from all the tables that are registered to this database as
155     * {@link Database#entities()}.
156     * <p>
157     * This does NOT reset the auto-increment value generated by {@link PrimaryKey#autoGenerate()}.
158     * <p>
159     * After deleting the rows, Room will set a WAL checkpoint and run VACUUM. This means that the
160     * data is completely erased. The space will be reclaimed by the system if the amount surpasses
161     * the threshold of database file size.
162     *
163     * @see <a href="https://www.sqlite.org/fileformat.html">Database File Format</a>
164     */
165    @WorkerThread
166    public abstract void clearAllTables();
167
168    /**
169     * Returns true if database connection is open and initialized.
170     *
171     * @return true if the database connection is open, false otherwise.
172     */
173    public boolean isOpen() {
174        final SupportSQLiteDatabase db = mDatabase;
175        return db != null && db.isOpen();
176    }
177
178    /**
179     * Closes the database if it is already open.
180     */
181    public void close() {
182        if (isOpen()) {
183            try {
184                mCloseLock.lock();
185                mOpenHelper.close();
186            } finally {
187                mCloseLock.unlock();
188            }
189        }
190    }
191
192    /**
193     * Asserts that we are not on the main thread.
194     *
195     * @hide
196     */
197    @SuppressWarnings("WeakerAccess")
198    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
199    // used in generated code
200    public void assertNotMainThread() {
201        if (mAllowMainThreadQueries) {
202            return;
203        }
204        if (ArchTaskExecutor.getInstance().isMainThread()) {
205            throw new IllegalStateException("Cannot access database on the main thread since"
206                    + " it may potentially lock the UI for a long period of time.");
207        }
208    }
209
210    // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
211    // methods we are using and also helps unit tests to mock this class without mocking
212    // all SQLite database methods.
213
214    /**
215     * Convenience method to query the database with arguments.
216     *
217     * @param query The sql query
218     * @param args The bind arguments for the placeholders in the query
219     *
220     * @return A Cursor obtained by running the given query in the Room database.
221     */
222    public Cursor query(String query, @Nullable Object[] args) {
223        return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
224    }
225
226    /**
227     * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
228     *
229     * @param query The Query which includes the SQL and a bind callback for bind arguments.
230     * @return Result of the query.
231     */
232    public Cursor query(SupportSQLiteQuery query) {
233        assertNotMainThread();
234        return mOpenHelper.getWritableDatabase().query(query);
235    }
236
237    /**
238     * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
239     *
240     * @param sql The query to compile.
241     * @return The compiled query.
242     */
243    public SupportSQLiteStatement compileStatement(@NonNull String sql) {
244        assertNotMainThread();
245        return mOpenHelper.getWritableDatabase().compileStatement(sql);
246    }
247
248    /**
249     * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}.
250     */
251    public void beginTransaction() {
252        assertNotMainThread();
253        SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
254        mInvalidationTracker.syncTriggers(database);
255        database.beginTransaction();
256    }
257
258    /**
259     * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}.
260     */
261    public void endTransaction() {
262        mOpenHelper.getWritableDatabase().endTransaction();
263        if (!inTransaction()) {
264            // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
265            // endTransaction call to do it.
266            mInvalidationTracker.refreshVersionsAsync();
267        }
268    }
269
270    /**
271     * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
272     */
273    public void setTransactionSuccessful() {
274        mOpenHelper.getWritableDatabase().setTransactionSuccessful();
275    }
276
277    /**
278     * Executes the specified {@link Runnable} in a database transaction. The transaction will be
279     * marked as successful unless an exception is thrown in the {@link Runnable}.
280     *
281     * @param body The piece of code to execute.
282     */
283    public void runInTransaction(@NonNull Runnable body) {
284        beginTransaction();
285        try {
286            body.run();
287            setTransactionSuccessful();
288        } finally {
289            endTransaction();
290        }
291    }
292
293    /**
294     * Executes the specified {@link Callable} in a database transaction. The transaction will be
295     * marked as successful unless an exception is thrown in the {@link Callable}.
296     *
297     * @param body The piece of code to execute.
298     * @param <V>  The type of the return value.
299     * @return The value returned from the {@link Callable}.
300     */
301    public <V> V runInTransaction(@NonNull Callable<V> body) {
302        beginTransaction();
303        try {
304            V result = body.call();
305            setTransactionSuccessful();
306            return result;
307        } catch (RuntimeException e) {
308            throw e;
309        } catch (Exception e) {
310            throw new RuntimeException("Exception in transaction", e);
311        } finally {
312            endTransaction();
313        }
314    }
315
316    /**
317     * Called by the generated code when database is open.
318     * <p>
319     * You should never call this method manually.
320     *
321     * @param db The database instance.
322     */
323    protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) {
324        mInvalidationTracker.internalInit(db);
325    }
326
327    /**
328     * Returns the invalidation tracker for this database.
329     * <p>
330     * You can use the invalidation tracker to get notified when certain tables in the database
331     * are modified.
332     *
333     * @return The invalidation tracker for the database.
334     */
335    @NonNull
336    public InvalidationTracker getInvalidationTracker() {
337        return mInvalidationTracker;
338    }
339
340    /**
341     * Returns true if current thread is in a transaction.
342     *
343     * @return True if there is an active transaction in current thread, false otherwise.
344     * @see SupportSQLiteDatabase#inTransaction()
345     */
346    @SuppressWarnings("WeakerAccess")
347    public boolean inTransaction() {
348        return mOpenHelper.getWritableDatabase().inTransaction();
349    }
350
351    /**
352     * Journal modes for SQLite database.
353     *
354     * @see RoomDatabase.Builder#setJournalMode(JournalMode)
355     */
356    public enum JournalMode {
357
358        /**
359         * Let Room choose the journal mode. This is the default value when no explicit value is
360         * specified.
361         * <p>
362         * The actual value will be {@link #TRUNCATE} when the device runs API Level lower than 16
363         * or it is a low-RAM device. Otherwise, {@link #WRITE_AHEAD_LOGGING} will be used.
364         */
365        AUTOMATIC,
366
367        /**
368         * Truncate journal mode.
369         */
370        TRUNCATE,
371
372        /**
373         * Write-Ahead Logging mode.
374         */
375        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
376        WRITE_AHEAD_LOGGING;
377
378        /**
379         * Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or
380         * {@link #WRITE_AHEAD_LOGGING}.
381         */
382        @SuppressLint("NewApi")
383        JournalMode resolve(Context context) {
384            if (this != AUTOMATIC) {
385                return this;
386            }
387            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
388                ActivityManager manager = (ActivityManager)
389                        context.getSystemService(Context.ACTIVITY_SERVICE);
390                if (manager != null && !ActivityManagerCompat.isLowRamDevice(manager)) {
391                    return WRITE_AHEAD_LOGGING;
392                }
393            }
394            return TRUNCATE;
395        }
396    }
397
398    /**
399     * Builder for RoomDatabase.
400     *
401     * @param <T> The type of the abstract database class.
402     */
403    public static class Builder<T extends RoomDatabase> {
404        private final Class<T> mDatabaseClass;
405        private final String mName;
406        private final Context mContext;
407        private ArrayList<Callback> mCallbacks;
408
409        private SupportSQLiteOpenHelper.Factory mFactory;
410        private boolean mAllowMainThreadQueries;
411        private JournalMode mJournalMode;
412        private boolean mRequireMigration;
413        /**
414         * Migrations, mapped by from-to pairs.
415         */
416        private final MigrationContainer mMigrationContainer;
417        private Set<Integer> mMigrationsNotRequiredFrom;
418        /**
419         * Keeps track of {@link Migration#startVersion}s and {@link Migration#endVersion}s added in
420         * {@link #addMigrations(Migration...)} for later validation that makes those versions don't
421         * match any versions passed to {@link #fallbackToDestructiveMigrationFrom(int...)}.
422         */
423        private Set<Integer> mMigrationStartAndEndVersions;
424
425        Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
426            mContext = context;
427            mDatabaseClass = klass;
428            mName = name;
429            mJournalMode = JournalMode.AUTOMATIC;
430            mRequireMigration = true;
431            mMigrationContainer = new MigrationContainer();
432        }
433
434        /**
435         * Sets the database factory. If not set, it defaults to
436         * {@link FrameworkSQLiteOpenHelperFactory}.
437         *
438         * @param factory The factory to use to access the database.
439         * @return this
440         */
441        @NonNull
442        public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
443            mFactory = factory;
444            return this;
445        }
446
447        /**
448         * Adds a migration to the builder.
449         * <p>
450         * Each Migration has a start and end versions and Room runs these migrations to bring the
451         * database to the latest version.
452         * <p>
453         * If a migration item is missing between current version and the latest version, Room
454         * will clear the database and recreate so even if you have no changes between 2 versions,
455         * you should still provide a Migration object to the builder.
456         * <p>
457         * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
458         * going version 3 to 5 without going to version 4). If Room opens a database at version
459         * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
460         * 3 to 5 instead of 3 to 4 and 4 to 5.
461         *
462         * @param migrations The migration object that can modify the database and to the necessary
463         *                   changes.
464         * @return this
465         */
466        @NonNull
467        public Builder<T> addMigrations(@NonNull  Migration... migrations) {
468            if (mMigrationStartAndEndVersions == null) {
469                mMigrationStartAndEndVersions = new HashSet<>();
470            }
471            for (Migration migration: migrations) {
472                mMigrationStartAndEndVersions.add(migration.startVersion);
473                mMigrationStartAndEndVersions.add(migration.endVersion);
474            }
475
476            mMigrationContainer.addMigrations(migrations);
477            return this;
478        }
479
480        /**
481         * Disables the main thread query check for Room.
482         * <p>
483         * Room ensures that Database is never accessed on the main thread because it may lock the
484         * main thread and trigger an ANR. If you need to access the database from the main thread,
485         * you should always use async alternatives or manually move the call to a background
486         * thread.
487         * <p>
488         * You may want to turn this check off for testing.
489         *
490         * @return this
491         */
492        @NonNull
493        public Builder<T> allowMainThreadQueries() {
494            mAllowMainThreadQueries = true;
495            return this;
496        }
497
498        /**
499         * Sets the journal mode for this database.
500         *
501         * <p>
502         * This value is ignored if the builder is initialized with
503         * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
504         * <p>
505         * The journal mode should be consistent across multiple instances of
506         * {@link RoomDatabase} for a single SQLite database file.
507         * <p>
508         * The default value is {@link JournalMode#AUTOMATIC}.
509         *
510         * @param journalMode The journal mode.
511         * @return this
512         */
513        @NonNull
514        public Builder<T> setJournalMode(@NonNull JournalMode journalMode) {
515            mJournalMode = journalMode;
516            return this;
517        }
518
519        /**
520         * Allows Room to destructively recreate database tables if {@link Migration}s that would
521         * migrate old database schemas to the latest schema version are not found.
522         * <p>
523         * When the database version on the device does not match the latest schema version, Room
524         * runs necessary {@link Migration}s on the database.
525         * <p>
526         * If it cannot find the set of {@link Migration}s that will bring the database to the
527         * current version, it will throw an {@link IllegalStateException}.
528         * <p>
529         * You can call this method to change this behavior to re-create the database instead of
530         * crashing.
531         * <p>
532         * Note that this will delete all of the data in the database tables managed by Room.
533         *
534         * @return this
535         */
536        @NonNull
537        public Builder<T> fallbackToDestructiveMigration() {
538            mRequireMigration = false;
539            return this;
540        }
541
542        /**
543         * Informs Room that it is allowed to destructively recreate database tables from specific
544         * starting schema versions.
545         * <p>
546         * This functionality is the same as that provided by
547         * {@link #fallbackToDestructiveMigration()}, except that this method allows the
548         * specification of a set of schema versions for which destructive recreation is allowed.
549         * <p>
550         * Using this method is preferable to {@link #fallbackToDestructiveMigration()} if you want
551         * to allow destructive migrations from some schema versions while still taking advantage
552         * of exceptions being thrown due to unintentionally missing migrations.
553         * <p>
554         * Note: No versions passed to this method may also exist as either starting or ending
555         * versions in the {@link Migration}s provided to {@link #addMigrations(Migration...)}. If a
556         * version passed to this method is found as a starting or ending version in a Migration, an
557         * exception will be thrown.
558         *
559         * @param startVersions The set of schema versions from which Room should use a destructive
560         *                      migration.
561         * @return this
562         */
563        @NonNull
564        public Builder<T> fallbackToDestructiveMigrationFrom(int... startVersions) {
565            if (mMigrationsNotRequiredFrom == null) {
566                mMigrationsNotRequiredFrom = new HashSet<>(startVersions.length);
567            }
568            for (int startVersion : startVersions) {
569                mMigrationsNotRequiredFrom.add(startVersion);
570            }
571            return this;
572        }
573
574        /**
575         * Adds a {@link Callback} to this database.
576         *
577         * @param callback The callback.
578         * @return this
579         */
580        @NonNull
581        public Builder<T> addCallback(@NonNull Callback callback) {
582            if (mCallbacks == null) {
583                mCallbacks = new ArrayList<>();
584            }
585            mCallbacks.add(callback);
586            return this;
587        }
588
589        /**
590         * Creates the databases and initializes it.
591         * <p>
592         * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
593         * triggers.
594         *
595         * @return A new database instance.
596         */
597        @NonNull
598        public T build() {
599            //noinspection ConstantConditions
600            if (mContext == null) {
601                throw new IllegalArgumentException("Cannot provide null context for the database.");
602            }
603            //noinspection ConstantConditions
604            if (mDatabaseClass == null) {
605                throw new IllegalArgumentException("Must provide an abstract class that"
606                        + " extends RoomDatabase");
607            }
608
609            if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
610                for (Integer version : mMigrationStartAndEndVersions) {
611                    if (mMigrationsNotRequiredFrom.contains(version)) {
612                        throw new IllegalArgumentException(
613                                "Inconsistency detected. A Migration was supplied to "
614                                        + "addMigration(Migration... migrations) that has a start "
615                                        + "or end version equal to a start version supplied to "
616                                        + "fallbackToDestructiveMigrationFrom(int... "
617                                        + "startVersions). Start version: "
618                                        + version);
619                    }
620                }
621            }
622
623            if (mFactory == null) {
624                mFactory = new FrameworkSQLiteOpenHelperFactory();
625            }
626            DatabaseConfiguration configuration =
627                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
628                            mCallbacks, mAllowMainThreadQueries,
629                            mJournalMode.resolve(mContext),
630                            mRequireMigration, mMigrationsNotRequiredFrom);
631            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
632            db.init(configuration);
633            return db;
634        }
635    }
636
637    /**
638     * A container to hold migrations. It also allows querying its contents to find migrations
639     * between two versions.
640     */
641    public static class MigrationContainer {
642        private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
643                new SparseArrayCompat<>();
644
645        /**
646         * Adds the given migrations to the list of available migrations. If 2 migrations have the
647         * same start-end versions, the latter migration overrides the previous one.
648         *
649         * @param migrations List of available migrations.
650         */
651        public void addMigrations(@NonNull Migration... migrations) {
652            for (Migration migration : migrations) {
653                addMigration(migration);
654            }
655        }
656
657        private void addMigration(Migration migration) {
658            final int start = migration.startVersion;
659            final int end = migration.endVersion;
660            SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
661            if (targetMap == null) {
662                targetMap = new SparseArrayCompat<>();
663                mMigrations.put(start, targetMap);
664            }
665            Migration existing = targetMap.get(end);
666            if (existing != null) {
667                Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
668            }
669            targetMap.append(end, migration);
670        }
671
672        /**
673         * Finds the list of migrations that should be run to move from {@code start} version to
674         * {@code end} version.
675         *
676         * @param start The current database version
677         * @param end   The target database version
678         * @return An ordered list of {@link Migration} objects that should be run to migrate
679         * between the given versions. If a migration path cannot be found, returns {@code null}.
680         */
681        @SuppressWarnings("WeakerAccess")
682        @Nullable
683        public List<Migration> findMigrationPath(int start, int end) {
684            if (start == end) {
685                return Collections.emptyList();
686            }
687            boolean migrateUp = end > start;
688            List<Migration> result = new ArrayList<>();
689            return findUpMigrationPath(result, migrateUp, start, end);
690        }
691
692        private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
693                int start, int end) {
694            final int searchDirection = upgrade ? -1 : 1;
695            while (upgrade ? start < end : start > end) {
696                SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
697                if (targetNodes == null) {
698                    return null;
699                }
700                // keys are ordered so we can start searching from one end of them.
701                final int size = targetNodes.size();
702                final int firstIndex;
703                final int lastIndex;
704
705                if (upgrade) {
706                    firstIndex = size - 1;
707                    lastIndex = -1;
708                } else {
709                    firstIndex = 0;
710                    lastIndex = size;
711                }
712                boolean found = false;
713                for (int i = firstIndex; i != lastIndex; i += searchDirection) {
714                    final int targetVersion = targetNodes.keyAt(i);
715                    final boolean shouldAddToPath;
716                    if (upgrade) {
717                        shouldAddToPath = targetVersion <= end && targetVersion > start;
718                    } else {
719                        shouldAddToPath = targetVersion >= end && targetVersion < start;
720                    }
721                    if (shouldAddToPath) {
722                        result.add(targetNodes.valueAt(i));
723                        start = targetVersion;
724                        found = true;
725                        break;
726                    }
727                }
728                if (!found) {
729                    return null;
730                }
731            }
732            return result;
733        }
734    }
735
736    /**
737     * Callback for {@link RoomDatabase}.
738     */
739    public abstract static class Callback {
740
741        /**
742         * Called when the database is created for the first time. This is called after all the
743         * tables are created.
744         *
745         * @param db The database.
746         */
747        public void onCreate(@NonNull SupportSQLiteDatabase db) {
748        }
749
750        /**
751         * Called when the database has been opened.
752         *
753         * @param db The database.
754         */
755        public void onOpen(@NonNull SupportSQLiteDatabase db) {
756        }
757    }
758}
759