Home | History | Annotate | Download | only in db
      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.sqlite.db;
     18 
     19 import android.content.Context;
     20 import android.database.sqlite.SQLiteDatabase;
     21 import android.database.sqlite.SQLiteException;
     22 import android.os.Build;
     23 import android.util.Log;
     24 import android.util.Pair;
     25 
     26 import androidx.annotation.NonNull;
     27 import androidx.annotation.Nullable;
     28 import androidx.annotation.RequiresApi;
     29 
     30 import java.io.File;
     31 import java.io.IOException;
     32 import java.util.List;
     33 
     34 /**
     35  * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
     36  * Note that since that class requires overriding certain methods, support implementation
     37  * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement
     38  * the methods that should be overridden.
     39  */
     40 @SuppressWarnings("unused")
     41 public interface SupportSQLiteOpenHelper {
     42     /**
     43      * Return the name of the SQLite database being opened, as given to
     44      * the constructor.
     45      */
     46     String getDatabaseName();
     47 
     48     /**
     49      * Enables or disables the use of write-ahead logging for the database.
     50      *
     51      * Write-ahead logging cannot be used with read-only databases so the value of
     52      * this flag is ignored if the database is opened read-only.
     53      *
     54      * @param enabled True if write-ahead logging should be enabled, false if it
     55      *                should be disabled.
     56      * @see SupportSQLiteDatabase#enableWriteAheadLogging()
     57      */
     58     @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
     59     void setWriteAheadLoggingEnabled(boolean enabled);
     60 
     61     /**
     62      * Create and/or open a database that will be used for reading and writing.
     63      * The first time this is called, the database will be opened and
     64      * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be
     65      * called.
     66      *
     67      * <p>Once opened successfully, the database is cached, so you can
     68      * call this method every time you need to write to the database.
     69      * (Make sure to call {@link #close} when you no longer need the database.)
     70      * Errors such as bad permissions or a full disk may cause this method
     71      * to fail, but future attempts may succeed if the problem is fixed.</p>
     72      *
     73      * <p class="caution">Database upgrade may take a long time, you
     74      * should not call this method from the application main thread, including
     75      * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     76      *
     77      * @return a read/write database object valid until {@link #close} is called
     78      * @throws SQLiteException if the database cannot be opened for writing
     79      */
     80     SupportSQLiteDatabase getWritableDatabase();
     81 
     82     /**
     83      * Create and/or open a database.  This will be the same object returned by
     84      * {@link #getWritableDatabase} unless some problem, such as a full disk,
     85      * requires the database to be opened read-only.  In that case, a read-only
     86      * database object will be returned.  If the problem is fixed, a future call
     87      * to {@link #getWritableDatabase} may succeed, in which case the read-only
     88      * database object will be closed and the read/write object will be returned
     89      * in the future.
     90      *
     91      * <p class="caution">Like {@link #getWritableDatabase}, this method may
     92      * take a long time to return, so you should not call it from the
     93      * application main thread, including from
     94      * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     95      *
     96      * @return a database object valid until {@link #getWritableDatabase}
     97      * or {@link #close} is called.
     98      * @throws SQLiteException if the database cannot be opened
     99      */
    100     SupportSQLiteDatabase getReadableDatabase();
    101 
    102     /**
    103      * Close any open database object.
    104      */
    105     void close();
    106 
    107     /**
    108      * Handles various lifecycle events for the SQLite connection, similar to
    109      * {@link android.database.sqlite.SQLiteOpenHelper}.
    110      */
    111     @SuppressWarnings({"unused", "WeakerAccess"})
    112     abstract class Callback {
    113         private static final String TAG = "SupportSQLite";
    114         /**
    115          * Version number of the database (starting at 1); if the database is older,
    116          * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
    117          * will be used to upgrade the database; if the database is newer,
    118          * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
    119          * will be used to downgrade the database.
    120          */
    121         public final int version;
    122 
    123         /**
    124          * Creates a new Callback to get database lifecycle events.
    125          * @param version The version for the database instance. See {@link #version}.
    126          */
    127         public Callback(int version) {
    128             this.version = version;
    129         }
    130 
    131         /**
    132          * Called when the database connection is being configured, to enable features such as
    133          * write-ahead logging or foreign key support.
    134          * <p>
    135          * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade},
    136          * or {@link #onOpen} are called. It should not modify the database except to configure the
    137          * database connection as required.
    138          * </p>
    139          * <p>
    140          * This method should only call methods that configure the parameters of the database
    141          * connection, such as {@link SupportSQLiteDatabase#enableWriteAheadLogging}
    142          * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled},
    143          * {@link SupportSQLiteDatabase#setLocale},
    144          * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
    145          * </p>
    146          *
    147          * @param db The database.
    148          */
    149         public void onConfigure(SupportSQLiteDatabase db) {
    150 
    151         }
    152 
    153         /**
    154          * Called when the database is created for the first time. This is where the
    155          * creation of tables and the initial population of the tables should happen.
    156          *
    157          * @param db The database.
    158          */
    159         public abstract void onCreate(SupportSQLiteDatabase db);
    160 
    161         /**
    162          * Called when the database needs to be upgraded. The implementation
    163          * should use this method to drop tables, add tables, or do anything else it
    164          * needs to upgrade to the new schema version.
    165          *
    166          * <p>
    167          * The SQLite ALTER TABLE documentation can be found
    168          * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
    169          * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
    170          * you can use ALTER TABLE to rename the old table, then create the new table and then
    171          * populate the new table with the contents of the old table.
    172          * </p><p>
    173          * This method executes within a transaction.  If an exception is thrown, all changes
    174          * will automatically be rolled back.
    175          * </p>
    176          *
    177          * @param db         The database.
    178          * @param oldVersion The old database version.
    179          * @param newVersion The new database version.
    180          */
    181         public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);
    182 
    183         /**
    184          * Called when the database needs to be downgraded. This is strictly similar to
    185          * {@link #onUpgrade} method, but is called whenever current version is newer than requested
    186          * one.
    187          * However, this method is not abstract, so it is not mandatory for a customer to
    188          * implement it. If not overridden, default implementation will reject downgrade and
    189          * throws SQLiteException
    190          *
    191          * <p>
    192          * This method executes within a transaction.  If an exception is thrown, all changes
    193          * will automatically be rolled back.
    194          * </p>
    195          *
    196          * @param db         The database.
    197          * @param oldVersion The old database version.
    198          * @param newVersion The new database version.
    199          */
    200         public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
    201             throw new SQLiteException("Can't downgrade database from version "
    202                     + oldVersion + " to " + newVersion);
    203         }
    204 
    205         /**
    206          * Called when the database has been opened.  The implementation
    207          * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the
    208          * database.
    209          * <p>
    210          * This method is called after the database connection has been configured
    211          * and after the database schema has been created, upgraded or downgraded as necessary.
    212          * If the database connection must be configured in some way before the schema
    213          * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
    214          * </p>
    215          *
    216          * @param db The database.
    217          */
    218         public void onOpen(SupportSQLiteDatabase db) {
    219 
    220         }
    221 
    222         /**
    223          * The method invoked when database corruption is detected. Default implementation will
    224          * delete the database file.
    225          *
    226          * @param db the {@link SupportSQLiteDatabase} object representing the database on which
    227          *           corruption is detected.
    228          */
    229         public void onCorruption(SupportSQLiteDatabase db) {
    230             // the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
    231 
    232             Log.e(TAG, "Corruption reported by sqlite on database: " + db.getPath());
    233             // is the corruption detected even before database could be 'opened'?
    234             if (!db.isOpen()) {
    235                 // database files are not even openable. delete this database file.
    236                 // NOTE if the database has attached databases, then any of them could be corrupt.
    237                 // and not deleting all of them could cause corrupted database file to remain and
    238                 // make the application crash on database open operation. To avoid this problem,
    239                 // the application should provide its own {@link DatabaseErrorHandler} impl class
    240                 // to delete ALL files of the database (including the attached databases).
    241                 deleteDatabaseFile(db.getPath());
    242                 return;
    243             }
    244 
    245             List<Pair<String, String>> attachedDbs = null;
    246             try {
    247                 // Close the database, which will cause subsequent operations to fail.
    248                 // before that, get the attached database list first.
    249                 try {
    250                     attachedDbs = db.getAttachedDbs();
    251                 } catch (SQLiteException e) {
    252                 /* ignore */
    253                 }
    254                 try {
    255                     db.close();
    256                 } catch (IOException e) {
    257                 /* ignore */
    258                 }
    259             } finally {
    260                 // Delete all files of this corrupt database and/or attached databases
    261                 if (attachedDbs != null) {
    262                     for (Pair<String, String> p : attachedDbs) {
    263                         deleteDatabaseFile(p.second);
    264                     }
    265                 } else {
    266                     // attachedDbs = null is possible when the database is so corrupt that even
    267                     // "PRAGMA database_list;" also fails. delete the main database file
    268                     deleteDatabaseFile(db.getPath());
    269                 }
    270             }
    271         }
    272 
    273         private void deleteDatabaseFile(String fileName) {
    274             if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
    275                 return;
    276             }
    277             Log.w(TAG, "deleting the database file: " + fileName);
    278             try {
    279                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    280                     SQLiteDatabase.deleteDatabase(new File(fileName));
    281                 } else {
    282                     try {
    283                         final boolean deleted = new File(fileName).delete();
    284                         if (!deleted) {
    285                             Log.e(TAG, "Could not delete the database file " + fileName);
    286                         }
    287                     } catch (Exception error) {
    288                         Log.e(TAG, "error while deleting corrupted database file", error);
    289                     }
    290                 }
    291             } catch (Exception e) {
    292             /* print warning and ignore exception */
    293                 Log.w(TAG, "delete failed: ", e);
    294             }
    295         }
    296     }
    297 
    298     /**
    299      * The configuration to create an SQLite open helper object using {@link Factory}.
    300      */
    301     @SuppressWarnings("WeakerAccess")
    302     class Configuration {
    303         /**
    304          * Context to use to open or create the database.
    305          */
    306         @NonNull
    307         public final Context context;
    308         /**
    309          * Name of the database file, or null for an in-memory database.
    310          */
    311         @Nullable
    312         public final String name;
    313         /**
    314          * The callback class to handle creation, upgrade and downgrade.
    315          */
    316         @NonNull
    317         public final SupportSQLiteOpenHelper.Callback callback;
    318 
    319         Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
    320             this.context = context;
    321             this.name = name;
    322             this.callback = callback;
    323         }
    324 
    325         /**
    326          * Creates a new Configuration.Builder to create an instance of Configuration.
    327          *
    328          * @param context to use to open or create the database.
    329          */
    330         public static Builder builder(Context context) {
    331             return new Builder(context);
    332         }
    333 
    334         /**
    335          * Builder class for {@link Configuration}.
    336          */
    337         public static class Builder {
    338             Context mContext;
    339             String mName;
    340             SupportSQLiteOpenHelper.Callback mCallback;
    341 
    342             public Configuration build() {
    343                 if (mCallback == null) {
    344                     throw new IllegalArgumentException("Must set a callback to create the"
    345                             + " configuration.");
    346                 }
    347                 if (mContext == null) {
    348                     throw new IllegalArgumentException("Must set a non-null context to create"
    349                             + " the configuration.");
    350                 }
    351                 return new Configuration(mContext, mName, mCallback);
    352             }
    353 
    354             Builder(@NonNull Context context) {
    355                 mContext = context;
    356             }
    357 
    358             /**
    359              * @param name Name of the database file, or null for an in-memory database.
    360              * @return This
    361              */
    362             public Builder name(@Nullable String name) {
    363                 mName = name;
    364                 return this;
    365             }
    366 
    367             /**
    368              * @param callback The callback class to handle creation, upgrade and downgrade.
    369              * @return this
    370              */
    371             public Builder callback(@NonNull Callback callback) {
    372                 mCallback = callback;
    373                 return this;
    374             }
    375         }
    376     }
    377 
    378     /**
    379      * Factory class to create instances of {@link SupportSQLiteOpenHelper} using
    380      * {@link Configuration}.
    381      */
    382     interface Factory {
    383         /**
    384          * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration.
    385          *
    386          * @param configuration The configuration to use while creating the open helper.
    387          *
    388          * @return A SupportSQLiteOpenHelper which can be used to open a database.
    389          */
    390         SupportSQLiteOpenHelper create(Configuration configuration);
    391     }
    392 }
    393