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 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 */ 42 public 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 */ SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version)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 */ SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, DatabaseErrorHandler errorHandler)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 */ getDatabaseName()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 */ setWriteAheadLoggingEnabled(boolean enabled)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 */ getWritableDatabase()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 */ getReadableDatabase()185 public SQLiteDatabase getReadableDatabase() { 186 synchronized (this) { 187 return getDatabaseLocked(false); 188 } 189 } 190 getDatabaseLocked(boolean writable)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 */ close()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 */ onConfigure(SQLiteDatabase db)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 */ onCreate(SQLiteDatabase db)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 */ onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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 */ onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)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 */ onOpen(SQLiteDatabase db)377 public void onOpen(SQLiteDatabase db) {} 378 } 379