Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc6f5491 authored by Vasu Nori's avatar Vasu Nori
Browse files

SQLiteOpenHelper should discard closed singleton database objects

bug:2943028
Change-Id: I4b6263cc25413995363158c976d3c723231cc050
parent 69d71966
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -2066,11 +2066,9 @@ public class SQLiteDatabase extends SQLiteClosable {
     * mapping is NOT replaced with the new mapping).
     */
    /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
        SQLiteCompiledSql compiledSql = null;
        synchronized(mCompiledQueries) {
            // don't insert the new mapping if a mapping already exists
            compiledSql = mCompiledQueries.get(sql);
            if (compiledSql != null) {
            if (mCompiledQueries.containsKey(sql)) {
                return;
            }

+14 −4
Original line number Diff line number Diff line
@@ -121,9 +121,14 @@ public abstract class SQLiteOpenHelper {
     * @return a read/write database object valid until {@link #close} is called
     */
    public synchronized SQLiteDatabase getWritableDatabase() {
        if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // darn! the user closed the database by calling mDatabase.close()
                mDatabase = null;
            } else if (!mDatabase.isReadOnly()) {
                return mDatabase;  // The database is already open for business
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getWritableDatabase called recursively");
@@ -207,9 +212,14 @@ public abstract class SQLiteOpenHelper {
     *     or {@link #close} is called.
     */
    public synchronized SQLiteDatabase getReadableDatabase() {
        if (mDatabase != null && mDatabase.isOpen()) {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // darn! the user closed the database by calling mDatabase.close()
                mDatabase = null;
            } else {
                return mDatabase;  // The database is already open for business
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getReadableDatabase called recursively");
+3 −3
Original line number Diff line number Diff line
@@ -158,18 +158,18 @@ public abstract class SQLiteProgram extends SQLiteClosable {

    @Override
    protected void onAllReferencesReleased() {
        releaseCompiledSqlIfNotInCache();
        release();
        mDatabase.removeSQLiteClosable(this);
        mDatabase.releaseReference();
    }

    @Override
    protected void onAllReferencesReleasedFromContainer() {
        releaseCompiledSqlIfNotInCache();
        release();
        mDatabase.releaseReference();
    }

    /* package */ synchronized void releaseCompiledSqlIfNotInCache() {
    /* package */ synchronized void release() {
        if (mCompiledSql == null) {
            return;
        }
+1 −1
Original line number Diff line number Diff line
@@ -265,7 +265,7 @@ public class SQLiteStatement extends SQLiteProgram
        clearBindings();
        // release the compiled sql statement so that the caller's SQLiteStatement no longer
        // has a hard reference to a database object that may get deallocated at any point.
        releaseCompiledSqlIfNotInCache();
        release();
        // restore the database connection handle to the original value
        mDatabase = mOrigDb;
        nHandle = mDatabase.mNativeHandle;
+40 −4
Original line number Diff line number Diff line
@@ -19,8 +19,11 @@ package android.database.sqlite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.DatabaseUtils;
import android.database.DefaultDatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteStatement;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@@ -41,6 +44,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
    private static final int INSERT = 1;
    private static final int UPDATE = 2;
    private static final int DELETE = 3;
    private static final String DB_NAME = "database_test.db";

    @Override
    protected void setUp() throws Exception {
@@ -61,7 +65,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {

    private void dbSetUp() throws Exception {
        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
        mDatabaseFile = new File(dbDir, "database_test.db");
        mDatabaseFile = new File(dbDir, DB_NAME);
        if (mDatabaseFile.exists()) {
            mDatabaseFile.delete();
        }
@@ -860,7 +864,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
                    "select count(*) from " + TEST_TABLE, null));
            // query in a different thread. but since the transaction is started using
            // execSQ() instead of beginTransaction(), cursor's query is considered part of
            // the same ransaction - and hence it should see the above inserted row
            // the same transaction - and hence it should see the above inserted row
            Thread t = new Thread() {
                @Override public void run() {
                    c1.requery();
@@ -878,11 +882,11 @@ public class SQLiteDatabaseTest extends AndroidTestCase {

    /**
     * This test is same as {@link #testTransactionAndWalInterplay2()} except the following:
     * instead of commiting the data, do rollback and make sure the data seen by the query
     * instead of committing the data, do rollback and make sure the data seen by the query
     * within the transaction is now gone.
     */
    @SmallTest
    public void testTransactionAndWalInterplay3() throws InterruptedException {
    public void testTransactionAndWalInterplay3() {
        createTableAndClearCache();
        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
        String sql = "select * from " + TEST_TABLE;
@@ -909,4 +913,36 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
                "select count(*) from " + TEST_TABLE, null));
        c.close();
    }

    /**
     * http://b/issue?id=2943028
     * SQLiteOpenHelper maintains a Singleton even if it is in bad state.
     */
    @SmallTest
    public void testCloseAndReopen() {
        mDatabase.close();
        TestOpenHelper helper = new TestOpenHelper(getContext(), DB_NAME, null,
                CURRENT_DATABASE_VERSION, new DefaultDatabaseErrorHandler());
        mDatabase = helper.getWritableDatabase();
        createTableAndClearCache();
        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
        Cursor c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
        assertEquals(1, c.getCount());
        c.close();
        mDatabase.close();
        assertFalse(mDatabase.isOpen());
        mDatabase = helper.getReadableDatabase();
        assertTrue(mDatabase.isOpen());
        c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
        assertEquals(1, c.getCount());
        c.close();
    }
    private class TestOpenHelper extends SQLiteOpenHelper {
        public TestOpenHelper(Context context, String name, CursorFactory factory, int version,
                DatabaseErrorHandler errorHandler) {
            super(context, name, factory, version, errorHandler);
        }
        @Override public void onCreate(SQLiteDatabase db) {}
        @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
    }
}