blob: d89cf00f9bb8032afeea390aa8cfc6fbf163e3b1 [file] [log] [blame]
Justin Klaassen4d01eea2018-04-03 23:21:57 -04001/*
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}