Loading core/java/android/database/sqlite/SQLiteDatabase.java +22 −16 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import dalvik.system.BlockGuard; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; Loading Loading @@ -273,7 +272,7 @@ public class SQLiteDatabase extends SQLiteClosable { * invoked. * * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method * (@link setMaxSqlCacheSize(int)}). * (@link #setMaxSqlCacheSize(int)}). */ // default statement-cache size per database connection ( = instance of this class) private int mMaxSqlCacheSize = 25; Loading Loading @@ -903,8 +902,7 @@ public class SQLiteDatabase extends SQLiteClosable { public interface CursorFactory { /** * See * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver, * String, SQLiteQuery)}. * {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}. */ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, Loading Loading @@ -2080,7 +2078,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) { Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + getPath() + ". Consider increasing cachesize."); getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. "); } } /* add the given SQLiteCompiledSql compiledStatement to cache. Loading Loading @@ -2142,7 +2140,7 @@ public class SQLiteDatabase extends SQLiteClosable { * * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE}) * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or * > the value set with previous setMaxSqlCacheSize() call. * the value set with previous setMaxSqlCacheSize() call. */ public synchronized void setMaxSqlCacheSize(int cacheSize) { if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { Loading Loading @@ -2342,10 +2340,12 @@ public class SQLiteDatabase extends SQLiteClosable { /* package */ SQLiteDatabase getDbConnection(String sql) { verifyDbIsOpen(); // this method should always be called with main database connection handle // NEVER with pooled database connection handle // this method should always be called with main database connection handle. // the only time when it is called with pooled database connection handle is // corruption occurs while trying to open a pooled database connection handle. // in that case, simply return 'this' handle if (isPooledConnection()) { throw new IllegalStateException("incorrect database connection handle"); return this; } // use the current connection handle if Loading Loading @@ -2504,12 +2504,18 @@ public class SQLiteDatabase extends SQLiteClosable { */ public boolean isDatabaseIntegrityOk() { verifyDbIsOpen(); ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(); ArrayList<Pair<String, String>> attachedDbs = null; try { attachedDbs = getAttachedDbs(); if (attachedDbs == null) { throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + "be retrieved. probably because the database is closed"); } boolean isDatabaseCorrupt = false; } catch (SQLiteException e) { // can't get attachedDb list. do integrity check on the main database attachedDbs = new ArrayList<Pair<String, String>>(); attachedDbs.add(new Pair<String, String>("main", this.mPath)); } for (int i = 0; i < attachedDbs.size(); i++) { Pair<String, String> p = attachedDbs.get(i); SQLiteStatement prog = null; Loading @@ -2518,14 +2524,14 @@ public class SQLiteDatabase extends SQLiteClosable { String rslt = prog.simpleQueryForString(); if (!rslt.equalsIgnoreCase("ok")) { // integrity_checker failed on main or attached databases isDatabaseCorrupt = true; Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); return false; } } finally { if (prog != null) prog.close(); } } return isDatabaseCorrupt; return true; } /** Loading core/java/android/database/sqlite/SQLiteMisuseException.java +12 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,18 @@ package android.database.sqlite; /** * This error can occur if the application creates a SQLiteStatement object and allows multiple * threads in the application use it at the same time. * Sqlite returns this error if bind and execute methods on this object occur at the same time * from multiple threads, like so: * thread # 1: in execute() method of the SQLiteStatement object * while thread # 2: is in bind..() on the same object. *</p> * FIX this by NEVER sharing the same SQLiteStatement object between threads. * Create a local instance of the SQLiteStatement whenever it is needed, use it and close it ASAP. * NEVER make it globally available. */ public class SQLiteMisuseException extends SQLiteException { public SQLiteMisuseException() {} Loading core/jni/android_database_SQLiteQuery.cpp +4 −4 Original line number Diff line number Diff line Loading @@ -205,7 +205,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int offset = window->alloc(size); if (!offset) { window->freeLastRow(); LOGE("Failed allocating %u bytes for text/blob at %d,%d", size, LOGD("Failed allocating %u bytes for text/blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } Loading @@ -225,7 +225,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int64_t value = sqlite3_column_int64(statement, i); if (!window->putLong(numRows, i, value)) { window->freeLastRow(); LOGE("Failed allocating space for a long in column %d", i); LOGD("Failed allocating space for a long in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value); Loading @@ -234,7 +234,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, double value = sqlite3_column_double(statement, i); if (!window->putDouble(numRows, i, value)) { window->freeLastRow(); LOGE("Failed allocating space for a double in column %d", i); LOGD("Failed allocating space for a double in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value); Loading @@ -245,7 +245,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int offset = window->alloc(size); if (!offset) { window->freeLastRow(); LOGE("Failed allocating %u bytes for blob at %d,%d", size, LOGD("Failed allocating %u bytes for blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } Loading core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.test.AndroidTestCase; import android.util.Log; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class DatabaseErrorHandlerTest extends AndroidTestCase { private SQLiteDatabase mDatabase; private File mDatabaseFile; private static final String DB_NAME = "database_test.db"; private File dbDir; @Override protected void setUp() throws Exception { super.setUp(); dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); mDatabaseFile = new File(dbDir, DB_NAME); if (mDatabaseFile.exists()) { mDatabaseFile.delete(); } mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, new MyDatabaseCorruptionHandler()); assertNotNull(mDatabase); } @Override protected void tearDown() throws Exception { mDatabase.close(); mDatabaseFile.delete(); super.tearDown(); } public void testNoCorruptionCase() { new MyDatabaseCorruptionHandler().onCorruption(mDatabase); // database file should still exist assertTrue(mDatabaseFile.exists()); } public void testDatabaseIsCorrupt() throws IOException { mDatabase.execSQL("create table t (i int);"); // write junk into the database file BufferedWriter writer = new BufferedWriter(new FileWriter(mDatabaseFile.getPath())); writer.write("blah"); writer.close(); assertTrue(mDatabaseFile.exists()); // since the database file is now corrupt, doing any sql on this database connection // should trigger call to MyDatabaseCorruptionHandler.onCorruption try { mDatabase.execSQL("select * from t;"); fail("expected exception"); } catch (SQLiteException e) { // expected } // after corruption handler is called, the database file should be free of // database corruption SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, new MyDatabaseCorruptionHandler()); assertTrue(db.isDatabaseIntegrityOk()); } /** * An example implementation of {@link DatabaseErrorHandler} to demonstrate * database corruption handler which checks to make sure database is indeed * corrupt before deleting the file. */ public class MyDatabaseCorruptionHandler implements DatabaseErrorHandler { public void onCorruption(SQLiteDatabase dbObj) { boolean databaseOk = dbObj.isDatabaseIntegrityOk(); // close the database try { dbObj.close(); } catch (SQLiteException e) { /* ignore */ } if (databaseOk) { // database is just fine. no need to delete the database file Log.e("MyDatabaseCorruptionHandler", "no corruption in the database: " + mDatabaseFile.getPath()); } else { // database is corrupt. delete the database file Log.e("MyDatabaseCorruptionHandler", "deleting the database file: " + mDatabaseFile.getPath()); new File(dbDir, DB_NAME).delete(); } } } } No newline at end of file Loading
core/java/android/database/sqlite/SQLiteDatabase.java +22 −16 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import dalvik.system.BlockGuard; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; Loading Loading @@ -273,7 +272,7 @@ public class SQLiteDatabase extends SQLiteClosable { * invoked. * * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method * (@link setMaxSqlCacheSize(int)}). * (@link #setMaxSqlCacheSize(int)}). */ // default statement-cache size per database connection ( = instance of this class) private int mMaxSqlCacheSize = 25; Loading Loading @@ -903,8 +902,7 @@ public class SQLiteDatabase extends SQLiteClosable { public interface CursorFactory { /** * See * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver, * String, SQLiteQuery)}. * {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}. */ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, Loading Loading @@ -2080,7 +2078,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) { Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + getPath() + ". Consider increasing cachesize."); getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. "); } } /* add the given SQLiteCompiledSql compiledStatement to cache. Loading Loading @@ -2142,7 +2140,7 @@ public class SQLiteDatabase extends SQLiteClosable { * * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE}) * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or * > the value set with previous setMaxSqlCacheSize() call. * the value set with previous setMaxSqlCacheSize() call. */ public synchronized void setMaxSqlCacheSize(int cacheSize) { if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { Loading Loading @@ -2342,10 +2340,12 @@ public class SQLiteDatabase extends SQLiteClosable { /* package */ SQLiteDatabase getDbConnection(String sql) { verifyDbIsOpen(); // this method should always be called with main database connection handle // NEVER with pooled database connection handle // this method should always be called with main database connection handle. // the only time when it is called with pooled database connection handle is // corruption occurs while trying to open a pooled database connection handle. // in that case, simply return 'this' handle if (isPooledConnection()) { throw new IllegalStateException("incorrect database connection handle"); return this; } // use the current connection handle if Loading Loading @@ -2504,12 +2504,18 @@ public class SQLiteDatabase extends SQLiteClosable { */ public boolean isDatabaseIntegrityOk() { verifyDbIsOpen(); ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(); ArrayList<Pair<String, String>> attachedDbs = null; try { attachedDbs = getAttachedDbs(); if (attachedDbs == null) { throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + "be retrieved. probably because the database is closed"); } boolean isDatabaseCorrupt = false; } catch (SQLiteException e) { // can't get attachedDb list. do integrity check on the main database attachedDbs = new ArrayList<Pair<String, String>>(); attachedDbs.add(new Pair<String, String>("main", this.mPath)); } for (int i = 0; i < attachedDbs.size(); i++) { Pair<String, String> p = attachedDbs.get(i); SQLiteStatement prog = null; Loading @@ -2518,14 +2524,14 @@ public class SQLiteDatabase extends SQLiteClosable { String rslt = prog.simpleQueryForString(); if (!rslt.equalsIgnoreCase("ok")) { // integrity_checker failed on main or attached databases isDatabaseCorrupt = true; Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); return false; } } finally { if (prog != null) prog.close(); } } return isDatabaseCorrupt; return true; } /** Loading
core/java/android/database/sqlite/SQLiteMisuseException.java +12 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,18 @@ package android.database.sqlite; /** * This error can occur if the application creates a SQLiteStatement object and allows multiple * threads in the application use it at the same time. * Sqlite returns this error if bind and execute methods on this object occur at the same time * from multiple threads, like so: * thread # 1: in execute() method of the SQLiteStatement object * while thread # 2: is in bind..() on the same object. *</p> * FIX this by NEVER sharing the same SQLiteStatement object between threads. * Create a local instance of the SQLiteStatement whenever it is needed, use it and close it ASAP. * NEVER make it globally available. */ public class SQLiteMisuseException extends SQLiteException { public SQLiteMisuseException() {} Loading
core/jni/android_database_SQLiteQuery.cpp +4 −4 Original line number Diff line number Diff line Loading @@ -205,7 +205,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int offset = window->alloc(size); if (!offset) { window->freeLastRow(); LOGE("Failed allocating %u bytes for text/blob at %d,%d", size, LOGD("Failed allocating %u bytes for text/blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } Loading @@ -225,7 +225,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int64_t value = sqlite3_column_int64(statement, i); if (!window->putLong(numRows, i, value)) { window->freeLastRow(); LOGE("Failed allocating space for a long in column %d", i); LOGD("Failed allocating space for a long in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value); Loading @@ -234,7 +234,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, double value = sqlite3_column_double(statement, i); if (!window->putDouble(numRows, i, value)) { window->freeLastRow(); LOGE("Failed allocating space for a double in column %d", i); LOGD("Failed allocating space for a double in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value); Loading @@ -245,7 +245,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int offset = window->alloc(size); if (!offset) { window->freeLastRow(); LOGE("Failed allocating %u bytes for blob at %d,%d", size, LOGD("Failed allocating %u bytes for blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; } Loading
core/tests/coretests/src/android/database/DatabaseErrorHandlerTest.java 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.test.AndroidTestCase; import android.util.Log; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class DatabaseErrorHandlerTest extends AndroidTestCase { private SQLiteDatabase mDatabase; private File mDatabaseFile; private static final String DB_NAME = "database_test.db"; private File dbDir; @Override protected void setUp() throws Exception { super.setUp(); dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); mDatabaseFile = new File(dbDir, DB_NAME); if (mDatabaseFile.exists()) { mDatabaseFile.delete(); } mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, new MyDatabaseCorruptionHandler()); assertNotNull(mDatabase); } @Override protected void tearDown() throws Exception { mDatabase.close(); mDatabaseFile.delete(); super.tearDown(); } public void testNoCorruptionCase() { new MyDatabaseCorruptionHandler().onCorruption(mDatabase); // database file should still exist assertTrue(mDatabaseFile.exists()); } public void testDatabaseIsCorrupt() throws IOException { mDatabase.execSQL("create table t (i int);"); // write junk into the database file BufferedWriter writer = new BufferedWriter(new FileWriter(mDatabaseFile.getPath())); writer.write("blah"); writer.close(); assertTrue(mDatabaseFile.exists()); // since the database file is now corrupt, doing any sql on this database connection // should trigger call to MyDatabaseCorruptionHandler.onCorruption try { mDatabase.execSQL("select * from t;"); fail("expected exception"); } catch (SQLiteException e) { // expected } // after corruption handler is called, the database file should be free of // database corruption SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, new MyDatabaseCorruptionHandler()); assertTrue(db.isDatabaseIntegrityOk()); } /** * An example implementation of {@link DatabaseErrorHandler} to demonstrate * database corruption handler which checks to make sure database is indeed * corrupt before deleting the file. */ public class MyDatabaseCorruptionHandler implements DatabaseErrorHandler { public void onCorruption(SQLiteDatabase dbObj) { boolean databaseOk = dbObj.isDatabaseIntegrityOk(); // close the database try { dbObj.close(); } catch (SQLiteException e) { /* ignore */ } if (databaseOk) { // database is just fine. no need to delete the database file Log.e("MyDatabaseCorruptionHandler", "no corruption in the database: " + mDatabaseFile.getPath()); } else { // database is corrupt. delete the database file Log.e("MyDatabaseCorruptionHandler", "deleting the database file: " + mDatabaseFile.getPath()); new File(dbDir, DB_NAME).delete(); } } } } No newline at end of file