summaryrefslogtreecommitdiffstats
path: root/core/tests
diff options
context:
space:
mode:
Diffstat (limited to 'core/tests')
-rw-r--r--core/tests/coretests/src/android/database/DatabaseCursorTest.java3
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java401
2 files changed, 371 insertions, 33 deletions
diff --git a/core/tests/coretests/src/android/database/DatabaseCursorTest.java b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
index 0265c87..0733229 100644
--- a/core/tests/coretests/src/android/database/DatabaseCursorTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
@@ -33,7 +33,6 @@ import android.test.AndroidTestCase;
import android.test.PerformanceTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
@@ -41,8 +40,6 @@ import java.io.File;
import java.util.Arrays;
import java.util.Random;
-import junit.framework.TestCase;
-
public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTestCase {
private static final String sString1 = "this is a test";
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 662ba97..dc5613e 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -33,10 +34,13 @@ import java.util.ArrayList;
public class SQLiteDatabaseTest extends AndroidTestCase {
private static final String TAG = "DatabaseGeneralTest";
-
+ private static final String TEST_TABLE = "test";
private static final int CURRENT_DATABASE_VERSION = 42;
private SQLiteDatabase mDatabase;
private File mDatabaseFile;
+ private static final int INSERT = 1;
+ private static final int UPDATE = 2;
+ private static final int DELETE = 3;
@Override
protected void setUp() throws Exception {
@@ -150,6 +154,100 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
}
/**
+ * a transaction should be started before a standalone-update/insert/delete statement
+ */
+ @SmallTest
+ public void testStartXactBeforeUpdateSql() throws InterruptedException {
+ runTestForStartXactBeforeUpdateSql(INSERT);
+ runTestForStartXactBeforeUpdateSql(UPDATE);
+ runTestForStartXactBeforeUpdateSql(DELETE);
+ }
+ private void runTestForStartXactBeforeUpdateSql(int stmtType) throws InterruptedException {
+ createTableAndClearCache();
+
+ ContentValues values = new ContentValues();
+ // make some changes to data in TEST_TABLE
+ for (int i = 0; i < 5; i++) {
+ values.put("i", i);
+ values.put("j", "i" + System.currentTimeMillis());
+ mDatabase.insert(TEST_TABLE, null, values);
+ switch (stmtType) {
+ case UPDATE:
+ values.put("j", "u" + System.currentTimeMillis());
+ mDatabase.update(TEST_TABLE, values, "i = " + i, null);
+ break;
+ case DELETE:
+ mDatabase.delete(TEST_TABLE, "i = 1", null);
+ break;
+ }
+ }
+ // do a query. even though query uses a different database connection,
+ // it should still see the above changes to data because the above standalone
+ // insert/update/deletes are done in transactions automatically.
+ String sql = "select count(*) from " + TEST_TABLE;
+ SQLiteStatement stmt = mDatabase.compileStatement(sql);
+ final int expectedValue = (stmtType == DELETE) ? 4 : 5;
+ assertEquals(expectedValue, stmt.simpleQueryForLong());
+ stmt.close();
+ Cursor c = mDatabase.rawQuery(sql, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(expectedValue, c.getLong(0));
+ c.close();
+
+ // do 5 more changes in a transaction but do a query before and after the commit
+ mDatabase.beginTransaction();
+ for (int i = 10; i < 15; i++) {
+ values.put("i", i);
+ values.put("j", "i" + System.currentTimeMillis());
+ mDatabase.insert(TEST_TABLE, null, values);
+ switch (stmtType) {
+ case UPDATE:
+ values.put("j", "u" + System.currentTimeMillis());
+ mDatabase.update(TEST_TABLE, values, "i = " + i, null);
+ break;
+ case DELETE:
+ mDatabase.delete(TEST_TABLE, "i = 1", null);
+ break;
+ }
+ }
+ mDatabase.setTransactionSuccessful();
+ // do a query before commit - should still have 5 rows
+ // this query should run in a different thread to force it to use a different database
+ // connection
+ Thread t = new Thread() {
+ @Override public void run() {
+ String sql = "select count(*) from " + TEST_TABLE;
+ SQLiteStatement stmt = getDb().compileStatement(sql);
+ assertEquals(expectedValue, stmt.simpleQueryForLong());
+ stmt.close();
+ Cursor c = getDb().rawQuery(sql, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(expectedValue, c.getLong(0));
+ c.close();
+ }
+ };
+ t.start();
+ // wait until the above thread is done
+ t.join();
+ // commit and then query. should see changes from the transaction
+ mDatabase.endTransaction();
+ stmt = mDatabase.compileStatement(sql);
+ final int expectedValue2 = (stmtType == DELETE) ? 9 : 10;
+ assertEquals(expectedValue2, stmt.simpleQueryForLong());
+ stmt.close();
+ c = mDatabase.rawQuery(sql, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(expectedValue2, c.getLong(0));
+ c.close();
+ }
+ private synchronized SQLiteDatabase getDb() {
+ return mDatabase;
+ }
+
+ /**
* Test to ensure that readers are able to read the database data (old versions)
* EVEN WHEN the writer is in a transaction on the same database.
*<p>
@@ -198,6 +296,8 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
// set up connection pool
mDatabase.enableWriteAheadLogging();
mDatabase.setConnectionPoolSize(i + 1);
+ } else {
+ mDatabase.disableWriteAheadLogging();
}
mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
@@ -345,8 +445,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
@SmallTest
public void testLruCachingOfSqliteCompiledSqlObjs() {
- mDatabase.disableWriteAheadLogging();
- mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+ createTableAndClearCache();
// set cache size
int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
mDatabase.setMaxSqlCacheSize(N);
@@ -393,14 +492,27 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
*/
}
+ private void createTableAndClearCache() {
+ mDatabase.disableWriteAheadLogging();
+ mDatabase.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE);
+ mDatabase.execSQL("CREATE TABLE " + TEST_TABLE + " (i int, j int);");
+ mDatabase.enableWriteAheadLogging();
+ mDatabase.lock();
+ // flush the above statement from cache and close all the pending statements to be released
+ mDatabase.deallocCachedSqlStatements();
+ mDatabase.closePendingStatements();
+ mDatabase.unlock();
+ assertEquals(0, mDatabase.getQueuedUpStmtList().size());
+ }
+
/**
* test to make sure the statement finalizations are not done right away but
* piggy-backed onto the next sql statement execution on the same database.
*/
@SmallTest
public void testStatementClose() {
- mDatabase.execSQL("CREATE TABLE test (i int, j int);");
- // fill up statement cache in mDatabase\
+ createTableAndClearCache();
+ // fill up statement cache in mDatabase
int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
mDatabase.setMaxSqlCacheSize(N);
SQLiteStatement stmt;
@@ -429,7 +541,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
// execute something to see if this statement gets finalized
mDatabase.execSQL("delete from test where i = 10;");
statementIds = mDatabase.getQueuedUpStmtList();
- assertEquals(0, statementIds.size());
+ assertFalse(statementIds.contains(stmt0Id));
}
/**
@@ -439,16 +551,16 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
*/
@LargeTest
public void testStatementCloseDiffThread() throws InterruptedException {
- mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+ createTableAndClearCache();
+ final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+ mDatabase.setMaxSqlCacheSize(N);
// fill up statement cache in mDatabase in a thread
Thread t1 = new Thread() {
@Override public void run() {
- int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
- mDatabase.setMaxSqlCacheSize(N);
SQLiteStatement stmt;
- for (int i = 0; i < N; i ++) {
+ for (int i = 0; i < N; i++) {
ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(mDatabase,
+ ClassToTestSqlCompilationAndCaching.create(getDb(),
"insert into test values(" + i + ", ?);");
// keep track of 0th entry
if (i == 0) {
@@ -461,45 +573,44 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
t1.start();
// wait for the thread to finish
t1.join();
+ // mDatabase shouldn't have any statements to be released
+ assertEquals(0, mDatabase.getQueuedUpStmtList().size());
// add one more to the cache - and the above 'stmt0Id' should fall out of cache
// just for the heck of it, do it in a separate thread
Thread t2 = new Thread() {
@Override public void run() {
ClassToTestSqlCompilationAndCaching stmt1 =
- ClassToTestSqlCompilationAndCaching.create(mDatabase,
- "insert into test values(100, ?);");
+ ClassToTestSqlCompilationAndCaching.create(getDb(),
+ "insert into test values(100, ?);");
+ stmt1.bindLong(1, 1);
stmt1.close();
}
};
t2.start();
t2.join();
- // close() in the above thread should have queuedUp the statement for finalization
- ArrayList<Integer> statementIds = mDatabase.getQueuedUpStmtList();
- assertTrue(getStmt0Id() > 0);
- assertTrue(statementIds.contains(stmt0Id));
+ // close() in the above thread should have queuedUp the stmt0Id for finalization
+ ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
+ assertTrue(statementIds.contains(getStmt0Id()));
assertEquals(1, statementIds.size());
// execute something to see if this statement gets finalized
// again do it in a separate thread
Thread t3 = new Thread() {
@Override public void run() {
- mDatabase.execSQL("delete from test where i = 10;");
+ getDb().execSQL("delete from test where i = 10;");
}
};
t3.start();
t3.join();
// is the statement finalized?
- statementIds = mDatabase.getQueuedUpStmtList();
- assertEquals(0, statementIds.size());
+ statementIds = getDb().getQueuedUpStmtList();
+ assertFalse(statementIds.contains(getStmt0Id()));
}
private volatile int stmt0Id = 0;
- private synchronized void setStmt0Id(int stmt0Id) {
- this.stmt0Id = stmt0Id;
- }
private synchronized int getStmt0Id() {
return this.stmt0Id;
}
@@ -510,16 +621,16 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
*/
@LargeTest
public void testStatementCloseByDbClose() throws InterruptedException {
- mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+ createTableAndClearCache();
// fill up statement cache in mDatabase in a thread
Thread t1 = new Thread() {
@Override public void run() {
int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
- mDatabase.setMaxSqlCacheSize(N);
+ getDb().setMaxSqlCacheSize(N);
SQLiteStatement stmt;
for (int i = 0; i < N; i ++) {
ClassToTestSqlCompilationAndCaching c =
- ClassToTestSqlCompilationAndCaching.create(mDatabase,
+ ClassToTestSqlCompilationAndCaching.create(getDb(),
"insert into test values(" + i + ", ?);");
// keep track of 0th entry
if (i == 0) {
@@ -538,7 +649,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
Thread t2 = new Thread() {
@Override public void run() {
ClassToTestSqlCompilationAndCaching stmt1 =
- ClassToTestSqlCompilationAndCaching.create(mDatabase,
+ ClassToTestSqlCompilationAndCaching.create(getDb(),
"insert into test values(100, ?);");
stmt1.bindLong(1, 1);
stmt1.close();
@@ -548,7 +659,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
t2.join();
// close() in the above thread should have queuedUp the statement for finalization
- ArrayList<Integer> statementIds = mDatabase.getQueuedUpStmtList();
+ ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
assertTrue(getStmt0Id() > 0);
assertTrue(statementIds.contains(stmt0Id));
assertEquals(1, statementIds.size());
@@ -558,14 +669,244 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
// again do it in a separate thread
Thread t3 = new Thread() {
@Override public void run() {
- mDatabase.close();
+ getDb().close();
}
};
t3.start();
t3.join();
// check mClosedStatementIds in mDatabase. it should be empty
- statementIds = mDatabase.getQueuedUpStmtList();
+ statementIds = getDb().getQueuedUpStmtList();
assertEquals(0, statementIds.size());
}
+
+ /**
+ * This test tests usage execSQL() to begin transaction works in the following way
+ * Thread #1 does
+ * execSQL("begin transaction");
+ * insert()
+ * Thread # 2
+ * query()
+ * Thread#1 ("end transaction")
+ * Thread # 2 query will execute - because java layer will not have locked the SQLiteDatabase
+ * object and sqlite will consider this query to be part of the transaction.
+ *
+ * but if thread # 1 uses beginTransaction() instead of execSQL() to start transaction,
+ * then Thread # 2's query will have been blocked by java layer
+ * until Thread#1 ends transaction.
+ *
+ * @throws InterruptedException
+ */
+ @SmallTest
+ public void testExecSqlToStartAndEndTransaction() throws InterruptedException {
+ runExecSqlToStartAndEndTransaction("END");
+ // same as above, instead now do "COMMIT" or "ROLLBACK" instead of "END" transaction
+ runExecSqlToStartAndEndTransaction("COMMIT");
+ runExecSqlToStartAndEndTransaction("ROLLBACK");
+ }
+ private void runExecSqlToStartAndEndTransaction(String str) throws InterruptedException {
+ createTableAndClearCache();
+ // disable WAL just so queries and updates use the same database connection
+ mDatabase.disableWriteAheadLogging();
+ mDatabase.execSQL("BEGIN transaction");
+ // even though mDatabase.beginTransaction() is not called to start transaction,
+ // mDatabase connection should now be in transaction as a result of
+ // mDatabase.execSQL("BEGIN transaction")
+ // but mDatabase.mLock should not be held by any thread
+ assertTrue(mDatabase.inTransaction());
+ assertFalse(mDatabase.isDbLockedByCurrentThread());
+ assertFalse(mDatabase.isDbLockedByOtherThreads());
+ assertTrue(mDatabase.amIInTransaction());
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+ assertTrue(mDatabase.inTransaction());
+ assertFalse(mDatabase.isDbLockedByCurrentThread());
+ assertFalse(mDatabase.isDbLockedByOtherThreads());
+ assertTrue(mDatabase.amIInTransaction());
+ Thread t = new Thread() {
+ @Override public void run() {
+ assertTrue(mDatabase.amIInTransaction());
+ assertEquals(999, DatabaseUtils.longForQuery(getDb(),
+ "select j from " + TEST_TABLE + " WHERE i = 10", null));
+ assertTrue(getDb().inTransaction());
+ assertFalse(getDb().isDbLockedByCurrentThread());
+ assertFalse(getDb().isDbLockedByOtherThreads());
+ assertTrue(mDatabase.amIInTransaction());
+ }
+ };
+ t.start();
+ t.join();
+ assertTrue(mDatabase.amIInTransaction());
+ assertTrue(mDatabase.inTransaction());
+ assertFalse(mDatabase.isDbLockedByCurrentThread());
+ assertFalse(mDatabase.isDbLockedByOtherThreads());
+ mDatabase.execSQL(str);
+ assertFalse(mDatabase.amIInTransaction());
+ assertFalse(mDatabase.inTransaction());
+ assertFalse(mDatabase.isDbLockedByCurrentThread());
+ assertFalse(mDatabase.isDbLockedByOtherThreads());
+ }
+
+ /**
+ * test the following
+ * http://b/issue?id=2871037
+ * Cursor cursor = db.query(...);
+ * // with WAL enabled, the above uses a pooled database connection
+ * db.beginTransaction()
+ * try {
+ * db.insert(......);
+ * cursor.requery();
+ * // since the cursor uses pooled database connection, the above requery
+ * // will not return the results that were inserted above since the insert is
+ * // done using main database connection AND the transaction is not committed yet.
+ * // fix is to make the above cursor use the main database connection - and NOT
+ * // the pooled database connection
+ * db.setTransactionSuccessful()
+ * } finally {
+ * db.endTransaction()
+ * }
+ *
+ * @throws InterruptedException
+ */
+ @SmallTest
+ public void testTransactionAndWalInterplay1() throws InterruptedException {
+ createTableAndClearCache();
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+ String sql = "select * from " + TEST_TABLE;
+ Cursor c = mDatabase.rawQuery(sql, null);
+ // should have 1 row in the table
+ assertEquals(1, c.getCount());
+ mDatabase.beginTransactionNonExclusive();
+ try {
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+ assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "select count(*) from " + TEST_TABLE, null));
+ // requery on the previously opened cursor
+ // cursor should now use the main database connection and see 2 rows
+ c.requery();
+ assertEquals(2, c.getCount());
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ c.close();
+
+ // do the same test but now do the requery in a separate thread.
+ createTableAndClearCache();
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+ final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
+ // should have 1 row in the table
+ assertEquals(1, c1.getCount());
+ mDatabase.beginTransactionNonExclusive();
+ try {
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+ assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "select count(*) from " + TEST_TABLE, null));
+ // query in a different thread. that causes the cursor to use a pooled connection
+ // and since this thread hasn't committed its changes, the cursor should still see only
+ // 1 row
+ Thread t = new Thread() {
+ @Override public void run() {
+ c1.requery();
+ assertEquals(1, c1.getCount());
+ }
+ };
+ t.start();
+ t.join();
+ // should be 2 rows now - including the the row inserted above
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ c1.close();
+ }
+
+ /**
+ * This test is same as {@link #testTransactionAndWalInterplay1()} except the following:
+ * instead of mDatabase.beginTransactionNonExclusive(), use execSQL("BEGIN transaction")
+ * and instead of mDatabase.endTransaction(), use execSQL("END");
+ */
+ @SmallTest
+ public void testTransactionAndWalInterplay2() throws InterruptedException {
+ createTableAndClearCache();
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+ String sql = "select * from " + TEST_TABLE;
+ Cursor c = mDatabase.rawQuery(sql, null);
+ // should have 1 row in the table
+ assertEquals(1, c.getCount());
+ mDatabase.execSQL("BEGIN transaction");
+ try {
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+ assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "select count(*) from " + TEST_TABLE, null));
+ // requery on the previously opened cursor
+ // cursor should now use the main database connection and see 2 rows
+ c.requery();
+ assertEquals(2, c.getCount());
+ } finally {
+ mDatabase.execSQL("commit;");
+ }
+ c.close();
+
+ // do the same test but now do the requery in a separate thread.
+ createTableAndClearCache();
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+ final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
+ // should have 1 row in the table
+ assertEquals(1, c1.getCount());
+ mDatabase.execSQL("BEGIN transaction");
+ try {
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+ assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "select count(*) from " + TEST_TABLE, null));
+ // query in a different thread. but since the transaction is started using
+ // execSQ() instead of beginTransaction(), cursor's query is considered part of
+ // the same ransaction - and hence it should see the above inserted row
+ Thread t = new Thread() {
+ @Override public void run() {
+ c1.requery();
+ assertEquals(1, c1.getCount());
+ }
+ };
+ t.start();
+ t.join();
+ // should be 2 rows now - including the the row inserted above
+ } finally {
+ mDatabase.execSQL("commit");
+ }
+ c1.close();
+ }
+
+ /**
+ * This test is same as {@link #testTransactionAndWalInterplay2()} except the following:
+ * instead of commiting the data, do rollback and make sure the data seen by the query
+ * within the transaction is now gone.
+ */
+ @SmallTest
+ public void testTransactionAndWalInterplay3() throws InterruptedException {
+ createTableAndClearCache();
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+ String sql = "select * from " + TEST_TABLE;
+ Cursor c = mDatabase.rawQuery(sql, null);
+ // should have 1 row in the table
+ assertEquals(1, c.getCount());
+ mDatabase.execSQL("BEGIN transaction");
+ try {
+ mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+ assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "select count(*) from " + TEST_TABLE, null));
+ // requery on the previously opened cursor
+ // cursor should now use the main database connection and see 2 rows
+ c.requery();
+ assertEquals(2, c.getCount());
+ } finally {
+ // rollback the change
+ mDatabase.execSQL("rollback;");
+ }
+ // since the change is rolled back, do the same query again and should now find only 1 row
+ c.requery();
+ assertEquals(1, c.getCount());
+ assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "select count(*) from " + TEST_TABLE, null));
+ c.close();
+ }
}