Merge "for WAL to work, can't keep prepared SQL stmt_id in SQLiteStatement"

This commit is contained in:
Vasu Nori
2010-07-07 15:35:26 -07:00
committed by Android (Google) Code Review
5 changed files with 244 additions and 124 deletions

View File

@@ -343,25 +343,14 @@ public class SQLiteDatabase extends SQLiteClosable {
private static final String MEMORY_DB_PATH = ":memory:";
/**
* @param closable
*/
void addSQLiteClosable(SQLiteClosable closable) {
lock();
try {
mPrograms.put(closable, null);
} finally {
unlock();
}
synchronized void addSQLiteClosable(SQLiteClosable closable) {
// mPrograms is per instance of SQLiteDatabase and it doesn't actually touch the database
// itself. so, there is no need to lock().
mPrograms.put(closable, null);
}
void removeSQLiteClosable(SQLiteClosable closable) {
lock();
try {
mPrograms.remove(closable);
} finally {
unlock();
}
synchronized void removeSQLiteClosable(SQLiteClosable closable) {
mPrograms.remove(closable);
}
@Override
@@ -1261,6 +1250,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* statement and fill in those values with {@link SQLiteProgram#bindString}
* and {@link SQLiteProgram#bindLong} each time you want to run the
* statement. Statements may not return result sets larger than 1x1.
*<p>
* No two threads should be using the same {@link SQLiteStatement} at the same time.
*
* @param sql The raw SQL statement, may contain ? for unknown values to be
* bound later.
@@ -1269,19 +1260,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public SQLiteStatement compileStatement(String sql) throws SQLException {
verifyDbIsOpen();
String prefixSql = sql.trim().substring(0, 6);
SQLiteDatabase db = this;
// get a pooled database connection handle to use, if this is a query
if (prefixSql.equalsIgnoreCase("SELECT")) {
db = getDbConnection(sql);
}
db.lock();
try {
return new SQLiteStatement(db, sql);
} finally {
releaseDbConnection(db);
db.unlock();
}
return new SQLiteStatement(this, sql);
}
/**
@@ -2354,7 +2333,10 @@ public class SQLiteDatabase extends SQLiteClosable {
return true;
}
private synchronized void disableWriteAheadLogging() {
/**
* package visibility only for testing purposes
*/
/* package */ synchronized void disableWriteAheadLogging() {
if (mConnectionPool == null) {
return;
}
@@ -2394,7 +2376,7 @@ public class SQLiteDatabase extends SQLiteClosable {
return this.mConnectionNum > 0;
}
private SQLiteDatabase getDbConnection(String sql) {
/* package */ SQLiteDatabase getDbConnection(String sql) {
verifyDbIsOpen();
// use the current connection handle if

View File

@@ -64,10 +64,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
protected int nStatement = 0;
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
mDatabase = db;
this(db, sql, true);
}
/* package */ SQLiteProgram(SQLiteDatabase db, String sql, boolean compileFlag) {
mSql = sql.trim();
attachObjectToDatabase(db);
compileSql();
if (compileFlag) {
compileSql();
}
}
private void compileSql() {
@@ -139,6 +144,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
private synchronized void attachObjectToDatabase(SQLiteDatabase db) {
db.acquireReference();
db.addSQLiteClosable(this);
mDatabase = db;
nHandle = db.mNativeHandle;
}
@@ -147,6 +153,26 @@ public abstract class SQLiteProgram extends SQLiteClosable {
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
mDatabase.lock();
try {
compileSql();
} finally {
mDatabase.unlock();
}
}
@Override
protected void onAllReferencesReleased() {
releaseCompiledSqlIfNotInCache();
@@ -159,7 +185,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
mDatabase.releaseReference();
}
private void releaseCompiledSqlIfNotInCache() {
/* package */ synchronized void releaseCompiledSqlIfNotInCache() {
if (mCompiledSql == null) {
return;
}
@@ -222,7 +248,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
public void bindNull(int index) {
synchronized (this) {
mDatabase.verifyDbIsOpen();
verifyDbAndCompileSql();
acquireReference();
try {
native_bind_null(index);
@@ -241,7 +267,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
public void bindLong(int index, long value) {
synchronized (this) {
mDatabase.verifyDbIsOpen();
verifyDbAndCompileSql();
acquireReference();
try {
native_bind_long(index, value);
@@ -260,7 +286,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
public void bindDouble(int index, double value) {
synchronized (this) {
mDatabase.verifyDbIsOpen();
verifyDbAndCompileSql();
acquireReference();
try {
native_bind_double(index, value);
@@ -282,7 +308,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
synchronized (this) {
mDatabase.verifyDbIsOpen();
verifyDbAndCompileSql();
acquireReference();
try {
native_bind_string(index, value);
@@ -304,7 +330,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
synchronized (this) {
mDatabase.verifyDbIsOpen();
verifyDbAndCompileSql();
acquireReference();
try {
native_bind_blob(index, value);
@@ -319,6 +345,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
public void clearBindings() {
synchronized (this) {
if (this.nStatement == 0) {
return;
}
mDatabase.verifyDbIsOpen();
acquireReference();
try {
@@ -334,14 +363,10 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
public void close() {
synchronized (this) {
if (nStatement == 0 || nHandle == 0 || !mDatabase.isOpen()) {
if (nHandle == 0 || !mDatabase.isOpen()) {
return;
}
releaseReference();
// set all database objects to null/0, so that the user can't use a closed Object.
mCompiledSql = null;
nStatement = 0;
nHandle = 0;
}
}
@@ -366,6 +391,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
protected final native void native_bind_double(int index, double value);
protected final native void native_bind_string(int index, String value);
protected final native void native_bind_blob(int index, byte[] value);
private final native void native_clear_bindings();
/* package */ final native void native_clear_bindings();
}

View File

@@ -29,6 +29,7 @@ import dalvik.system.BlockGuard;
* SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple
* threads should perform its own synchronization when using the SQLiteStatement.
*/
@SuppressWarnings("deprecation")
public class SQLiteStatement extends SQLiteProgram
{
private static final boolean READ = true;
@@ -41,7 +42,7 @@ public class SQLiteStatement extends SQLiteProgram
* @param sql
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
super(db, sql, false /* don't compile sql statement */);
}
/**
@@ -134,7 +135,7 @@ public class SQLiteStatement extends SQLiteProgram
* methods in this class.
*/
private long acquireAndLock(boolean rwFlag) {
mDatabase.verifyDbIsOpen();
verifyDbAndCompileSql();
if (rwFlag == WRITE) {
BlockGuard.getThreadPolicy().onWriteToDisk();
} else {
@@ -153,6 +154,10 @@ public class SQLiteStatement extends SQLiteProgram
private void releaseAndUnlock() {
releaseReference();
mDatabase.unlock();
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();
}
private final native void native_execute();

View File

@@ -67,6 +67,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
@SmallTest
public void testEnableWriteAheadLogging() {
mDatabase.disableWriteAheadLogging();
assertNull(mDatabase.mConnectionPool);
mDatabase.enableWriteAheadLogging();
DatabaseConnectionPool pool = mDatabase.mConnectionPool;
@@ -280,6 +281,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
@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
@@ -292,9 +294,10 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
ArrayList<String> sqlStrings = new ArrayList<String>();
SQLiteStatement stmt0 = null;
for (int i = 0; i < N+1; i++) {
String s = "select * from test where i = " + i;
String s = "select * from test where i = " + i + " and j = ?";
sqlStrings.add(s);
SQLiteStatement c = mDatabase.compileStatement(s);
c.bindLong(1, 1);
stmtObjs.add(i, c.getSqlStatementId());
if (i == 0) {
// save thie SQLiteStatement obj. we want to make sure it is thrown out of
@@ -307,6 +310,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
assertEquals(0, stmt0.getSqlStatementId());
for (int i = 1; i < N+1; i++) {
SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
assertNotNull(compSql);
assertTrue(stmtObjs.contains(compSql.nStatement));
}
}
@@ -317,9 +321,11 @@ 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();
assertTrue(statementDoNotClose.getSqlStatementId() == nStatement);
/* do not close statementDoNotClose object.
* That should leave it in SQLiteDatabase.mPrograms.
* mDatabase.close() in tearDown() should release it.
@@ -332,24 +338,26 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
*/
@SmallTest
public void testStatementClose() {
mDatabase.execSQL("CREATE TABLE test (i int);");
mDatabase.execSQL("CREATE TABLE test (i int, j int);");
// fill up statement cache in mDatabase\
int N = 26;
mDatabase.setMaxSqlCacheSize(N);
SQLiteStatement stmt;
int stmt0Id = 0;
for (int i = 0; i < N; i ++) {
stmt = mDatabase.compileStatement("insert into test values(" + i + ");");
stmt.executeInsert();
stmt = mDatabase.compileStatement("insert into test values(" + i + ", ?);");
stmt.bindLong(1, 1);
// keep track of 0th entry
if (i == 0) {
stmt0Id = stmt.getSqlStatementId();
}
stmt.executeInsert();
stmt.close();
}
// add one more to the cache - and the above 'stmt0Id' should fall out of cache
SQLiteStatement stmt1 = mDatabase.compileStatement("select * from test where i = 1;");
SQLiteStatement stmt1 = mDatabase.compileStatement("select * from test where i = ?;");
stmt1.bindLong(1, 1);
stmt1.close();
// the above close() should have queuedUp the statement for finalization
@@ -369,7 +377,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
*/
@LargeTest
public void testStatementCloseDiffThread() throws InterruptedException {
mDatabase.execSQL("CREATE TABLE test (i int);");
mDatabase.execSQL("CREATE TABLE test (i int, j int);");
// fill up statement cache in mDatabase in a thread
Thread t1 = new Thread() {
@Override public void run() {
@@ -377,12 +385,13 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
mDatabase.setMaxSqlCacheSize(N);
SQLiteStatement stmt;
for (int i = 0; i < N; i ++) {
stmt = mDatabase.compileStatement("insert into test values(" + i + ");");
stmt.executeInsert();
stmt = mDatabase.compileStatement("insert into test values(" + i + ", ?);");
stmt.bindLong(1,1);
// keep track of 0th entry
if (i == 0) {
setStmt0Id(stmt.getSqlStatementId());
}
stmt.executeInsert();
stmt.close();
}
}
@@ -396,7 +405,8 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
Thread t2 = new Thread() {
@Override public void run() {
SQLiteStatement stmt1 = mDatabase.compileStatement(
"select * from test where i = 1;");
"select * from test where i = ?;");
stmt1.bindLong(1, 1);
stmt1.close();
}
};
@@ -438,7 +448,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
*/
@LargeTest
public void testStatementCloseByDbClose() throws InterruptedException {
mDatabase.execSQL("CREATE TABLE test (i int);");
mDatabase.execSQL("CREATE TABLE test (i int, j int);");
// fill up statement cache in mDatabase in a thread
Thread t1 = new Thread() {
@Override public void run() {
@@ -446,12 +456,13 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
mDatabase.setMaxSqlCacheSize(N);
SQLiteStatement stmt;
for (int i = 0; i < N; i ++) {
stmt = mDatabase.compileStatement("insert into test values(" + i + ");");
stmt.executeInsert();
stmt = mDatabase.compileStatement("insert into test values(" + i + ", ?);");
stmt.bindLong(1, 1);
// keep track of 0th entry
if (i == 0) {
setStmt0Id(stmt.getSqlStatementId());
}
stmt.executeInsert();
stmt.close();
}
}
@@ -465,7 +476,8 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
Thread t2 = new Thread() {
@Override public void run() {
SQLiteStatement stmt1 = mDatabase.compileStatement(
"select * from test where i = 1;");
"select * from test where i = ?;");
stmt1.bindLong(1, 1);
stmt1.close();
}
};

View File

@@ -18,21 +18,21 @@ package android.database.sqlite;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.FlakyTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.File;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
public class SQLiteStatementTest extends AndroidTestCase {
private SQLiteDatabase mDatabase;
private File mDatabaseFile;
Boolean exceptionRecvd = false;
@Override
protected void setUp() throws Exception {
super.setUp();
exceptionRecvd = false;
File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
@@ -49,78 +49,60 @@ public class SQLiteStatementTest extends AndroidTestCase {
super.tearDown();
}
/**
* Start 2 threads to repeatedly execute the above SQL statement.
* Even though 2 threads are executing the same SQL, they each should get their own copy of
* prepared SQL statement id and there SHOULD NOT be an error from sqlite or android.
* @throws InterruptedException thrown if the test threads started by this test are interrupted
*/
@LargeTest
public void testUseOfSameSqlStatementBy2Threads() throws Exception {
public void testUseOfSameSqlStatementBy2Threads() throws InterruptedException {
mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
// thread 1 creates a prepared statement
final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
// start 2 threads to do repeatedly execute "stmt"
// since these 2 threads are executing the same sql, they each should get
// their own copy and
// there SHOULD NOT be an error from sqlite: "prepared statement is busy"
class RunStmtThread extends Thread {
private static final int N = 1000;
@Override public void run() {
int i = 0;
try {
// execute many times
for (i = 0; i < N; i++) {
SQLiteStatement s1 = mDatabase.compileStatement(stmt);
s1.bindLong(1, i);
s1.execute();
s1.close();
}
} catch (SQLiteException e) {
fail("SQLiteException: " + e.getMessage());
return;
} catch (Exception e) {
e.printStackTrace();
fail("random unexpected exception: " + e.getMessage());
return;
}
// do it enough times to make sure there are no corner cases going untested
for (int i = 0; i < 1000; i++) {
SQLiteStatement s1 = mDatabase.compileStatement(stmt);
s1.bindLong(1, i);
s1.execute();
s1.close();
}
}
}
RunStmtThread t1 = new RunStmtThread();
t1.start();
RunStmtThread t2 = new RunStmtThread();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
Thread.sleep(1000);
}
}
while (t1.isAlive() || t2.isAlive()) {
Thread.sleep(10);
}
}
@FlakyTest
public void testUseOfSamePreparedStatementBy2Threads() throws Exception {
/**
* A simple test: start 2 threads to repeatedly execute the same {@link SQLiteStatement}.
* The 2 threads take turns to use the {@link SQLiteStatement}; i.e., it is NOT in use
* by both the threads at the same time.
*
* @throws InterruptedException thrown if the test threads started by this test are interrupted
*/
@LargeTest
public void testUseOfSameSqliteStatementBy2Threads() throws InterruptedException {
mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
// thread 1 creates a prepared statement
final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
// start 2 threads to do repeatedly execute "stmt"
// since these 2 threads are executing the same prepared statement,
// should see an error from sqlite: "prepared statement is busy"
class RunStmtThread extends Thread {
private static final int N = 1000;
@Override public void run() {
int i = 0;
try {
// execute many times
for (i = 0; i < N; i++) {
// do it enough times to make sure there are no corner cases going untested
for (int i = 0; i < 1000; i++) {
lock();
try {
s1.bindLong(1, i);
s1.execute();
} finally {
unlock();
}
} catch (SQLiteException e) {
// expect it
assertTrue(e.getMessage().contains("library routine called out of sequence:"));
exceptionRecvd = true;
return;
} catch (Exception e) {
e.printStackTrace();
fail("random unexpected exception: " + e.getMessage());
return;
Thread.yield();
}
}
}
@@ -129,33 +111,147 @@ public class SQLiteStatementTest extends AndroidTestCase {
RunStmtThread t2 = new RunStmtThread();
t2.start();
while (t1.isAlive() || t2.isAlive()) {
Thread.sleep(1000);
Thread.sleep(10);
}
assertTrue(exceptionRecvd);
}
/** Synchronize on this when accessing the SqliteStatemet in the above */
private final ReentrantLock mLock = new ReentrantLock(true);
private void lock() {
mLock.lock();
}
private void unlock() {
mLock.unlock();
}
/**
* Tests the following: a {@link SQLiteStatement} object should not refer to a
* pre-compiled SQL statement id except in during the period of binding the arguments
* and executing the SQL statement.
*/
@SmallTest
public void testReferenceToPrecompiledStatementId() {
mDatabase.execSQL("create table t (i int, j text);");
verifyReferenceToPrecompiledStatementId(false);
verifyReferenceToPrecompiledStatementId(true);
// a small stress test to make sure there are no side effects of
// the acquire & release of pre-compiled statement id by SQLiteStatement object.
for (int i = 0; i < 100; i++) {
verifyReferenceToPrecompiledStatementId(false);
verifyReferenceToPrecompiledStatementId(true);
}
}
@SuppressWarnings("deprecation")
private void verifyReferenceToPrecompiledStatementId(boolean wal) {
if (wal) {
mDatabase.enableWriteAheadLogging();
} else {
mDatabase.disableWriteAheadLogging();
}
// test with INSERT statement - doesn't use connection pool, if WAL is set
SQLiteStatement stmt = mDatabase.compileStatement("insert into t values(?,?);");
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
assertEquals(mDatabase, stmt.mDatabase);
// sql statement should not be compiled yet
assertEquals(0, stmt.nStatement);
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());
// should still be using the mDatabase connection - verify
assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
stmt.bindString(2, "blah" + colValue);
assertEquals(n, stmt.nStatement);
stmt.executeInsert();
// now that the statement is executed, pre-compiled statement should be released
assertEquals(0, stmt.nStatement);
assertEquals(0, stmt.getSqlStatementId());
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 be the same
assertEquals(mDatabase, stmt.mDatabase);
// test with a SELECT statement - uses connection pool if WAL is set
stmt = mDatabase.compileStatement("select i from t where j=?;");
// sql statement should not be compiled yet
assertEquals(0, stmt.nStatement);
assertEquals(0, stmt.getSqlStatementId());
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);
// 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);
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=1;");
SQLiteStatement statement2 = mDatabase.compileStatement("DELETE FROM test WHERE _id=2;");
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();
@@ -166,8 +262,8 @@ public class SQLiteStatementTest extends AndroidTestCase {
"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);
int nStatement = statementOne.getSqlStatementId();
statementOne.releaseReference();
assertEquals(0, statementOne.getSqlStatementId());
statementOne.close();
@@ -178,8 +274,8 @@ public class SQLiteStatementTest extends AndroidTestCase {
"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);
int nStatement = statementOne.getSqlStatementId();
statementOne.releaseReferenceFromContainer();
assertEquals(0, statementOne.getSqlStatementId());
statementOne.close();