diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 1a096971e4f2d..fa79a9e56440c 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -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.
+ *
+ * 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
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index ba39283c65dfd..fb3f42865d9b0 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -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();
}
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index f6ca1d128d9f3..966459389623a 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -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();
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 2a9a427faa37f..8006b738b1cae 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -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 sqlStrings = new ArrayList();
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();
}
};
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
index 93dbab56bee1f..eb27551b0add9 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
@@ -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();