blob: 39258ae473eec966d21b1e98746008a1afb81657 [file] [log] [blame]
Vasu Nori6c354da2010-04-26 23:33:39 -07001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.database.sqlite;
18
Vasu Norice38b982010-07-22 13:57:13 -070019import android.content.ContentValues;
Vasu Nori6c354da2010-04-26 23:33:39 -070020import android.content.Context;
Vasu Nori65a88832010-07-16 15:14:08 -070021import android.database.Cursor;
Vasu Noricc6f5492010-08-23 17:05:25 -070022import android.database.DatabaseErrorHandler;
Vasu Nori6c354da2010-04-26 23:33:39 -070023import android.database.DatabaseUtils;
Vasu Noricc6f5492010-08-23 17:05:25 -070024import android.database.DefaultDatabaseErrorHandler;
Vasu Nori75010102010-07-01 16:23:06 -070025import android.database.sqlite.SQLiteDatabase;
Vasu Noricc6f5492010-08-23 17:05:25 -070026import android.database.sqlite.SQLiteDatabase.CursorFactory;
Vasu Nori75010102010-07-01 16:23:06 -070027import android.database.sqlite.SQLiteStatement;
Vasu Nori6c354da2010-04-26 23:33:39 -070028import android.test.AndroidTestCase;
29import android.test.suitebuilder.annotation.LargeTest;
Vasu Nori59d60422010-07-03 15:37:21 -070030import android.test.suitebuilder.annotation.MediumTest;
Vasu Nori6c354da2010-04-26 23:33:39 -070031import android.test.suitebuilder.annotation.SmallTest;
Vasu Nori75010102010-07-01 16:23:06 -070032import android.test.suitebuilder.annotation.Suppress;
Vasu Nori6c354da2010-04-26 23:33:39 -070033import android.util.Log;
34
35import java.io.File;
Vasu Nori75010102010-07-01 16:23:06 -070036import java.util.ArrayList;
Vasu Nori6c354da2010-04-26 23:33:39 -070037
38public class SQLiteDatabaseTest extends AndroidTestCase {
39 private static final String TAG = "DatabaseGeneralTest";
Vasu Norice38b982010-07-22 13:57:13 -070040 private static final String TEST_TABLE = "test";
Vasu Nori6c354da2010-04-26 23:33:39 -070041 private static final int CURRENT_DATABASE_VERSION = 42;
42 private SQLiteDatabase mDatabase;
43 private File mDatabaseFile;
Vasu Norice38b982010-07-22 13:57:13 -070044 private static final int INSERT = 1;
45 private static final int UPDATE = 2;
46 private static final int DELETE = 3;
Vasu Noricc6f5492010-08-23 17:05:25 -070047 private static final String DB_NAME = "database_test.db";
Vasu Nori6c354da2010-04-26 23:33:39 -070048
49 @Override
50 protected void setUp() throws Exception {
51 super.setUp();
52 dbSetUp();
53 }
54
55 @Override
56 protected void tearDown() throws Exception {
57 dbTeardown();
58 super.tearDown();
59 }
60
61 private void dbTeardown() throws Exception {
62 mDatabase.close();
63 mDatabaseFile.delete();
64 }
65
66 private void dbSetUp() throws Exception {
67 File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
Vasu Noricc6f5492010-08-23 17:05:25 -070068 mDatabaseFile = new File(dbDir, DB_NAME);
Vasu Nori6c354da2010-04-26 23:33:39 -070069 if (mDatabaseFile.exists()) {
70 mDatabaseFile.delete();
71 }
72 mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
73 assertNotNull(mDatabase);
74 mDatabase.setVersion(CURRENT_DATABASE_VERSION);
75 }
76
77 @SmallTest
78 public void testEnableWriteAheadLogging() {
Vasu Nori2827d6d2010-07-04 00:26:18 -070079 mDatabase.disableWriteAheadLogging();
Vasu Nori6c354da2010-04-26 23:33:39 -070080 assertNull(mDatabase.mConnectionPool);
81 mDatabase.enableWriteAheadLogging();
82 DatabaseConnectionPool pool = mDatabase.mConnectionPool;
83 assertNotNull(pool);
84 // make the same call again and make sure the pool already setup is not re-created
85 mDatabase.enableWriteAheadLogging();
86 assertEquals(pool, mDatabase.mConnectionPool);
87 }
88
89 @SmallTest
Vasu Nori65a88832010-07-16 15:14:08 -070090 public void testDisableWriteAheadLogging() {
91 mDatabase.execSQL("create table test (i int);");
92 mDatabase.enableWriteAheadLogging();
93 assertNotNull(mDatabase.mConnectionPool);
94 // get a pooled database connection
95 SQLiteDatabase db = mDatabase.getDbConnection("select * from test");
96 assertNotNull(db);
97 assertFalse(mDatabase.equals(db));
98 assertTrue(db.isOpen());
99 // disable WAL - which should close connection pool and all pooled connections
100 mDatabase.disableWriteAheadLogging();
101 assertNull(mDatabase.mConnectionPool);
102 assertFalse(db.isOpen());
103 }
104
105 @SmallTest
106 public void testCursorsWithClosedDbConnAfterDisableWriteAheadLogging() {
107 mDatabase.disableWriteAheadLogging();
108 mDatabase.beginTransactionNonExclusive();
109 mDatabase.execSQL("create table test (i int);");
110 mDatabase.execSQL("insert into test values(1);");
111 mDatabase.setTransactionSuccessful();
112 mDatabase.endTransaction();
113 mDatabase.enableWriteAheadLogging();
114 assertNotNull(mDatabase.mConnectionPool);
115 assertEquals(0, mDatabase.mConnectionPool.getSize());
116 assertEquals(0, mDatabase.mConnectionPool.getFreePoolSize());
117 // get a cursor which should use pooled database connection
118 Cursor c = mDatabase.rawQuery("select * from test", null);
119 assertEquals(1, c.getCount());
120 assertEquals(1, mDatabase.mConnectionPool.getSize());
121 assertEquals(1, mDatabase.mConnectionPool.getFreePoolSize());
122 SQLiteDatabase db = mDatabase.mConnectionPool.getConnectionList().get(0);
123 assertTrue(mDatabase.mConnectionPool.isDatabaseObjFree(db));
124 // disable WAL - which should close connection pool and all pooled connections
125 mDatabase.disableWriteAheadLogging();
126 assertNull(mDatabase.mConnectionPool);
127 assertFalse(db.isOpen());
128 // cursor data should still be accessible because it is fetching data from CursorWindow
129 c.moveToNext();
130 assertEquals(1, c.getInt(0));
131 c.requery();
132 assertEquals(1, c.getCount());
133 c.moveToNext();
134 assertEquals(1, c.getInt(0));
135 c.close();
136 }
137
Vasu Nori6c354da2010-04-26 23:33:39 -0700138 /**
Vasu Norice38b982010-07-22 13:57:13 -0700139 * a transaction should be started before a standalone-update/insert/delete statement
140 */
141 @SmallTest
142 public void testStartXactBeforeUpdateSql() throws InterruptedException {
143 runTestForStartXactBeforeUpdateSql(INSERT);
144 runTestForStartXactBeforeUpdateSql(UPDATE);
145 runTestForStartXactBeforeUpdateSql(DELETE);
146 }
147 private void runTestForStartXactBeforeUpdateSql(int stmtType) throws InterruptedException {
148 createTableAndClearCache();
149
150 ContentValues values = new ContentValues();
151 // make some changes to data in TEST_TABLE
152 for (int i = 0; i < 5; i++) {
153 values.put("i", i);
154 values.put("j", "i" + System.currentTimeMillis());
155 mDatabase.insert(TEST_TABLE, null, values);
156 switch (stmtType) {
157 case UPDATE:
158 values.put("j", "u" + System.currentTimeMillis());
159 mDatabase.update(TEST_TABLE, values, "i = " + i, null);
160 break;
161 case DELETE:
162 mDatabase.delete(TEST_TABLE, "i = 1", null);
163 break;
164 }
165 }
166 // do a query. even though query uses a different database connection,
167 // it should still see the above changes to data because the above standalone
168 // insert/update/deletes are done in transactions automatically.
169 String sql = "select count(*) from " + TEST_TABLE;
170 SQLiteStatement stmt = mDatabase.compileStatement(sql);
171 final int expectedValue = (stmtType == DELETE) ? 4 : 5;
172 assertEquals(expectedValue, stmt.simpleQueryForLong());
173 stmt.close();
174 Cursor c = mDatabase.rawQuery(sql, null);
175 assertEquals(1, c.getCount());
176 c.moveToFirst();
177 assertEquals(expectedValue, c.getLong(0));
178 c.close();
179
180 // do 5 more changes in a transaction but do a query before and after the commit
181 mDatabase.beginTransaction();
182 for (int i = 10; i < 15; i++) {
183 values.put("i", i);
184 values.put("j", "i" + System.currentTimeMillis());
185 mDatabase.insert(TEST_TABLE, null, values);
186 switch (stmtType) {
187 case UPDATE:
188 values.put("j", "u" + System.currentTimeMillis());
189 mDatabase.update(TEST_TABLE, values, "i = " + i, null);
190 break;
191 case DELETE:
192 mDatabase.delete(TEST_TABLE, "i = 1", null);
193 break;
194 }
195 }
196 mDatabase.setTransactionSuccessful();
197 // do a query before commit - should still have 5 rows
198 // this query should run in a different thread to force it to use a different database
199 // connection
200 Thread t = new Thread() {
201 @Override public void run() {
202 String sql = "select count(*) from " + TEST_TABLE;
203 SQLiteStatement stmt = getDb().compileStatement(sql);
204 assertEquals(expectedValue, stmt.simpleQueryForLong());
205 stmt.close();
206 Cursor c = getDb().rawQuery(sql, null);
207 assertEquals(1, c.getCount());
208 c.moveToFirst();
209 assertEquals(expectedValue, c.getLong(0));
210 c.close();
211 }
212 };
213 t.start();
214 // wait until the above thread is done
215 t.join();
216 // commit and then query. should see changes from the transaction
217 mDatabase.endTransaction();
218 stmt = mDatabase.compileStatement(sql);
219 final int expectedValue2 = (stmtType == DELETE) ? 9 : 10;
220 assertEquals(expectedValue2, stmt.simpleQueryForLong());
221 stmt.close();
222 c = mDatabase.rawQuery(sql, null);
223 assertEquals(1, c.getCount());
224 c.moveToFirst();
225 assertEquals(expectedValue2, c.getLong(0));
226 c.close();
227 }
228 private synchronized SQLiteDatabase getDb() {
229 return mDatabase;
230 }
231
232 /**
Vasu Nori6c354da2010-04-26 23:33:39 -0700233 * Test to ensure that readers are able to read the database data (old versions)
234 * EVEN WHEN the writer is in a transaction on the same database.
235 *<p>
236 * This test starts 1 Writer and 2 Readers and sets up connection pool for readers
237 * by calling the method {@link SQLiteDatabase#enableWriteAheadLogging()}.
238 * <p>
239 * Writer does the following in a tight loop
240 * <pre>
241 * begin transaction
242 * insert into table_1
243 * insert into table_2
244 * commit
245 * </pre>
246 * <p>
247 * As long a the writer is alive, Readers do the following in a tight loop at the same time
248 * <pre>
249 * Reader_K does "select count(*) from table_K" where K = 1 or 2
250 * </pre>
251 * <p>
252 * The test is run for TIME_TO_RUN_WAL_TEST_FOR sec.
253 * <p>
254 * The test is repeated for different connection-pool-sizes (1..3)
255 * <p>
256 * And at the end of of each test, the following statistics are printed
257 * <ul>
258 * <li>connection-pool-size</li>
259 * <li>number-of-transactions by writer</li>
260 * <li>number of reads by reader_K while the writer is IN or NOT-IN xaction</li>
261 * </ul>
262 */
263 @LargeTest
Vasu Nori75010102010-07-01 16:23:06 -0700264 @Suppress // run this test only if you need to collect the numbers from this test
Vasu Nori6c354da2010-04-26 23:33:39 -0700265 public void testConcurrencyEffectsOfConnPool() throws Exception {
266 // run the test with sqlite WAL enable
267 runConnectionPoolTest(true);
268
269 // run the same test WITHOUT sqlite WAL enabled
270 runConnectionPoolTest(false);
271 }
272
273 private void runConnectionPoolTest(boolean useWal) throws Exception {
274 int M = 3;
275 StringBuilder[] buff = new StringBuilder[M];
276 for (int i = 0; i < M; i++) {
277 if (useWal) {
278 // set up connection pool
279 mDatabase.enableWriteAheadLogging();
Vasu Noriffe06122010-09-27 12:32:57 -0700280 mDatabase.mConnectionPool.setMaxPoolSize(i + 1);
Vasu Norice38b982010-07-22 13:57:13 -0700281 } else {
282 mDatabase.disableWriteAheadLogging();
Vasu Nori6c354da2010-04-26 23:33:39 -0700283 }
284 mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
285 mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
286 mDatabase.beginTransaction();
287 for (int k = 0; k < 5; k++) {
288 mDatabase.execSQL("insert into t1 values(?,?);", new String[] {k+"", k+""});
289 mDatabase.execSQL("insert into t2 values(?,?);", new String[] {k+"", k+""});
290 }
291 mDatabase.setTransactionSuccessful();
292 mDatabase.endTransaction();
293
294 // start a writer
295 Writer w = new Writer(mDatabase);
296
297 // initialize an array of counters to be passed to the readers
298 Reader r1 = new Reader(mDatabase, "t1", w, 0);
299 Reader r2 = new Reader(mDatabase, "t2", w, 1);
300 w.start();
301 r1.start();
302 r2.start();
303
304 // wait for all threads to die
305 w.join();
306 r1.join();
307 r2.join();
308
309 // print the stats
310 int[][] counts = getCounts();
311 buff[i] = new StringBuilder();
312 buff[i].append("connpool-size = ");
313 buff[i].append(i + 1);
314 buff[i].append(", num xacts by writer = ");
315 buff[i].append(getNumXacts());
316 buff[i].append(", num-reads-in-xact/NOT-in-xact by reader1 = ");
317 buff[i].append(counts[0][1] + "/" + counts[0][0]);
318 buff[i].append(", by reader2 = ");
319 buff[i].append(counts[1][1] + "/" + counts[1][0]);
320
321 Log.i(TAG, "done testing for conn-pool-size of " + (i+1));
322
323 dbTeardown();
324 dbSetUp();
325 }
326 Log.i(TAG, "duration of test " + TIME_TO_RUN_WAL_TEST_FOR + " sec");
327 for (int i = 0; i < M; i++) {
328 Log.i(TAG, buff[i].toString());
329 }
330 }
331
332 private boolean inXact = false;
333 private int numXacts;
Vasu Nori57feb5d2010-06-22 10:39:04 -0700334 private static final int TIME_TO_RUN_WAL_TEST_FOR = 15; // num sec this test should run
Vasu Nori6c354da2010-04-26 23:33:39 -0700335 private int[][] counts = new int[2][2];
336
337 private synchronized boolean inXact() {
338 return inXact;
339 }
340
341 private synchronized void setInXactFlag(boolean flag) {
342 inXact = flag;
343 }
344
345 private synchronized void setCounts(int readerNum, int[] numReads) {
346 counts[readerNum][0] = numReads[0];
347 counts[readerNum][1] = numReads[1];
348 }
349
350 private synchronized int[][] getCounts() {
351 return counts;
352 }
353
354 private synchronized void setNumXacts(int num) {
355 numXacts = num;
356 }
357
358 private synchronized int getNumXacts() {
359 return numXacts;
360 }
361
362 private class Writer extends Thread {
363 private SQLiteDatabase db = null;
364 public Writer(SQLiteDatabase db) {
365 this.db = db;
366 }
367 @Override public void run() {
368 // in a loop, for N sec, do the following
369 // BEGIN transaction
370 // insert into table t1, t2
371 // Commit
372 long now = System.currentTimeMillis();
373 int k;
374 for (k = 0;(System.currentTimeMillis() - now) / 1000 < TIME_TO_RUN_WAL_TEST_FOR; k++) {
375 db.beginTransactionNonExclusive();
376 setInXactFlag(true);
377 for (int i = 0; i < 10; i++) {
378 db.execSQL("insert into t1 values(?,?);", new String[] {i+"", i+""});
379 db.execSQL("insert into t2 values(?,?);", new String[] {i+"", i+""});
380 }
381 db.setTransactionSuccessful();
382 setInXactFlag(false);
383 db.endTransaction();
384 }
385 setNumXacts(k);
386 }
387 }
388
389 private class Reader extends Thread {
390 private SQLiteDatabase db = null;
391 private String table = null;
392 private Writer w = null;
393 private int readerNum;
394 private int[] numReads = new int[2];
395 public Reader(SQLiteDatabase db, String table, Writer w, int readerNum) {
396 this.db = db;
397 this.table = table;
398 this.w = w;
399 this.readerNum = readerNum;
400 }
401 @Override public void run() {
402 // while the write is alive, in a loop do the query on a table
403 while (w.isAlive()) {
404 for (int i = 0; i < 10; i++) {
405 DatabaseUtils.longForQuery(db, "select count(*) from " + this.table, null);
406 // update count of reads
407 numReads[inXact() ? 1 : 0] += 1;
408 }
409 }
410 setCounts(readerNum, numReads);
411 }
412 }
Vasu Nori75010102010-07-01 16:23:06 -0700413
Vasu Nori65a88832010-07-16 15:14:08 -0700414 public static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram {
Vasu Norie25539f2010-07-08 17:06:13 -0700415 private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) {
416 super(db, sql);
417 }
Vasu Nori65a88832010-07-16 15:14:08 -0700418 public static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) {
Vasu Norie25539f2010-07-08 17:06:13 -0700419 db.lock();
420 try {
421 return new ClassToTestSqlCompilationAndCaching(db, sql);
422 } finally {
423 db.unlock();
424 }
425 }
426 }
427
Vasu Norib729dcc2010-09-14 11:35:49 -0700428 @SmallTest
429 public void testLruCachingOfSqliteCompiledSqlObjs() {
430 createTableAndClearCache();
431 // set cache size
432 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
433 mDatabase.setMaxSqlCacheSize(N);
434
435 // do N+1 queries - and when the 0th entry is removed from LRU cache due to the
436 // insertion of (N+1)th entry, make sure 0th entry is closed
437 ArrayList<Integer> stmtObjs = new ArrayList<Integer>();
438 ArrayList<String> sqlStrings = new ArrayList<String>();
439 int stmt0 = 0;
440 for (int i = 0; i < N+1; i++) {
441 String s = "insert into test values(" + i + ",?);";
442 sqlStrings.add(s);
443 ClassToTestSqlCompilationAndCaching c =
444 ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
445 int n = c.getSqlStatementId();
446 stmtObjs.add(i, n);
447 if (i == 0) {
448 // save the statementId of this obj. we want to make sure it is thrown out of
449 // the cache at the end of this test.
450 stmt0 = n;
451 }
452 c.close();
453 }
454 // is 0'th entry out of the cache? it should be in the list of statementIds
455 // corresponding to the pre-compiled sql statements to be finalized.
456 assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
457 for (int i = 1; i < N+1; i++) {
458 SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
459 assertNotNull(compSql);
460 assertTrue(stmtObjs.contains(compSql.nStatement));
461 }
462 }
463
Vasu Nori59d60422010-07-03 15:37:21 -0700464 @MediumTest
465 public void testDbCloseReleasingAllCachedSql() {
466 mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
467 "num1 INTEGER, num2 INTEGER, image BLOB);");
468 final String statement = "DELETE FROM test WHERE _id=?;";
469 SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
Vasu Nori2827d6d2010-07-04 00:26:18 -0700470 statementDoNotClose.bindLong(1, 1);
Vasu Nori59d60422010-07-03 15:37:21 -0700471 /* do not close statementDoNotClose object.
472 * That should leave it in SQLiteDatabase.mPrograms.
473 * mDatabase.close() in tearDown() should release it.
474 */
475 }
476
Vasu Norice38b982010-07-22 13:57:13 -0700477 private void createTableAndClearCache() {
478 mDatabase.disableWriteAheadLogging();
479 mDatabase.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE);
480 mDatabase.execSQL("CREATE TABLE " + TEST_TABLE + " (i int, j int);");
481 mDatabase.enableWriteAheadLogging();
482 mDatabase.lock();
483 // flush the above statement from cache and close all the pending statements to be released
Vasu Norib729dcc2010-09-14 11:35:49 -0700484 mDatabase.deallocCachedSqlStatements();
Vasu Norice38b982010-07-22 13:57:13 -0700485 mDatabase.closePendingStatements();
486 mDatabase.unlock();
487 assertEquals(0, mDatabase.getQueuedUpStmtList().size());
488 }
489
Vasu Nori59d60422010-07-03 15:37:21 -0700490 /**
491 * test to make sure the statement finalizations are not done right away but
Vasu Nori57feb5d2010-06-22 10:39:04 -0700492 * piggy-backed onto the next sql statement execution on the same database.
Vasu Nori59d60422010-07-03 15:37:21 -0700493 */
494 @SmallTest
495 public void testStatementClose() {
Vasu Norice38b982010-07-22 13:57:13 -0700496 createTableAndClearCache();
497 // fill up statement cache in mDatabase
Vasu Norie25539f2010-07-08 17:06:13 -0700498 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
Vasu Nori59d60422010-07-03 15:37:21 -0700499 mDatabase.setMaxSqlCacheSize(N);
500 SQLiteStatement stmt;
501 int stmt0Id = 0;
502 for (int i = 0; i < N; i ++) {
Vasu Norie25539f2010-07-08 17:06:13 -0700503 ClassToTestSqlCompilationAndCaching c =
504 ClassToTestSqlCompilationAndCaching.create(mDatabase,
505 "insert into test values(" + i + ", ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700506 // keep track of 0th entry
507 if (i == 0) {
Vasu Norie25539f2010-07-08 17:06:13 -0700508 stmt0Id = c.getSqlStatementId();
Vasu Nori59d60422010-07-03 15:37:21 -0700509 }
Vasu Norie25539f2010-07-08 17:06:13 -0700510 c.close();
Vasu Nori59d60422010-07-03 15:37:21 -0700511 }
512
513 // add one more to the cache - and the above 'stmt0Id' should fall out of cache
Vasu Norie25539f2010-07-08 17:06:13 -0700514 ClassToTestSqlCompilationAndCaching stmt1 =
515 ClassToTestSqlCompilationAndCaching.create(mDatabase,
516 "insert into test values(100, ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700517 stmt1.close();
518
519 // the above close() should have queuedUp the statement for finalization
520 ArrayList<Integer> statementIds = mDatabase.getQueuedUpStmtList();
521 assertTrue(statementIds.contains(stmt0Id));
522
523 // execute something to see if this statement gets finalized
524 mDatabase.execSQL("delete from test where i = 10;");
525 statementIds = mDatabase.getQueuedUpStmtList();
Vasu Norice38b982010-07-22 13:57:13 -0700526 assertFalse(statementIds.contains(stmt0Id));
Vasu Nori59d60422010-07-03 15:37:21 -0700527 }
528
529 /**
530 * same as above - except that the statement to be finalized is from Thread # 1.
Vasu Nori57feb5d2010-06-22 10:39:04 -0700531 * and it is eventually finalized in Thread # 2 when it executes a SQL statement.
Vasu Nori59d60422010-07-03 15:37:21 -0700532 * @throws InterruptedException
533 */
534 @LargeTest
535 public void testStatementCloseDiffThread() throws InterruptedException {
Vasu Norice38b982010-07-22 13:57:13 -0700536 createTableAndClearCache();
537 final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
538 mDatabase.setMaxSqlCacheSize(N);
Vasu Nori59d60422010-07-03 15:37:21 -0700539 // fill up statement cache in mDatabase in a thread
540 Thread t1 = new Thread() {
541 @Override public void run() {
Vasu Nori59d60422010-07-03 15:37:21 -0700542 SQLiteStatement stmt;
Vasu Norice38b982010-07-22 13:57:13 -0700543 for (int i = 0; i < N; i++) {
Vasu Norie25539f2010-07-08 17:06:13 -0700544 ClassToTestSqlCompilationAndCaching c =
Vasu Norice38b982010-07-22 13:57:13 -0700545 ClassToTestSqlCompilationAndCaching.create(getDb(),
Vasu Norie25539f2010-07-08 17:06:13 -0700546 "insert into test values(" + i + ", ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700547 // keep track of 0th entry
548 if (i == 0) {
Vasu Norie25539f2010-07-08 17:06:13 -0700549 stmt0Id = c.getSqlStatementId();
Vasu Nori59d60422010-07-03 15:37:21 -0700550 }
Vasu Norie25539f2010-07-08 17:06:13 -0700551 c.close();
Vasu Nori59d60422010-07-03 15:37:21 -0700552 }
553 }
554 };
555 t1.start();
556 // wait for the thread to finish
557 t1.join();
Vasu Norice38b982010-07-22 13:57:13 -0700558 // mDatabase shouldn't have any statements to be released
559 assertEquals(0, mDatabase.getQueuedUpStmtList().size());
Vasu Nori59d60422010-07-03 15:37:21 -0700560
561 // add one more to the cache - and the above 'stmt0Id' should fall out of cache
562 // just for the heck of it, do it in a separate thread
563 Thread t2 = new Thread() {
564 @Override public void run() {
Vasu Norie25539f2010-07-08 17:06:13 -0700565 ClassToTestSqlCompilationAndCaching stmt1 =
Vasu Norice38b982010-07-22 13:57:13 -0700566 ClassToTestSqlCompilationAndCaching.create(getDb(),
567 "insert into test values(100, ?);");
568 stmt1.bindLong(1, 1);
Vasu Nori59d60422010-07-03 15:37:21 -0700569 stmt1.close();
570 }
571 };
572 t2.start();
573 t2.join();
574
Vasu Norice38b982010-07-22 13:57:13 -0700575 // close() in the above thread should have queuedUp the stmt0Id for finalization
576 ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
577 assertTrue(statementIds.contains(getStmt0Id()));
Vasu Nori59d60422010-07-03 15:37:21 -0700578 assertEquals(1, statementIds.size());
579
580 // execute something to see if this statement gets finalized
581 // again do it in a separate thread
582 Thread t3 = new Thread() {
583 @Override public void run() {
Vasu Norice38b982010-07-22 13:57:13 -0700584 getDb().execSQL("delete from test where i = 10;");
Vasu Nori59d60422010-07-03 15:37:21 -0700585 }
586 };
587 t3.start();
588 t3.join();
589
590 // is the statement finalized?
Vasu Norice38b982010-07-22 13:57:13 -0700591 statementIds = getDb().getQueuedUpStmtList();
592 assertFalse(statementIds.contains(getStmt0Id()));
Vasu Nori59d60422010-07-03 15:37:21 -0700593 }
594
595 private volatile int stmt0Id = 0;
Vasu Nori59d60422010-07-03 15:37:21 -0700596 private synchronized int getStmt0Id() {
597 return this.stmt0Id;
598 }
599
600 /**
601 * same as above - except that the queue of statements to be finalized are finalized
602 * by database close() operation.
603 */
604 @LargeTest
605 public void testStatementCloseByDbClose() throws InterruptedException {
Vasu Norice38b982010-07-22 13:57:13 -0700606 createTableAndClearCache();
Vasu Nori59d60422010-07-03 15:37:21 -0700607 // fill up statement cache in mDatabase in a thread
608 Thread t1 = new Thread() {
609 @Override public void run() {
Vasu Norie25539f2010-07-08 17:06:13 -0700610 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
Vasu Norice38b982010-07-22 13:57:13 -0700611 getDb().setMaxSqlCacheSize(N);
Vasu Nori59d60422010-07-03 15:37:21 -0700612 SQLiteStatement stmt;
613 for (int i = 0; i < N; i ++) {
Vasu Norie25539f2010-07-08 17:06:13 -0700614 ClassToTestSqlCompilationAndCaching c =
Vasu Norice38b982010-07-22 13:57:13 -0700615 ClassToTestSqlCompilationAndCaching.create(getDb(),
Vasu Norie25539f2010-07-08 17:06:13 -0700616 "insert into test values(" + i + ", ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700617 // keep track of 0th entry
618 if (i == 0) {
Vasu Norie25539f2010-07-08 17:06:13 -0700619 stmt0Id = c.getSqlStatementId();
Vasu Nori59d60422010-07-03 15:37:21 -0700620 }
Vasu Norie25539f2010-07-08 17:06:13 -0700621 c.close();
Vasu Nori59d60422010-07-03 15:37:21 -0700622 }
623 }
624 };
625 t1.start();
626 // wait for the thread to finish
627 t1.join();
628
629 // add one more to the cache - and the above 'stmt0Id' should fall out of cache
630 // just for the heck of it, do it in a separate thread
631 Thread t2 = new Thread() {
632 @Override public void run() {
Vasu Norie25539f2010-07-08 17:06:13 -0700633 ClassToTestSqlCompilationAndCaching stmt1 =
Vasu Norice38b982010-07-22 13:57:13 -0700634 ClassToTestSqlCompilationAndCaching.create(getDb(),
Vasu Norie25539f2010-07-08 17:06:13 -0700635 "insert into test values(100, ?);");
Vasu Nori2827d6d2010-07-04 00:26:18 -0700636 stmt1.bindLong(1, 1);
Vasu Nori59d60422010-07-03 15:37:21 -0700637 stmt1.close();
638 }
639 };
640 t2.start();
641 t2.join();
642
643 // close() in the above thread should have queuedUp the statement for finalization
Vasu Norice38b982010-07-22 13:57:13 -0700644 ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
Vasu Nori59d60422010-07-03 15:37:21 -0700645 assertTrue(getStmt0Id() > 0);
646 assertTrue(statementIds.contains(stmt0Id));
647 assertEquals(1, statementIds.size());
648
649 // close the database. everything from mClosedStatementIds in mDatabase
650 // should be finalized and cleared from the list
651 // again do it in a separate thread
652 Thread t3 = new Thread() {
653 @Override public void run() {
Vasu Norice38b982010-07-22 13:57:13 -0700654 getDb().close();
Vasu Nori59d60422010-07-03 15:37:21 -0700655 }
656 };
657 t3.start();
658 t3.join();
659
660 // check mClosedStatementIds in mDatabase. it should be empty
Vasu Norice38b982010-07-22 13:57:13 -0700661 statementIds = getDb().getQueuedUpStmtList();
Vasu Nori59d60422010-07-03 15:37:21 -0700662 assertEquals(0, statementIds.size());
663 }
Vasu Norice38b982010-07-22 13:57:13 -0700664
665 /**
666 * This test tests usage execSQL() to begin transaction works in the following way
667 * Thread #1 does
668 * execSQL("begin transaction");
669 * insert()
670 * Thread # 2
671 * query()
672 * Thread#1 ("end transaction")
673 * Thread # 2 query will execute - because java layer will not have locked the SQLiteDatabase
674 * object and sqlite will consider this query to be part of the transaction.
675 *
676 * but if thread # 1 uses beginTransaction() instead of execSQL() to start transaction,
677 * then Thread # 2's query will have been blocked by java layer
678 * until Thread#1 ends transaction.
679 *
680 * @throws InterruptedException
681 */
682 @SmallTest
683 public void testExecSqlToStartAndEndTransaction() throws InterruptedException {
684 runExecSqlToStartAndEndTransaction("END");
685 // same as above, instead now do "COMMIT" or "ROLLBACK" instead of "END" transaction
686 runExecSqlToStartAndEndTransaction("COMMIT");
687 runExecSqlToStartAndEndTransaction("ROLLBACK");
688 }
689 private void runExecSqlToStartAndEndTransaction(String str) throws InterruptedException {
690 createTableAndClearCache();
691 // disable WAL just so queries and updates use the same database connection
692 mDatabase.disableWriteAheadLogging();
693 mDatabase.execSQL("BEGIN transaction");
694 // even though mDatabase.beginTransaction() is not called to start transaction,
695 // mDatabase connection should now be in transaction as a result of
696 // mDatabase.execSQL("BEGIN transaction")
697 // but mDatabase.mLock should not be held by any thread
698 assertTrue(mDatabase.inTransaction());
699 assertFalse(mDatabase.isDbLockedByCurrentThread());
700 assertFalse(mDatabase.isDbLockedByOtherThreads());
701 assertTrue(mDatabase.amIInTransaction());
702 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
703 assertTrue(mDatabase.inTransaction());
704 assertFalse(mDatabase.isDbLockedByCurrentThread());
705 assertFalse(mDatabase.isDbLockedByOtherThreads());
706 assertTrue(mDatabase.amIInTransaction());
707 Thread t = new Thread() {
708 @Override public void run() {
709 assertTrue(mDatabase.amIInTransaction());
710 assertEquals(999, DatabaseUtils.longForQuery(getDb(),
711 "select j from " + TEST_TABLE + " WHERE i = 10", null));
712 assertTrue(getDb().inTransaction());
713 assertFalse(getDb().isDbLockedByCurrentThread());
714 assertFalse(getDb().isDbLockedByOtherThreads());
715 assertTrue(mDatabase.amIInTransaction());
716 }
717 };
718 t.start();
719 t.join();
720 assertTrue(mDatabase.amIInTransaction());
721 assertTrue(mDatabase.inTransaction());
722 assertFalse(mDatabase.isDbLockedByCurrentThread());
723 assertFalse(mDatabase.isDbLockedByOtherThreads());
724 mDatabase.execSQL(str);
725 assertFalse(mDatabase.amIInTransaction());
726 assertFalse(mDatabase.inTransaction());
727 assertFalse(mDatabase.isDbLockedByCurrentThread());
728 assertFalse(mDatabase.isDbLockedByOtherThreads());
729 }
730
731 /**
732 * test the following
733 * http://b/issue?id=2871037
734 * Cursor cursor = db.query(...);
735 * // with WAL enabled, the above uses a pooled database connection
736 * db.beginTransaction()
737 * try {
738 * db.insert(......);
739 * cursor.requery();
740 * // since the cursor uses pooled database connection, the above requery
741 * // will not return the results that were inserted above since the insert is
742 * // done using main database connection AND the transaction is not committed yet.
743 * // fix is to make the above cursor use the main database connection - and NOT
744 * // the pooled database connection
745 * db.setTransactionSuccessful()
746 * } finally {
747 * db.endTransaction()
748 * }
749 *
750 * @throws InterruptedException
751 */
752 @SmallTest
753 public void testTransactionAndWalInterplay1() throws InterruptedException {
754 createTableAndClearCache();
755 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
756 String sql = "select * from " + TEST_TABLE;
757 Cursor c = mDatabase.rawQuery(sql, null);
758 // should have 1 row in the table
759 assertEquals(1, c.getCount());
760 mDatabase.beginTransactionNonExclusive();
761 try {
762 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
763 assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
764 "select count(*) from " + TEST_TABLE, null));
765 // requery on the previously opened cursor
766 // cursor should now use the main database connection and see 2 rows
767 c.requery();
768 assertEquals(2, c.getCount());
769 mDatabase.setTransactionSuccessful();
770 } finally {
771 mDatabase.endTransaction();
772 }
773 c.close();
774
775 // do the same test but now do the requery in a separate thread.
776 createTableAndClearCache();
777 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
778 final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
779 // should have 1 row in the table
780 assertEquals(1, c1.getCount());
781 mDatabase.beginTransactionNonExclusive();
782 try {
783 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
784 assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
785 "select count(*) from " + TEST_TABLE, null));
786 // query in a different thread. that causes the cursor to use a pooled connection
787 // and since this thread hasn't committed its changes, the cursor should still see only
788 // 1 row
789 Thread t = new Thread() {
790 @Override public void run() {
791 c1.requery();
792 assertEquals(1, c1.getCount());
793 }
794 };
795 t.start();
796 t.join();
797 // should be 2 rows now - including the the row inserted above
798 mDatabase.setTransactionSuccessful();
799 } finally {
800 mDatabase.endTransaction();
801 }
802 c1.close();
803 }
804
805 /**
806 * This test is same as {@link #testTransactionAndWalInterplay1()} except the following:
807 * instead of mDatabase.beginTransactionNonExclusive(), use execSQL("BEGIN transaction")
808 * and instead of mDatabase.endTransaction(), use execSQL("END");
809 */
810 @SmallTest
811 public void testTransactionAndWalInterplay2() throws InterruptedException {
812 createTableAndClearCache();
813 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
814 String sql = "select * from " + TEST_TABLE;
815 Cursor c = mDatabase.rawQuery(sql, null);
816 // should have 1 row in the table
817 assertEquals(1, c.getCount());
818 mDatabase.execSQL("BEGIN transaction");
819 try {
820 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
821 assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
822 "select count(*) from " + TEST_TABLE, null));
823 // requery on the previously opened cursor
824 // cursor should now use the main database connection and see 2 rows
825 c.requery();
826 assertEquals(2, c.getCount());
827 } finally {
828 mDatabase.execSQL("commit;");
829 }
830 c.close();
831
832 // do the same test but now do the requery in a separate thread.
833 createTableAndClearCache();
834 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
835 final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
836 // should have 1 row in the table
837 assertEquals(1, c1.getCount());
838 mDatabase.execSQL("BEGIN transaction");
839 try {
840 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
841 assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
842 "select count(*) from " + TEST_TABLE, null));
843 // query in a different thread. but since the transaction is started using
844 // execSQ() instead of beginTransaction(), cursor's query is considered part of
Vasu Noricc6f5492010-08-23 17:05:25 -0700845 // the same transaction - and hence it should see the above inserted row
Vasu Norice38b982010-07-22 13:57:13 -0700846 Thread t = new Thread() {
847 @Override public void run() {
848 c1.requery();
849 assertEquals(1, c1.getCount());
850 }
851 };
852 t.start();
853 t.join();
854 // should be 2 rows now - including the the row inserted above
855 } finally {
856 mDatabase.execSQL("commit");
857 }
858 c1.close();
859 }
860
861 /**
862 * This test is same as {@link #testTransactionAndWalInterplay2()} except the following:
Vasu Noricc6f5492010-08-23 17:05:25 -0700863 * instead of committing the data, do rollback and make sure the data seen by the query
Vasu Norice38b982010-07-22 13:57:13 -0700864 * within the transaction is now gone.
865 */
866 @SmallTest
Vasu Noricc6f5492010-08-23 17:05:25 -0700867 public void testTransactionAndWalInterplay3() {
Vasu Norice38b982010-07-22 13:57:13 -0700868 createTableAndClearCache();
869 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
870 String sql = "select * from " + TEST_TABLE;
871 Cursor c = mDatabase.rawQuery(sql, null);
872 // should have 1 row in the table
873 assertEquals(1, c.getCount());
874 mDatabase.execSQL("BEGIN transaction");
875 try {
876 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
877 assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
878 "select count(*) from " + TEST_TABLE, null));
879 // requery on the previously opened cursor
880 // cursor should now use the main database connection and see 2 rows
881 c.requery();
882 assertEquals(2, c.getCount());
883 } finally {
884 // rollback the change
885 mDatabase.execSQL("rollback;");
886 }
887 // since the change is rolled back, do the same query again and should now find only 1 row
888 c.requery();
889 assertEquals(1, c.getCount());
890 assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
891 "select count(*) from " + TEST_TABLE, null));
892 c.close();
893 }
Vasu Noricc6f5492010-08-23 17:05:25 -0700894
895 /**
896 * http://b/issue?id=2943028
897 * SQLiteOpenHelper maintains a Singleton even if it is in bad state.
898 */
899 @SmallTest
900 public void testCloseAndReopen() {
901 mDatabase.close();
902 TestOpenHelper helper = new TestOpenHelper(getContext(), DB_NAME, null,
903 CURRENT_DATABASE_VERSION, new DefaultDatabaseErrorHandler());
904 mDatabase = helper.getWritableDatabase();
905 createTableAndClearCache();
906 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
907 Cursor c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
908 assertEquals(1, c.getCount());
909 c.close();
910 mDatabase.close();
911 assertFalse(mDatabase.isOpen());
912 mDatabase = helper.getReadableDatabase();
913 assertTrue(mDatabase.isOpen());
914 c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
915 assertEquals(1, c.getCount());
916 c.close();
917 }
918 private class TestOpenHelper extends SQLiteOpenHelper {
919 public TestOpenHelper(Context context, String name, CursorFactory factory, int version,
920 DatabaseErrorHandler errorHandler) {
921 super(context, name, factory, version, errorHandler);
922 }
923 @Override public void onCreate(SQLiteDatabase db) {}
924 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
925 }
Vasu Nori6c354da2010-04-26 23:33:39 -0700926}