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.content.Context;
     20 import android.database.DatabaseErrorHandler;
     21 import android.database.sqlite.SQLiteDatabase.CursorFactory;
     22 import android.util.Log;
     23 
     24 /**
     25  * A helper class to manage database creation and version management.
     26  *
     27  * <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
     28  * optionally {@link #onOpen}, and this class takes care of opening the database
     29  * if it exists, creating it if it does not, and upgrading it as necessary.
     30  * Transactions are used to make sure the database is always in a sensible state.
     31  *
     32  * <p>This class makes it easy for {@link android.content.ContentProvider}
     33  * implementations to defer opening and upgrading the database until first use,
     34  * to avoid blocking application startup with long-running database upgrades.
     35  *
     36  * <p>For an example, see the NotePadProvider class in the NotePad sample application,
     37  * in the <em>samples/</em> directory of the SDK.</p>
     38  *
     39  * <p class="note"><strong>Note:</strong> this class assumes
     40  * monotonically increasing version numbers for upgrades.</p>
     41  */
     42 public abstract class SQLiteOpenHelper {
     43     private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
     44 
     45     // When true, getReadableDatabase returns a read-only database if it is just being opened.
     46     // The database handle is reopened in read/write mode when getWritableDatabase is called.
     47     // We leave this behavior disabled in production because it is inefficient and breaks
     48     // many applications.  For debugging purposes it can be useful to turn on strict
     49     // read-only semantics to catch applications that call getReadableDatabase when they really
     50     // wanted getWritableDatabase.
     51     private static final boolean DEBUG_STRICT_READONLY = false;
     52 
     53     private final Context mContext;
     54     private final String mName;
     55     private final CursorFactory mFactory;
     56     private final int mNewVersion;
     57 
     58     private SQLiteDatabase mDatabase;
     59     private boolean mIsInitializing;
     60     private boolean mEnableWriteAheadLogging;
     61     private final DatabaseErrorHandler mErrorHandler;
     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 to open or create 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(Context context, String name, CursorFactory factory, int version) {
     77         this(context, name, factory, version, null);
     78     }
     79 
     80     /**
     81      * Create a helper object to create, open, and/or manage a database.
     82      * The database is not actually created or opened until one of
     83      * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
     84      *
     85      * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
     86      * used to handle corruption when sqlite reports database corruption.</p>
     87      *
     88      * @param context to use to open or create the database
     89      * @param name of the database file, or null for an in-memory database
     90      * @param factory to use for creating cursor objects, or null for the default
     91      * @param version number of the database (starting at 1); if the database is older,
     92      *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     93      *     newer, {@link #onDowngrade} will be used to downgrade the database
     94      * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
     95      * corruption, or null to use the default error handler.
     96      */
     97     public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
     98             DatabaseErrorHandler errorHandler) {
     99         if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
    100 
    101         mContext = context;
    102         mName = name;
    103         mFactory = factory;
    104         mNewVersion = version;
    105         mErrorHandler = errorHandler;
    106     }
    107 
    108     /**
    109      * Return the name of the SQLite database being opened, as given to
    110      * the constructor.
    111      */
    112     public String getDatabaseName() {
    113         return mName;
    114     }
    115 
    116     /**
    117      * Enables or disables the use of write-ahead logging for the database.
    118      *
    119      * Write-ahead logging cannot be used with read-only databases so the value of
    120      * this flag is ignored if the database is opened read-only.
    121      *
    122      * @param enabled True if write-ahead logging should be enabled, false if it
    123      * should be disabled.
    124      *
    125      * @see SQLiteDatabase#enableWriteAheadLogging()
    126      */
    127     public void setWriteAheadLoggingEnabled(boolean enabled) {
    128         synchronized (this) {
    129             if (mEnableWriteAheadLogging != enabled) {
    130                 if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
    131                     if (enabled) {
    132                         mDatabase.enableWriteAheadLogging();
    133                     } else {
    134                         mDatabase.disableWriteAheadLogging();
    135                     }
    136                 }
    137                 mEnableWriteAheadLogging = enabled;
    138             }
    139         }
    140     }
    141 
    142     /**
    143      * Create and/or open a database that will be used for reading and writing.
    144      * The first time this is called, the database will be opened and
    145      * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
    146      * called.
    147      *
    148      * <p>Once opened successfully, the database is cached, so you can
    149      * call this method every time you need to write to the database.
    150      * (Make sure to call {@link #close} when you no longer need the database.)
    151      * Errors such as bad permissions or a full disk may cause this method
    152      * to fail, but future attempts may succeed if the problem is fixed.</p>
    153      *
    154      * <p class="caution">Database upgrade may take a long time, you
    155      * should not call this method from the application main thread, including
    156      * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
    157      *
    158      * @throws SQLiteException if the database cannot be opened for writing
    159      * @return a read/write database object valid until {@link #close} is called
    160      */
    161     public SQLiteDatabase getWritableDatabase() {
    162         synchronized (this) {
    163             return getDatabaseLocked(true);
    164         }
    165     }
    166 
    167     /**
    168      * Create and/or open a database.  This will be the same object returned by
    169      * {@link #getWritableDatabase} unless some problem, such as a full disk,
    170      * requires the database to be opened read-only.  In that case, a read-only
    171      * database object will be returned.  If the problem is fixed, a future call
    172      * to {@link #getWritableDatabase} may succeed, in which case the read-only
    173      * database object will be closed and the read/write object will be returned
    174      * in the future.
    175      *
    176      * <p class="caution">Like {@link #getWritableDatabase}, this method may
    177      * take a long time to return, so you should not call it from the
    178      * application main thread, including from
    179      * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
    180      *
    181      * @throws SQLiteException if the database cannot be opened
    182      * @return a database object valid until {@link #getWritableDatabase}
    183      *     or {@link #close} is called.
    184      */
    185     public SQLiteDatabase getReadableDatabase() {
    186         synchronized (this) {
    187             return getDatabaseLocked(false);
    188         }
    189     }
    190 
    191     private SQLiteDatabase getDatabaseLocked(boolean writable) {
    192         if (mDatabase != null) {
    193             if (!mDatabase.isOpen()) {
    194                 // Darn!  The user closed the database by calling mDatabase.close().
    195                 mDatabase = null;
    196             } else if (!writable || !mDatabase.isReadOnly()) {
    197                 // The database is already open for business.
    198                 return mDatabase;
    199             }
    200         }
    201 
    202         if (mIsInitializing) {
    203             throw new IllegalStateException("getDatabase called recursively");
    204         }
    205 
    206         SQLiteDatabase db = mDatabase;
    207         try {
    208             mIsInitializing = true;
    209 
    210             if (db != null) {
    211                 if (writable && db.isReadOnly()) {
    212                     db.reopenReadWrite();
    213                 }
    214             } else if (mName == null) {
    215                 db = SQLiteDatabase.create(null);
    216             } else {
    217                 try {
    218                     if (DEBUG_STRICT_READONLY && !writable) {
    219                         final String path = mContext.getDatabasePath(mName).getPath();
    220                         db = SQLiteDatabase.openDatabase(path, mFactory,
    221                                 SQLiteDatabase.OPEN_READONLY, mErrorHandler);
    222                     } else {
    223                         db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
    224                                 Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
    225                                 mFactory, mErrorHandler);
    226                     }
    227                 } catch (SQLiteException ex) {
    228                     if (writable) {
    229                         throw ex;
    230                     }
    231                     Log.e(TAG, "Couldn't open " + mName
    232                             + " for writing (will try read-only):", ex);
    233                     final String path = mContext.getDatabasePath(mName).getPath();
    234                     db = SQLiteDatabase.openDatabase(path, mFactory,
    235                             SQLiteDatabase.OPEN_READONLY, mErrorHandler);
    236                 }
    237             }
    238 
    239             onConfigure(db);
    240 
    241             final int version = db.getVersion();
    242             if (version != mNewVersion) {
    243                 if (db.isReadOnly()) {
    244                     throw new SQLiteException("Can't upgrade read-only database from version " +
    245                             db.getVersion() + " to " + mNewVersion + ": " + mName);
    246                 }
    247 
    248                 db.beginTransaction();
    249                 try {
    250                     if (version == 0) {
    251                         onCreate(db);
    252                     } else {
    253                         if (version > mNewVersion) {
    254                             onDowngrade(db, version, mNewVersion);
    255                         } else {
    256                             onUpgrade(db, version, mNewVersion);
    257                         }
    258                     }
    259                     db.setVersion(mNewVersion);
    260                     db.setTransactionSuccessful();
    261                 } finally {
    262                     db.endTransaction();
    263                 }
    264             }
    265 
    266             onOpen(db);
    267 
    268             if (db.isReadOnly()) {
    269                 Log.w(TAG, "Opened " + mName + " in read-only mode");
    270             }
    271 
    272             mDatabase = db;
    273             return db;
    274         } finally {
    275             mIsInitializing = false;
    276             if (db != null && db != mDatabase) {
    277                 db.close();
    278             }
    279         }
    280     }
    281 
    282     /**
    283      * Close any open database object.
    284      */
    285     public synchronized void close() {
    286         if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
    287 
    288         if (mDatabase != null && mDatabase.isOpen()) {
    289             mDatabase.close();
    290             mDatabase = null;
    291         }
    292     }
    293 
    294     /**
    295      * Called when the database connection is being configured, to enable features
    296      * such as write-ahead logging or foreign key support.
    297      * <p>
    298      * This method is called before {@link #onCreate}, {@link #onUpgrade},
    299      * {@link #onDowngrade}, or {@link #onOpen} are called.  It should not modify
    300      * the database except to configure the database connection as required.
    301      * </p><p>
    302      * This method should only call methods that configure the parameters of the
    303      * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
    304      * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled},
    305      * {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize},
    306      * or executing PRAGMA statements.
    307      * </p>
    308      *
    309      * @param db The database.
    310      */
    311     public void onConfigure(SQLiteDatabase db) {}
    312 
    313     /**
    314      * Called when the database is created for the first time. This is where the
    315      * creation of tables and the initial population of the tables should happen.
    316      *
    317      * @param db The database.
    318      */
    319     public abstract void onCreate(SQLiteDatabase db);
    320 
    321     /**
    322      * Called when the database needs to be upgraded. The implementation
    323      * should use this method to drop tables, add tables, or do anything else it
    324      * needs to upgrade to the new schema version.
    325      *
    326      * <p>
    327      * The SQLite ALTER TABLE documentation can be found
    328      * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
    329      * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
    330      * you can use ALTER TABLE to rename the old table, then create the new table and then
    331      * populate the new table with the contents of the old table.
    332      * </p><p>
    333      * This method executes within a transaction.  If an exception is thrown, all changes
    334      * will automatically be rolled back.
    335      * </p>
    336      *
    337      * @param db The database.
    338      * @param oldVersion The old database version.
    339      * @param newVersion The new database version.
    340      */
    341     public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    342 
    343     /**
    344      * Called when the database needs to be downgraded. This is strictly similar to
    345      * {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
    346      * However, this method is not abstract, so it is not mandatory for a customer to
    347      * implement it. If not overridden, default implementation will reject downgrade and
    348      * throws SQLiteException
    349      *
    350      * <p>
    351      * This method executes within a transaction.  If an exception is thrown, all changes
    352      * will automatically be rolled back.
    353      * </p>
    354      *
    355      * @param db The database.
    356      * @param oldVersion The old database version.
    357      * @param newVersion The new database version.
    358      */
    359     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    360         throw new SQLiteException("Can't downgrade database from version " +
    361                 oldVersion + " to " + newVersion);
    362     }
    363 
    364     /**
    365      * Called when the database has been opened.  The implementation
    366      * should check {@link SQLiteDatabase#isReadOnly} before updating the
    367      * database.
    368      * <p>
    369      * This method is called after the database connection has been configured
    370      * and after the database schema has been created, upgraded or downgraded as necessary.
    371      * If the database connection must be configured in some way before the schema
    372      * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
    373      * </p>
    374      *
    375      * @param db The database.
    376      */
    377     public void onOpen(SQLiteDatabase db) {}
    378 }
    379