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