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