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