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;
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 */
42public 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