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

Commit 187c7216 authored by Vasu Nori's avatar Vasu Nori Committed by Android (Google) Code Review
Browse files

Merge "reduce locking when using SQLiteStatement"

parents 3465d5f5 e25539fd
Loading
Loading
Loading
Loading
+97 −50
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package android.database.sqlite;

import android.util.Log;
import android.util.Pair;

import java.util.ArrayList;

/**
 * A base class for compiled SQLite programs.
@@ -29,7 +32,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
    private static final String TAG = "SQLiteProgram";

    /** the type of sql statement being processed by this object */
    private static final int SELECT_STMT = 1;
    /* package */ static final int SELECT_STMT = 1;
    private static final int UPDATE_STMT = 2;
    private static final int OTHER_STMT = 3;

@@ -63,13 +66,40 @@ public abstract class SQLiteProgram extends SQLiteClosable {
    @Deprecated
    protected int nStatement = 0;

    /**
     * In the case of {@link SQLiteStatement}, this member stores the bindargs passed
     * to the following methods, instead of actually doing the binding.
     * <ul>
     *   <li>{@link #bindBlob(int, byte[])}</li>
     *   <li>{@link #bindDouble(int, double)}</li>
     *   <li>{@link #bindLong(int, long)}</li>
     *   <li>{@link #bindNull(int)}</li>
     *   <li>{@link #bindString(int, String)}</li>
     * </ul>
     * <p>
     * Each entry in the array is a Pair of
     * <ol>
     *   <li>bind arg position number</li>
     *   <li>the value to be bound to the bindarg</li>
     * </ol>
     * <p>
     * It is lazily initialized in the above bind methods
     * and it is cleared in {@link #clearBindings()} method.
     * <p>
     * It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this
     */
    private ArrayList<Pair<Integer, Object>> bindArgs = null;

    /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
        this(db, sql, true);
    }

    /* package */ SQLiteProgram(SQLiteDatabase db, String sql, boolean compileFlag) {
        mSql = sql.trim();
        attachObjectToDatabase(db);
        db.acquireReference();
        db.addSQLiteClosable(this);
        mDatabase = db;
        nHandle = db.mNativeHandle;
        if (compileFlag) {
            compileSql();
        }
@@ -120,7 +150,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
        nStatement = mCompiledSql.nStatement;
    }

    private int getSqlStatementType(String sql) {
    /* package */ int getSqlStatementType(String sql) {
        if (mSql.length() < 6) {
            return OTHER_STMT;
        }
@@ -136,46 +166,11 @@ public abstract class SQLiteProgram extends SQLiteClosable {
        return OTHER_STMT;
    }

    private synchronized void attachObjectToDatabase(SQLiteDatabase db) {
        db.acquireReference();
        db.addSQLiteClosable(this);
        mDatabase = db;
        nHandle = db.mNativeHandle;
    }

    private synchronized void detachObjectFromDatabase() {
        mDatabase.removeSQLiteClosable(this);
        mDatabase.releaseReference();
    }

    /* package */ synchronized void verifyDbAndCompileSql() {
        mDatabase.verifyDbIsOpen();
        // use pooled database connection handles for SELECT SQL statements
        SQLiteDatabase db = (getSqlStatementType(mSql) != SELECT_STMT) ? mDatabase
                : mDatabase.getDbConnection(mSql);
        if (!db.equals(mDatabase)) {
            // the database connection handle to be used is not the same as the one supplied
            // in the constructor. do some housekeeping.
            detachObjectFromDatabase();
            attachObjectToDatabase(db);
        }
        // compile the sql statement
        if (nStatement > 0) {
            // already compiled.
            return;
        }
        mDatabase.lock();
        try {
            compileSql();
        } finally {
            mDatabase.unlock();
        }
    }

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

    @Override
@@ -246,11 +241,17 @@ public abstract class SQLiteProgram extends SQLiteClosable {
     * @param index The 1-based index to the parameter to bind null to
     */
    public void bindNull(int index) {
        mDatabase.verifyDbIsOpen();
        synchronized (this) {
            verifyDbAndCompileSql();
            acquireReference();
            try {
                if (this.nStatement == 0) {
                    // since the SQL statement is not compiled, don't do the binding yet.
                    // can be done before executing the SQL statement
                    addToBindArgs(index, null);
                } else {
                    native_bind_null(index);
                }
            } finally {
                releaseReference();
            }
@@ -265,11 +266,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
     * @param value The value to bind
     */
    public void bindLong(int index, long value) {
        mDatabase.verifyDbIsOpen();
        synchronized (this) {
            verifyDbAndCompileSql();
            acquireReference();
            try {
                if (this.nStatement == 0) {
                    addToBindArgs(index, value);
                } else {
                    native_bind_long(index, value);
                }
            } finally {
                releaseReference();
            }
@@ -284,11 +289,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
     * @param value The value to bind
     */
    public void bindDouble(int index, double value) {
        mDatabase.verifyDbIsOpen();
        synchronized (this) {
            verifyDbAndCompileSql();
            acquireReference();
            try {
                if (this.nStatement == 0) {
                    addToBindArgs(index, value);
                } else {
                    native_bind_double(index, value);
                }
            } finally {
                releaseReference();
            }
@@ -306,11 +315,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
        if (value == null) {
            throw new IllegalArgumentException("the bind value at index " + index + " is null");
        }
        mDatabase.verifyDbIsOpen();
        synchronized (this) {
            verifyDbAndCompileSql();
            acquireReference();
            try {
                if (this.nStatement == 0) {
                    addToBindArgs(index, value);
                } else {
                    native_bind_string(index, value);
                }
            } finally {
                releaseReference();
            }
@@ -328,11 +341,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
        if (value == null) {
            throw new IllegalArgumentException("the bind value at index " + index + " is null");
        }
        mDatabase.verifyDbIsOpen();
        synchronized (this) {
            verifyDbAndCompileSql();
            acquireReference();
            try {
                if (this.nStatement == 0) {
                    addToBindArgs(index, value);
                } else {
                    native_bind_blob(index, value);
                }
            } finally {
                releaseReference();
            }
@@ -344,6 +361,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
     */
    public void clearBindings() {
        synchronized (this) {
            bindArgs = null;
            if (this.nStatement == 0) {
                return;
            }
@@ -362,6 +380,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
     */
    public void close() {
        synchronized (this) {
            bindArgs = null;
            if (nHandle == 0 || !mDatabase.isOpen()) {
                return;
            }
@@ -369,6 +388,34 @@ public abstract class SQLiteProgram extends SQLiteClosable {
        }
    }

    private synchronized void addToBindArgs(int index, Object value) {
        if (bindArgs == null) {
            bindArgs = new ArrayList<Pair<Integer, Object>>();
        }
        bindArgs.add(new Pair<Integer, Object>(index, value));
    }

    /* package */ synchronized void compileAndbindAllArgs() {
        assert nStatement == 0;
        compileSql();
        if (bindArgs == null) {
            return;
        }
        for (Pair<Integer, Object> p : bindArgs) {
            if (p.second == null) {
                native_bind_null(p.first);
            } else if (p.second instanceof Long) {
                native_bind_long(p.first, (Long)p.second);
            } else if (p.second instanceof Double) {
                native_bind_double(p.first, (Double)p.second);
            } else if (p.second instanceof byte[]) {
                native_bind_blob(p.first, (byte[])p.second);
            }  else {
                native_bind_string(p.first, (String)p.second);
            }
        }
    }

    /**
     * @deprecated This method is deprecated and must not be used.
     * Compiles SQL into a SQLite program.
+50 −28
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ public class SQLiteStatement extends SQLiteProgram
    private static final boolean READ = true;
    private static final boolean WRITE = false;

    private SQLiteDatabase mOrigDb;

    /**
     * Don't use SQLiteStatement constructor directly, please use
     * {@link SQLiteDatabase#compileStatement(String)}
@@ -53,6 +55,7 @@ public class SQLiteStatement extends SQLiteProgram
     *         some reason
     */
    public void execute() {
        synchronized(this) {
            long timeStart = acquireAndLock(WRITE);
            try {
                native_execute();
@@ -61,6 +64,7 @@ public class SQLiteStatement extends SQLiteProgram
                releaseAndUnlock();
            }
        }
    }

    /**
     * Execute this SQL statement and return the ID of the row inserted due to this call.
@@ -72,6 +76,7 @@ public class SQLiteStatement extends SQLiteProgram
     *         some reason
     */
    public long executeInsert() {
        synchronized(this) {
            long timeStart = acquireAndLock(WRITE);
            try {
                native_execute();
@@ -81,6 +86,7 @@ public class SQLiteStatement extends SQLiteProgram
                releaseAndUnlock();
            }
        }
    }

    /**
     * Execute a statement that returns a 1 by 1 table with a numeric value.
@@ -91,6 +97,7 @@ public class SQLiteStatement extends SQLiteProgram
     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
     */
    public long simpleQueryForLong() {
        synchronized(this) {
            long timeStart = acquireAndLock(READ);
            try {
                long retValue = native_1x1_long();
@@ -100,6 +107,7 @@ public class SQLiteStatement extends SQLiteProgram
                releaseAndUnlock();
            }
        }
    }

    /**
     * Execute a statement that returns a 1 by 1 table with a text value.
@@ -110,6 +118,7 @@ public class SQLiteStatement extends SQLiteProgram
     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
     */
    public String simpleQueryForString() {
        synchronized(this) {
            long timeStart = acquireAndLock(READ);
            try {
                String retValue = native_1x1_string();
@@ -119,12 +128,14 @@ public class SQLiteStatement extends SQLiteProgram
                releaseAndUnlock();
            }
        }
    }

    /**
     * Called before every method in this class before executing a SQL statement,
     * this method does the following:
     * <ul>
     *   <li>make sure the database is open</li>
     *   <li>get a database connection from the connection pool,if possible</li>
     *   <li>notifies {@link BlockGuard} of read/write</li>
     *   <li>get lock on the database</li>
     *   <li>acquire reference on this object</li>
@@ -135,7 +146,14 @@ public class SQLiteStatement extends SQLiteProgram
     * methods in this class.
     */
    private long acquireAndLock(boolean rwFlag) {
        verifyDbAndCompileSql();
        // use pooled database connection handles for SELECT SQL statements
        mDatabase.verifyDbIsOpen();
        SQLiteDatabase db = (getSqlStatementType(mSql) != SELECT_STMT) ? mDatabase
                : mDatabase.getDbConnection(mSql);
        // use the database connection obtained above
        mOrigDb = mDatabase;
        mDatabase = db;
        nHandle = mDatabase.mNativeHandle;
        if (rwFlag == WRITE) {
            BlockGuard.getThreadPolicy().onWriteToDisk();
        } else {
@@ -145,6 +163,7 @@ public class SQLiteStatement extends SQLiteProgram
        mDatabase.lock();
        acquireReference();
        mDatabase.closePendingStatements();
        compileAndbindAllArgs();
        return startTime;
    }

@@ -158,6 +177,9 @@ public class SQLiteStatement extends SQLiteProgram
        // 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();
        // restore the database connection handle to the original value
        mDatabase = mOrigDb;
        nHandle = mDatabase.mNativeHandle;
    }

    private final native void native_execute();
+53 −40
Original line number Diff line number Diff line
@@ -279,11 +279,24 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
        }
    }

    private static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram {
        private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) {
            super(db, sql);
        }
        private static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) {
            db.lock();
            try {
                return new ClassToTestSqlCompilationAndCaching(db, sql);
            } finally {
                db.unlock();
            }
        }
    }

    @SmallTest
    public void testLruCachingOfSqliteCompiledSqlObjs() {
        mDatabase.disableWriteAheadLogging();
        mDatabase.execSQL("CREATE TABLE test (i int, j int);");
        mDatabase.execSQL("insert into test values(1,1);");
        // set cache size
        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
        mDatabase.setMaxSqlCacheSize(N);
@@ -292,22 +305,24 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
        // insertion of (N+1)th entry, make sure 0th entry is closed
        ArrayList<Integer> stmtObjs = new ArrayList<Integer>();
        ArrayList<String> sqlStrings = new ArrayList<String>();
        SQLiteStatement stmt0 = null;
        int stmt0 = 0;
        for (int i = 0; i < N+1; i++) {
            String s = "select * from test where i = " + i + " and j = ?";
            String s = "insert into test values(" + i + ",?);";
            sqlStrings.add(s);
            SQLiteStatement c = mDatabase.compileStatement(s);
            c.bindLong(1, 1);
            stmtObjs.add(i, c.getSqlStatementId());
            ClassToTestSqlCompilationAndCaching c =
                    ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
            int n = c.getSqlStatementId();
            stmtObjs.add(i, n);
            if (i == 0) {
                // save thie SQLiteStatement obj. we want to make sure it is thrown out of
                // the cache and its handle is 0'ed.
                stmt0 = c;
                // save the statementId of this obj. we want to make sure it is thrown out of
                // the cache at the end of this test.
                stmt0 = n;
            }
            c.close();
        }
        // is 0'th entry out of the cache?
        assertEquals(0, stmt0.getSqlStatementId());
        // is 0'th entry out of the cache? it should be in the list of statementIds
        // corresponding to the pre-compiled sql statements to be finalized.
        assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
        for (int i = 1; i < N+1; i++) {
            SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
            assertNotNull(compSql);
@@ -321,11 +336,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
                "num1 INTEGER, num2 INTEGER, image BLOB);");
        final String statement = "DELETE FROM test WHERE _id=?;";
        SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
        // SQl statement is compiled only at find bind or execute call
        assertTrue(statementDoNotClose.getSqlStatementId() == 0);
        statementDoNotClose.bindLong(1, 1);
        assertTrue(statementDoNotClose.getSqlStatementId() > 0);
        int nStatement = statementDoNotClose.getSqlStatementId();
        /* do not close statementDoNotClose object.
         * That should leave it in SQLiteDatabase.mPrograms.
         * mDatabase.close() in tearDown() should release it.
@@ -340,24 +351,25 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
    public void testStatementClose() {
        mDatabase.execSQL("CREATE TABLE test (i int, j int);");
        // fill up statement cache in mDatabase\
        int N = 26;
        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
        mDatabase.setMaxSqlCacheSize(N);
        SQLiteStatement stmt;
        int stmt0Id = 0;
        for (int i = 0; i < N; i ++) {
            stmt = mDatabase.compileStatement("insert into test values(" + i + ", ?);");
            stmt.bindLong(1, 1);
            ClassToTestSqlCompilationAndCaching c =
                    ClassToTestSqlCompilationAndCaching.create(mDatabase,
                            "insert into test values(" + i + ", ?);");
            // keep track of 0th entry
            if (i == 0) {
                stmt0Id = stmt.getSqlStatementId();
                stmt0Id = c.getSqlStatementId();
            }
            stmt.executeInsert();
            stmt.close();
            c.close();
        }

        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
        SQLiteStatement stmt1 = mDatabase.compileStatement("insert into test values(100, ?);");
        stmt1.bindLong(1, 1);
        ClassToTestSqlCompilationAndCaching stmt1 =
                ClassToTestSqlCompilationAndCaching.create(mDatabase,
                        "insert into test values(100, ?);");
        stmt1.close();

        // the above close() should have queuedUp the statement for finalization
@@ -381,18 +393,18 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
        // fill up statement cache in mDatabase in a thread
        Thread t1 = new Thread() {
            @Override public void run() {
                int N = 26;
                int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
                mDatabase.setMaxSqlCacheSize(N);
                SQLiteStatement stmt;
                for (int i = 0; i < N; i ++) {
                    stmt = mDatabase.compileStatement("insert into test values(" + i + ", ?);");
                    stmt.bindLong(1,1);
                    ClassToTestSqlCompilationAndCaching c =
                        ClassToTestSqlCompilationAndCaching.create(mDatabase,
                                "insert into test values(" + i + ", ?);");
                    // keep track of 0th entry
                    if (i == 0) {
                        setStmt0Id(stmt.getSqlStatementId());
                        stmt0Id = c.getSqlStatementId();
                    }
                    stmt.executeInsert();
                    stmt.close();
                    c.close();
                }
            }
        };
@@ -404,9 +416,9 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
        // just for the heck of it, do it in a separate thread
        Thread t2 = new Thread() {
            @Override public void run() {
                SQLiteStatement stmt1 = mDatabase.compileStatement(
                ClassToTestSqlCompilationAndCaching stmt1 =
                        ClassToTestSqlCompilationAndCaching.create(mDatabase,
                                "insert into test values(100, ?);");
                stmt1.bindLong(1, 1);
                stmt1.close();
            }
        };
@@ -452,18 +464,18 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
        // fill up statement cache in mDatabase in a thread
        Thread t1 = new Thread() {
            @Override public void run() {
                int N = 26;
                int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
                mDatabase.setMaxSqlCacheSize(N);
                SQLiteStatement stmt;
                for (int i = 0; i < N; i ++) {
                    stmt = mDatabase.compileStatement("insert into test values(" + i + ", ?);");
                    stmt.bindLong(1, 1);
                    ClassToTestSqlCompilationAndCaching c =
                            ClassToTestSqlCompilationAndCaching.create(mDatabase,
                                    "insert into test values(" + i + ", ?);");
                    // keep track of 0th entry
                    if (i == 0) {
                        setStmt0Id(stmt.getSqlStatementId());
                        stmt0Id = c.getSqlStatementId();
                    }
                    stmt.executeInsert();
                    stmt.close();
                    c.close();
                }
            }
        };
@@ -475,7 +487,8 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
        // just for the heck of it, do it in a separate thread
        Thread t2 = new Thread() {
            @Override public void run() {
                SQLiteStatement stmt1 = mDatabase.compileStatement(
                ClassToTestSqlCompilationAndCaching stmt1 =
                        ClassToTestSqlCompilationAndCaching.create(mDatabase,
                                "insert into test values(100, ?);");
                stmt1.bindLong(1, 1);
                stmt1.close();
+16 −86

File changed.

Preview size limit exceeded, changes collapsed.