Merge "reduce locking when using SQLiteStatement"
This commit is contained in:
@@ -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 {
|
||||
native_bind_null(index);
|
||||
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 {
|
||||
native_bind_long(index, value);
|
||||
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 {
|
||||
native_bind_double(index, value);
|
||||
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 {
|
||||
native_bind_string(index, value);
|
||||
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 {
|
||||
native_bind_blob(index, value);
|
||||
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.
|
||||
|
||||
@@ -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,12 +55,14 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* some reason
|
||||
*/
|
||||
public void execute() {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +76,15 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* some reason
|
||||
*/
|
||||
public long executeInsert() {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(WRITE);
|
||||
try {
|
||||
native_execute();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +97,15 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
|
||||
*/
|
||||
public long simpleQueryForLong() {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
long retValue = native_1x1_long();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
long retValue = native_1x1_long();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,13 +118,15 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
|
||||
*/
|
||||
public String simpleQueryForString() {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
String retValue = native_1x1_string();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
synchronized(this) {
|
||||
long timeStart = acquireAndLock(READ);
|
||||
try {
|
||||
String retValue = native_1x1_string();
|
||||
mDatabase.logTimeStat(mSql, timeStart);
|
||||
return retValue;
|
||||
} finally {
|
||||
releaseAndUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +135,7 @@ public class SQLiteStatement extends SQLiteProgram
|
||||
* 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();
|
||||
|
||||
@@ -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(
|
||||
"insert into test values(100, ?);");
|
||||
stmt1.bindLong(1, 1);
|
||||
ClassToTestSqlCompilationAndCaching stmt1 =
|
||||
ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"insert into test values(100, ?);");
|
||||
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,8 +487,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(
|
||||
"insert into test values(100, ?);");
|
||||
ClassToTestSqlCompilationAndCaching stmt1 =
|
||||
ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"insert into test values(100, ?);");
|
||||
stmt1.bindLong(1, 1);
|
||||
stmt1.close();
|
||||
}
|
||||
|
||||
@@ -158,14 +158,16 @@ public class SQLiteStatementTest extends AndroidTestCase {
|
||||
assertEquals(0, stmt.getSqlStatementId());
|
||||
int colValue = new Random().nextInt();
|
||||
stmt.bindLong(1, colValue);
|
||||
// verify that the sql statement is now compiled
|
||||
int n = stmt.nStatement;
|
||||
assertTrue(n > 0);
|
||||
assertEquals(n, stmt.getSqlStatementId());
|
||||
// verify that the sql statement is still not compiled
|
||||
assertEquals(0, stmt.getSqlStatementId());
|
||||
// should still be using the mDatabase connection - verify
|
||||
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(mDatabase, stmt.mDatabase);
|
||||
stmt.bindString(2, "blah" + colValue);
|
||||
assertEquals(n, stmt.nStatement);
|
||||
// verify that the sql statement is still not compiled
|
||||
assertEquals(0, stmt.getSqlStatementId());
|
||||
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(mDatabase, stmt.mDatabase);
|
||||
stmt.executeInsert();
|
||||
// now that the statement is executed, pre-compiled statement should be released
|
||||
assertEquals(0, stmt.nStatement);
|
||||
@@ -187,97 +189,25 @@ public class SQLiteStatementTest extends AndroidTestCase {
|
||||
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(mDatabase, stmt.mDatabase);
|
||||
stmt.bindString(1, "blah" + colValue);
|
||||
// verify that the sql statement is now compiled
|
||||
n = stmt.nStatement;
|
||||
assertTrue(n > 0);
|
||||
assertEquals(n, stmt.getSqlStatementId());
|
||||
SQLiteDatabase dbUsed = mDatabase;
|
||||
if (wal) {
|
||||
// if wal is set, should be using a pooled connection handle
|
||||
dbUsed = mDatabase.mConnectionPool.getConnectionList().get(0);
|
||||
assertTrue(mDatabase.mNativeHandle != dbUsed.mNativeHandle);
|
||||
}
|
||||
assertEquals(dbUsed.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(dbUsed, stmt.mDatabase);
|
||||
// verify that the sql statement is still not compiled
|
||||
assertEquals(0, stmt.nStatement);
|
||||
assertEquals(0, stmt.getSqlStatementId());
|
||||
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(mDatabase, stmt.mDatabase);
|
||||
// execute the statement
|
||||
Long l = stmt.simpleQueryForLong();
|
||||
assertEquals(colValue, l.intValue());
|
||||
// now that the statement is executed, pre-compiled statement should be released
|
||||
assertEquals(0, stmt.nStatement);
|
||||
assertEquals(0, stmt.getSqlStatementId());
|
||||
// but the database handle should still remain attached to the statement
|
||||
assertEquals(dbUsed.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(dbUsed, stmt.mDatabase);
|
||||
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(mDatabase, stmt.mDatabase);
|
||||
stmt.close();
|
||||
// pre-compiled SQL statement should still remain released from this object
|
||||
assertEquals(0, stmt.nStatement);
|
||||
assertEquals(0, stmt.getSqlStatementId());
|
||||
// but the database handle should still remain attached to the statement
|
||||
assertEquals(dbUsed, stmt.mDatabase);
|
||||
}
|
||||
|
||||
/**
|
||||
* test to make sure SqliteStatement.nStatement is populated only during bind and execute calls.
|
||||
*/
|
||||
public void testGetSqlStatementId() {
|
||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
|
||||
"num1 INTEGER, num2 INTEGER, image BLOB);");
|
||||
final String statement = "DELETE FROM test WHERE _id=?;";
|
||||
SQLiteStatement statementOne = mDatabase.compileStatement(statement);
|
||||
// sql statement is NOT compiled until the bind or execute call.
|
||||
statementOne.bindLong(1, 1);
|
||||
SQLiteStatement statementTwo = mDatabase.compileStatement(statement);
|
||||
statementTwo.bindLong(1, 1);
|
||||
// since the same compiled statement is being accessed at the same time by 2 different
|
||||
// objects, they each get their own statement id
|
||||
assertTrue(statementOne.getSqlStatementId() != statementTwo.getSqlStatementId());
|
||||
statementOne.close();
|
||||
statementTwo.close();
|
||||
|
||||
// two SQLiteStatements referring to the same SQL statement should refer to the same
|
||||
// pre-compiled SQl statement id if the SQLiteStatement objects are NOT in use at the same
|
||||
// time
|
||||
statementOne = mDatabase.compileStatement(statement);
|
||||
statementOne.bindLong(1, 1);
|
||||
// now that the SQL statement is compiled, get its pre-compiled SQL statement id
|
||||
int n = statementOne.getSqlStatementId();
|
||||
statementOne.close();
|
||||
statementTwo = mDatabase.compileStatement(statement);
|
||||
statementTwo.bindLong(1, 2); // use different value for bindarg, just for the heck of it
|
||||
assertEquals(n, statementTwo.getSqlStatementId());
|
||||
statementTwo.close();
|
||||
|
||||
// now try to compile 2 different statements and they should have different uniquerIds.
|
||||
SQLiteStatement statement1 = mDatabase.compileStatement("DELETE FROM test WHERE _id > ?;");
|
||||
statement1.bindLong(1, 1);
|
||||
SQLiteStatement statement2 = mDatabase.compileStatement("DELETE FROM test WHERE _id < ?;");
|
||||
statement2.bindLong(1, 11);
|
||||
assertTrue(statement1.getSqlStatementId() != statement2.getSqlStatementId());
|
||||
statement1.close();
|
||||
statement2.close();
|
||||
}
|
||||
|
||||
public void testOnAllReferencesReleased() {
|
||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
|
||||
"num1 INTEGER, num2 INTEGER, image BLOB);");
|
||||
final String statement = "DELETE FROM test WHERE _id=?;";
|
||||
SQLiteStatement statementOne = mDatabase.compileStatement(statement);
|
||||
statementOne.bindLong(1, 1);
|
||||
assertTrue(statementOne.getSqlStatementId() > 0);
|
||||
statementOne.releaseReference();
|
||||
assertEquals(0, statementOne.getSqlStatementId());
|
||||
statementOne.close();
|
||||
}
|
||||
|
||||
public void testOnAllReferencesReleasedFromContainer() {
|
||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
|
||||
"num1 INTEGER, num2 INTEGER, image BLOB);");
|
||||
final String statement = "DELETE FROM test WHERE _id=?;";
|
||||
SQLiteStatement statementOne = mDatabase.compileStatement(statement);
|
||||
statementOne.bindLong(1, 1);
|
||||
assertTrue(statementOne.getSqlStatementId() > 0);
|
||||
statementOne.releaseReferenceFromContainer();
|
||||
assertEquals(0, statementOne.getSqlStatementId());
|
||||
statementOne.close();
|
||||
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
|
||||
assertEquals(mDatabase, stmt.mDatabase);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user