Home | History | Annotate | Download | only in sqlite
      1 /*
      2  * Copyright (C) 2007 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 android.database.sqlite;
     18 
     19 import android.annotation.IntRange;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.content.Context;
     23 import android.database.DatabaseErrorHandler;
     24 import android.database.SQLException;
     25 import android.database.sqlite.SQLiteDatabase.CursorFactory;
     26 import android.os.FileUtils;
     27 import android.util.Log;
     28 
     29 import com.android.internal.util.Preconditions;
     30 
     31 import java.io.File;
     32 
     33 /**
     34  * A helper class to manage database creation and version management.
     35  *
     36  * <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
     37  * optionally {@link #onOpen}, and this class takes care of opening the database
     38  * if it exists, creating it if it does not, and upgrading it as necessary.
     39  * Transactions are used to make sure the database is always in a sensible state.
     40  *
     41  * <p>This class makes it easy for {@link android.content.ContentProvider}
     42  * implementations to defer opening and upgrading the database until first use,
     43  * to avoid blocking application startup with long-running database upgrades.
     44  *
     45  * <p>For an example, see the NotePadProvider class in the NotePad sample application,
     46  * in the <em>samples/</em> directory of the SDK.</p>
     47  *
     48  * <p class="note"><strong>Note:</strong> this class assumes
     49  * monotonically increasing version numbers for upgrades.</p>
     50  */
     51 public abstract class SQLiteOpenHelper {
     52     private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
     53 
     54     private final Context mContext;
     55     private final String mName;
     56     private final int mNewVersion;
     57     private final int mMinimumSupportedVersion;
     58 
     59     private SQLiteDatabase mDatabase;
     60     private boolean mIsInitializing;
     61     private SQLiteDatabase.OpenParams.Builder mOpenParamsBuilder;
     62 
     63     /**
     64      * Create a helper object to create, open, and/or manage a database.
     65      * This method always returns very quickly.  The database is not actually
     66      * created or opened until one of {@link #getWritableDatabase} or
     67      * {@link #getReadableDatabase} is called.
     68      *
     69      * @param context to use for locating paths to the the database
     70      * @param name of the database file, or null for an in-memory database
     71      * @param factory to use for creating cursor objects, or null for the default
     72      * @param version number of the database (starting at 1); if the database is older,
     73      *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     74      *     newer, {@link #onDowngrade} will be used to downgrade the database
     75      */
     76     public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
     77             @Nullable CursorFactory factory, int version) {
     78         this(context, name, factory, version, null);
     79     }
     80 
     81     /**
     82      * Create a helper object to create, open, and/or manage a database.
     83      * The database is not actually created or opened until one of
     84      * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
     85      *
     86      * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
     87      * used to handle corruption when sqlite reports database corruption.</p>
     88      *
     89      * @param context to use for locating paths to the the database
     90      * @param name of the database file, or null for an in-memory database
     91      * @param factory to use for creating cursor objects, or null for the default
     92      * @param version number of the database (starting at 1); if the database is older,
     93      *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     94      *     newer, {@link #onDowngrade} will be used to downgrade the database
     95      * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
     96      * corruption, or null to use the default error handler.
     97      */
     98     public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
     99             @Nullable CursorFactory factory, int version,
    100             @Nullable DatabaseErrorHandler errorHandler) {
    101         this(context, name, factory, version, 0, errorHandler);
    102     }
    103 
    104     /**
    105      * Create a helper object to create, open, and/or manage a database.
    106      * This method always returns very quickly.  The database is not actually
    107      * created or opened until one of {@link #getWritableDatabase} or
    108      * {@link #getReadableDatabase} is called.
    109      *
    110      * @param context to use for locating paths to the the database
    111      * @param name of the database file, or null for an in-memory database
    112      * @param version number of the database (starting at 1); if the database is older,
    113      *     {@link #onUpgrade} will be used to upgrade the database; if the database is
    114      *     newer, {@link #onDowngrade} will be used to downgrade the database
    115      * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
    116      *        Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be
    117      *        set when the helper opens the database
    118      */
    119     public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
    120             @NonNull SQLiteDatabase.OpenParams openParams) {
    121         this(context, name, version, 0, openParams.toBuilder());
    122     }
    123 
    124     /**
    125      * Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)}
    126      * but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old
    127      * versions of this database that are no longer supported. If a database with older version that
    128      * minimumSupportedVersion is found, it is simply deleted and a new database is created with the
    129      * given name and version
    130      *
    131      * @param context to use for locating paths to the the database
    132      * @param name the name of the database file, null for a temporary in-memory database
    133      * @param factory to use for creating cursor objects, null for default
    134      * @param version the required version of the database
    135      * @param minimumSupportedVersion the minimum version that is supported to be upgraded to
    136      *            {@code version} via {@link #onUpgrade}. If the current database version is lower
    137      *            than this, database is simply deleted and recreated with the version passed in
    138      *            {@code version}. {@link #onBeforeDelete} is called before deleting the database
    139      *            when this happens. This is 0 by default.
    140      * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
    141      *            corruption, or null to use the default error handler.
    142      * @see #onBeforeDelete(SQLiteDatabase)
    143      * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)
    144      * @see #onUpgrade(SQLiteDatabase, int, int)
    145      * @hide
    146      */
    147     public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
    148             @Nullable CursorFactory factory, int version,
    149             int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) {
    150         this(context, name, version, minimumSupportedVersion,
    151                 new SQLiteDatabase.OpenParams.Builder());
    152         mOpenParamsBuilder.setCursorFactory(factory);
    153         mOpenParamsBuilder.setErrorHandler(errorHandler);
    154     }
    155 
    156     private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
    157             int minimumSupportedVersion,
    158             @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
    159         Preconditions.checkNotNull(openParamsBuilder);
    160         if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
    161 
    162         mContext = context;
    163         mName = name;
    164         mNewVersion = version;
    165         mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
    166         setOpenParamsBuilder(openParamsBuilder);
    167     }
    168 
    169     /**
    170      * Return the name of the SQLite database being opened, as given to
    171      * the constructor.
    172      */
    173     public String getDatabaseName() {
    174         return mName;
    175     }
    176 
    177     /**
    178      * Enables or disables the use of write-ahead logging for the database.
    179      *
    180      * Write-ahead logging cannot be used with read-only databases so the value of
    181      * this flag is ignored if the database is opened read-only.
    182      *
    183      * @param enabled True if write-ahead logging should be enabled, false if it
    184      * should be disabled.
    185      *
    186      * @see SQLiteDatabase#enableWriteAheadLogging()
    187      */
    188     public void setWriteAheadLoggingEnabled(boolean enabled) {
    189         synchronized (this) {
    190             if (mOpenParamsBuilder.isWriteAheadLoggingEnabled() != enabled) {
    191                 if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
    192                     if (enabled) {
    193                         mDatabase.enableWriteAheadLogging();
    194                     } else {
    195                         mDatabase.disableWriteAheadLogging();
    196                     }
    197                 }
    198                 mOpenParamsBuilder.setWriteAheadLoggingEnabled(enabled);
    199             }
    200             // Compatibility WAL is disabled if an app disables or enables WAL
    201             mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.DISABLE_COMPATIBILITY_WAL);
    202         }
    203     }
    204 
    205     /**
    206      * Configures <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a>
    207      *
    208      * <p>This method should be called from the constructor of the subclass,
    209      * before opening the database, since lookaside memory configuration can only be changed
    210      * when no connection is using it
    211      *
    212      * <p>SQLite default settings will be used, if this method isn't called.
    213      * Use {@code setLookasideConfig(0,0)} to disable lookaside
    214      *
    215      * <p><strong>Note:</strong> Provided slotSize/slotCount configuration is just a recommendation.
    216      * The system may choose different values depending on a device, e.g. lookaside allocations
    217      * can be disabled on low-RAM devices
    218      *
    219      * @param slotSize The size in bytes of each lookaside slot.
    220      * @param slotCount The total number of lookaside memory slots per database connection.
    221      */
    222     public void setLookasideConfig(@IntRange(from = 0) final int slotSize,
    223             @IntRange(from = 0) final int slotCount) {
    224         synchronized (this) {
    225             if (mDatabase != null && mDatabase.isOpen()) {
    226                 throw new IllegalStateException(
    227                         "Lookaside memory config cannot be changed after opening the database");
    228             }
    229             mOpenParamsBuilder.setLookasideConfig(slotSize, slotCount);
    230         }
    231     }
    232 
    233     /**
    234      * Sets configuration parameters that are used for opening {@link SQLiteDatabase}.
    235      * <p>Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be set when
    236      * opening the database
    237      *
    238      * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
    239      * @throws IllegalStateException if the database is already open
    240      */
    241     public void setOpenParams(@NonNull SQLiteDatabase.OpenParams openParams) {
    242         Preconditions.checkNotNull(openParams);
    243         synchronized (this) {
    244             if (mDatabase != null && mDatabase.isOpen()) {
    245                 throw new IllegalStateException(
    246                         "OpenParams cannot be set after opening the database");
    247             }
    248             setOpenParamsBuilder(new SQLiteDatabase.OpenParams.Builder(openParams));
    249         }
    250     }
    251 
    252     private void setOpenParamsBuilder(SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
    253         mOpenParamsBuilder = openParamsBuilder;
    254         mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
    255     }
    256 
    257     /**
    258      * Sets the maximum number of milliseconds that SQLite connection is allowed to be idle
    259      * before it is closed and removed from the pool.
    260      *
    261      * <p>This method should be called from the constructor of the subclass,
    262      * before opening the database
    263      *
    264      * @param idleConnectionTimeoutMs timeout in milliseconds. Use {@link Long#MAX_VALUE} value
    265      * to allow unlimited idle connections.
    266      */
    267     public void setIdleConnectionTimeout(@IntRange(from = 0) final long idleConnectionTimeoutMs) {
    268         synchronized (this) {
    269             if (mDatabase != null && mDatabase.isOpen()) {
    270                 throw new IllegalStateException(
    271                         "Connection timeout setting cannot be changed after opening the database");
    272             }
    273             mOpenParamsBuilder.setIdleConnectionTimeout(idleConnectionTimeoutMs);
    274         }
    275     }
    276 
    277     /**
    278      * Create and/or open a database that will be used for reading and writing.
    279      * The first time this is called, the database will be opened and
    280      * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
    281      * called.
    282      *
    283      * <p>Once opened successfully, the database is cached, so you can
    284      * call this method every time you need to write to the database.
    285      * (Make sure to call {@link #close} when you no longer need the database.)
    286      * Errors such as bad permissions or a full disk may cause this method
    287      * to fail, but future attempts may succeed if the problem is fixed.</p>
    288      *
    289      * <p class="caution">Database upgrade may take a long time, you
    290      * should not call this method from the application main thread, including
    291      * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
    292      *
    293      * @throws SQLiteException if the database cannot be opened for writing
    294      * @return a read/write database object valid until {@link #close} is called
    295      */
    296     public SQLiteDatabase getWritableDatabase() {
    297         synchronized (this) {
    298             return getDatabaseLocked(true);
    299         }
    300     }
    301 
    302     /**
    303      * Create and/or open a database.  This will be the same object returned by
    304      * {@link #getWritableDatabase} unless some problem, such as a full disk,
    305      * requires the database to be opened read-only.  In that case, a read-only
    306      * database object will be returned.  If the problem is fixed, a future call
    307      * to {@link #getWritableDatabase} may succeed, in which case the read-only
    308      * database object will be closed and the read/write object will be returned
    309      * in the future.
    310      *
    311      * <p class="caution">Like {@link #getWritableDatabase}, this method may
    312      * take a long time to return, so you should not call it from the
    313      * application main thread, including from
    314      * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
    315      *
    316      * @throws SQLiteException if the database cannot be opened
    317      * @return a database object valid until {@link #getWritableDatabase}
    318      *     or {@link #close} is called.
    319      */
    320     public SQLiteDatabase getReadableDatabase() {
    321         synchronized (this) {
    322             return getDatabaseLocked(false);
    323         }
    324     }
    325 
    326     private SQLiteDatabase getDatabaseLocked(boolean writable) {
    327         if (mDatabase != null) {
    328             if (!mDatabase.isOpen()) {
    329                 // Darn!  The user closed the database by calling mDatabase.close().
    330                 mDatabase = null;
    331             } else if (!writable || !mDatabase.isReadOnly()) {
    332                 // The database is already open for business.
    333                 return mDatabase;
    334             }
    335         }
    336 
    337         if (mIsInitializing) {
    338             throw new IllegalStateException("getDatabase called recursively");
    339         }
    340 
    341         SQLiteDatabase db = mDatabase;
    342         try {
    343             mIsInitializing = true;
    344 
    345             if (db != null) {
    346                 if (writable && db.isReadOnly()) {
    347                     db.reopenReadWrite();
    348                 }
    349             } else if (mName == null) {
    350                 db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
    351             } else {
    352                 final File filePath = mContext.getDatabasePath(mName);
    353                 SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
    354                 try {
    355                     db = SQLiteDatabase.openDatabase(filePath, params);
    356                     // Keep pre-O-MR1 behavior by resetting file permissions to 660
    357                     setFilePermissionsForDb(filePath.getPath());
    358                 } catch (SQLException ex) {
    359                     if (writable) {
    360                         throw ex;
    361                     }
    362                     Log.e(TAG, "Couldn't open " + mName
    363                             + " for writing (will try read-only):", ex);
    364                     params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
    365                     db = SQLiteDatabase.openDatabase(filePath, params);
    366                 }
    367             }
    368 
    369             onConfigure(db);
    370 
    371             final int version = db.getVersion();
    372             if (version != mNewVersion) {
    373                 if (db.isReadOnly()) {
    374                     throw new SQLiteException("Can't upgrade read-only database from version " +
    375                             db.getVersion() + " to " + mNewVersion + ": " + mName);
    376                 }
    377 
    378                 if (version > 0 && version < mMinimumSupportedVersion) {
    379                     File databaseFile = new File(db.getPath());
    380                     onBeforeDelete(db);
    381                     db.close();
    382                     if (SQLiteDatabase.deleteDatabase(databaseFile)) {
    383                         mIsInitializing = false;
    384                         return getDatabaseLocked(writable);
    385                     } else {
    386                         throw new IllegalStateException("Unable to delete obsolete database "
    387                                 + mName + " with version " + version);
    388                     }
    389                 } else {
    390                     db.beginTransaction();
    391                     try {
    392                         if (version == 0) {
    393                             onCreate(db);
    394                         } else {
    395                             if (version > mNewVersion) {
    396                                 onDowngrade(db, version, mNewVersion);
    397                             } else {
    398                                 onUpgrade(db, version, mNewVersion);
    399                             }
    400                         }
    401                         db.setVersion(mNewVersion);
    402                         db.setTransactionSuccessful();
    403                     } finally {
    404                         db.endTransaction();
    405                     }
    406                 }
    407             }
    408 
    409             onOpen(db);
    410 
    411             if (db.isReadOnly()) {
    412                 Log.w(TAG, "Opened " + mName + " in read-only mode");
    413             }
    414 
    415             mDatabase = db;
    416             return db;
    417         } finally {
    418             mIsInitializing = false;
    419             if (db != null && db != mDatabase) {
    420                 db.close();
    421             }
    422         }
    423     }
    424 
    425     private static void setFilePermissionsForDb(String dbPath) {
    426         int perms = FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP;
    427         FileUtils.setPermissions(dbPath, perms, -1, -1);
    428     }
    429 
    430     /**
    431      * Close any open database object.
    432      */
    433     public synchronized void close() {
    434         if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
    435 
    436         if (mDatabase != null && mDatabase.isOpen()) {
    437             mDatabase.close();
    438             mDatabase = null;
    439         }
    440     }
    441 
    442     /**
    443      * Called when the database connection is being configured, to enable features such as
    444      * write-ahead logging or foreign key support.
    445      * <p>
    446      * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade}, or
    447      * {@link #onOpen} are called. It should not modify the database except to configure the
    448      * database connection as required.
    449      * </p>
    450      * <p>
    451      * This method should only call methods that configure the parameters of the database
    452      * connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
    453      * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, {@link SQLiteDatabase#setLocale},
    454      * {@link SQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
    455      * </p>
    456      *
    457      * @param db The database.
    458      */
    459     public void onConfigure(SQLiteDatabase db) {}
    460 
    461     /**
    462      * Called before the database is deleted when the version returned by
    463      * {@link SQLiteDatabase#getVersion()} is lower than the minimum supported version passed (if at
    464      * all) while creating this helper. After the database is deleted, a fresh database with the
    465      * given version is created. This will be followed by {@link #onConfigure(SQLiteDatabase)} and
    466      * {@link #onCreate(SQLiteDatabase)} being called with a new SQLiteDatabase object
    467      *
    468      * @param db the database opened with this helper
    469      * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, int, DatabaseErrorHandler)
    470      * @hide
    471      */
    472     public void onBeforeDelete(SQLiteDatabase db) {
    473     }
    474 
    475     /**
    476      * Called when the database is created for the first time. This is where the
    477      * creation of tables and the initial population of the tables should happen.
    478      *
    479      * @param db The database.
    480      */
    481     public abstract void onCreate(SQLiteDatabase db);
    482 
    483     /**
    484      * Called when the database needs to be upgraded. The implementation
    485      * should use this method to drop tables, add tables, or do anything else it
    486      * needs to upgrade to the new schema version.
    487      *
    488      * <p>
    489      * The SQLite ALTER TABLE documentation can be found
    490      * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
    491      * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
    492      * you can use ALTER TABLE to rename the old table, then create the new table and then
    493      * populate the new table with the contents of the old table.
    494      * </p><p>
    495      * This method executes within a transaction.  If an exception is thrown, all changes
    496      * will automatically be rolled back.
    497      * </p>
    498      *
    499      * @param db The database.
    500      * @param oldVersion The old database version.
    501      * @param newVersion The new database version.
    502      */
    503     public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    504 
    505     /**
    506      * Called when the database needs to be downgraded. This is strictly similar to
    507      * {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
    508      * However, this method is not abstract, so it is not mandatory for a customer to
    509      * implement it. If not overridden, default implementation will reject downgrade and
    510      * throws SQLiteException
    511      *
    512      * <p>
    513      * This method executes within a transaction.  If an exception is thrown, all changes
    514      * will automatically be rolled back.
    515      * </p>
    516      *
    517      * @param db The database.
    518      * @param oldVersion The old database version.
    519      * @param newVersion The new database version.
    520      */
    521     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    522         throw new SQLiteException("Can't downgrade database from version " +
    523                 oldVersion + " to " + newVersion);
    524     }
    525 
    526     /**
    527      * Called when the database has been opened.  The implementation
    528      * should check {@link SQLiteDatabase#isReadOnly} before updating the
    529      * database.
    530      * <p>
    531      * This method is called after the database connection has been configured
    532      * and after the database schema has been created, upgraded or downgraded as necessary.
    533      * If the database connection must be configured in some way before the schema
    534      * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
    535      * </p>
    536      *
    537      * @param db The database.
    538      */
    539     public void onOpen(SQLiteDatabase db) {}
    540 }
    541