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
17package android.database.sqlite;
18
19import android.content.Context;
20import android.database.DatabaseErrorHandler;
21import android.database.sqlite.SQLiteDatabase.CursorFactory;
22import android.util.Log;
23import 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 */
43public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
216    public SQLiteDatabase getReadableDatabase() {
217        synchronized (this) {
218            return getDatabaseLocked(false);
219        }
220    }
221
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     */
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     */
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     */
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     */
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     */
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     */
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     */
435    public void onOpen(SQLiteDatabase db) {}
436}
437