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