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 17 package androidx.room; 18 19 import android.annotation.SuppressLint; 20 import android.app.ActivityManager; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.os.Build; 24 import android.util.Log; 25 26 import androidx.annotation.CallSuper; 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 import androidx.annotation.RequiresApi; 30 import androidx.annotation.RestrictTo; 31 import androidx.annotation.WorkerThread; 32 import androidx.collection.SparseArrayCompat; 33 import androidx.core.app.ActivityManagerCompat; 34 import androidx.arch.core.executor.ArchTaskExecutor; 35 import androidx.room.migration.Migration; 36 import androidx.sqlite.db.SimpleSQLiteQuery; 37 import androidx.sqlite.db.SupportSQLiteDatabase; 38 import androidx.sqlite.db.SupportSQLiteOpenHelper; 39 import androidx.sqlite.db.SupportSQLiteQuery; 40 import androidx.sqlite.db.SupportSQLiteStatement; 41 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory; 42 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Set; 48 import java.util.concurrent.Callable; 49 import java.util.concurrent.locks.Lock; 50 import 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"}) 62 public 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 */ getCloseLock()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 */ RoomDatabase()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 init(@onNull DatabaseConfiguration configuration)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 getOpenHelper()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 createOpenHelper(DatabaseConfiguration config)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 createInvalidationTracker()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 clearAllTables()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 */ isOpen()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 */ close()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 assertNotMainThread()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 */ query(String query, @Nullable Object[] args)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 */ query(SupportSQLiteQuery query)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 */ compileStatement(@onNull String sql)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 */ beginTransaction()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 */ endTransaction()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 */ setTransactionSuccessful()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 */ runInTransaction(@onNull Runnable body)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 */ runInTransaction(@onNull Callable<V> body)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 */ internalInitInvalidationTracker(@onNull SupportSQLiteDatabase db)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 getInvalidationTracker()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") inTransaction()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") resolve(Context context)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 Builder(@onNull Context context, @NonNull Class<T> klass, @Nullable String name)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 openHelperFactory(@ullable SupportSQLiteOpenHelper.Factory factory)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 >= 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 addMigrations(@onNull Migration... migrations)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 allowMainThreadQueries()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 setJournalMode(@onNull JournalMode journalMode)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 fallbackToDestructiveMigration()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 fallbackToDestructiveMigrationFrom(int... startVersions)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 addCallback(@onNull Callback callback)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 build()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 */ addMigrations(@onNull Migration... migrations)651 public void addMigrations(@NonNull Migration... migrations) { 652 for (Migration migration : migrations) { 653 addMigration(migration); 654 } 655 } 656 addMigration(Migration migration)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 findMigrationPath(int start, int end)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 findUpMigrationPath(List<Migration> result, boolean upgrade, int start, int end)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