1 /* 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 17 package android.database.sqlite; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.database.DatabaseErrorHandler; 23 import android.database.DatabaseUtils; 24 import android.database.DefaultDatabaseErrorHandler; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteDatabase.CursorFactory; 27 import android.database.sqlite.SQLiteStatement; 28 import android.test.AndroidTestCase; 29 import android.test.suitebuilder.annotation.LargeTest; 30 import android.test.suitebuilder.annotation.MediumTest; 31 import android.test.suitebuilder.annotation.SmallTest; 32 import android.test.suitebuilder.annotation.Suppress; 33 import android.util.Log; 34 import android.util.Pair; 35 36 import java.io.File; 37 import java.util.ArrayList; 38 import java.util.List; 39 40 public class SQLiteDatabaseTest extends AndroidTestCase { 41 private static final String TAG = "DatabaseGeneralTest"; 42 private static final String TEST_TABLE = "test"; 43 private static final int CURRENT_DATABASE_VERSION = 42; 44 private SQLiteDatabase mDatabase; 45 private File mDatabaseFile; 46 private static final int INSERT = 1; 47 private static final int UPDATE = 2; 48 private static final int DELETE = 3; 49 private static final String DB_NAME = "database_test.db"; 50 51 @Override 52 protected void setUp() throws Exception { 53 super.setUp(); 54 dbSetUp(); 55 } 56 57 @Override 58 protected void tearDown() throws Exception { 59 dbTeardown(); 60 super.tearDown(); 61 } 62 63 private void dbTeardown() throws Exception { 64 mDatabase.close(); 65 mDatabaseFile.delete(); 66 } 67 68 private void dbSetUp() throws Exception { 69 File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); 70 mDatabaseFile = new File(dbDir, DB_NAME); 71 if (mDatabaseFile.exists()) { 72 mDatabaseFile.delete(); 73 } 74 mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null); 75 assertNotNull(mDatabase); 76 mDatabase.setVersion(CURRENT_DATABASE_VERSION); 77 } 78 79 @SmallTest 80 public void testEnableWriteAheadLogging() { 81 mDatabase.disableWriteAheadLogging(); 82 assertNull(mDatabase.mConnectionPool); 83 mDatabase.enableWriteAheadLogging(); 84 DatabaseConnectionPool pool = mDatabase.mConnectionPool; 85 assertNotNull(pool); 86 // make the same call again and make sure the pool already setup is not re-created 87 mDatabase.enableWriteAheadLogging(); 88 assertEquals(pool, mDatabase.mConnectionPool); 89 } 90 91 @SmallTest 92 public void testDisableWriteAheadLogging() { 93 mDatabase.execSQL("create table test (i int);"); 94 mDatabase.enableWriteAheadLogging(); 95 assertNotNull(mDatabase.mConnectionPool); 96 // get a pooled database connection 97 SQLiteDatabase db = mDatabase.getDbConnection("select * from test"); 98 assertNotNull(db); 99 assertFalse(mDatabase.equals(db)); 100 assertTrue(db.isOpen()); 101 // disable WAL - which should close connection pool and all pooled connections 102 mDatabase.disableWriteAheadLogging(); 103 assertNull(mDatabase.mConnectionPool); 104 assertFalse(db.isOpen()); 105 } 106 107 @SmallTest 108 public void testCursorsWithClosedDbConnAfterDisableWriteAheadLogging() { 109 mDatabase.disableWriteAheadLogging(); 110 mDatabase.beginTransactionNonExclusive(); 111 mDatabase.execSQL("create table test (i int);"); 112 mDatabase.execSQL("insert into test values(1);"); 113 mDatabase.setTransactionSuccessful(); 114 mDatabase.endTransaction(); 115 mDatabase.enableWriteAheadLogging(); 116 assertNotNull(mDatabase.mConnectionPool); 117 assertEquals(0, mDatabase.mConnectionPool.getSize()); 118 assertEquals(0, mDatabase.mConnectionPool.getFreePoolSize()); 119 // get a cursor which should use pooled database connection 120 Cursor c = mDatabase.rawQuery("select * from test", null); 121 assertEquals(1, c.getCount()); 122 assertEquals(1, mDatabase.mConnectionPool.getSize()); 123 assertEquals(1, mDatabase.mConnectionPool.getFreePoolSize()); 124 SQLiteDatabase db = mDatabase.mConnectionPool.getConnectionList().get(0); 125 assertTrue(mDatabase.mConnectionPool.isDatabaseObjFree(db)); 126 // disable WAL - which should close connection pool and all pooled connections 127 mDatabase.disableWriteAheadLogging(); 128 assertNull(mDatabase.mConnectionPool); 129 assertFalse(db.isOpen()); 130 // cursor data should still be accessible because it is fetching data from CursorWindow 131 c.moveToNext(); 132 assertEquals(1, c.getInt(0)); 133 c.requery(); 134 assertEquals(1, c.getCount()); 135 c.moveToNext(); 136 assertEquals(1, c.getInt(0)); 137 c.close(); 138 } 139 140 /** 141 * a transaction should be started before a standalone-update/insert/delete statement 142 */ 143 @SmallTest 144 public void testStartXactBeforeUpdateSql() throws InterruptedException { 145 runTestForStartXactBeforeUpdateSql(INSERT); 146 runTestForStartXactBeforeUpdateSql(UPDATE); 147 runTestForStartXactBeforeUpdateSql(DELETE); 148 } 149 private void runTestForStartXactBeforeUpdateSql(int stmtType) throws InterruptedException { 150 createTableAndClearCache(); 151 152 ContentValues values = new ContentValues(); 153 // make some changes to data in TEST_TABLE 154 for (int i = 0; i < 5; i++) { 155 values.put("i", i); 156 values.put("j", "i" + System.currentTimeMillis()); 157 mDatabase.insert(TEST_TABLE, null, values); 158 switch (stmtType) { 159 case UPDATE: 160 values.put("j", "u" + System.currentTimeMillis()); 161 mDatabase.update(TEST_TABLE, values, "i = " + i, null); 162 break; 163 case DELETE: 164 mDatabase.delete(TEST_TABLE, "i = 1", null); 165 break; 166 } 167 } 168 // do a query. even though query uses a different database connection, 169 // it should still see the above changes to data because the above standalone 170 // insert/update/deletes are done in transactions automatically. 171 String sql = "select count(*) from " + TEST_TABLE; 172 SQLiteStatement stmt = mDatabase.compileStatement(sql); 173 final int expectedValue = (stmtType == DELETE) ? 4 : 5; 174 assertEquals(expectedValue, stmt.simpleQueryForLong()); 175 stmt.close(); 176 Cursor c = mDatabase.rawQuery(sql, null); 177 assertEquals(1, c.getCount()); 178 c.moveToFirst(); 179 assertEquals(expectedValue, c.getLong(0)); 180 c.close(); 181 182 // do 5 more changes in a transaction but do a query before and after the commit 183 mDatabase.beginTransaction(); 184 for (int i = 10; i < 15; i++) { 185 values.put("i", i); 186 values.put("j", "i" + System.currentTimeMillis()); 187 mDatabase.insert(TEST_TABLE, null, values); 188 switch (stmtType) { 189 case UPDATE: 190 values.put("j", "u" + System.currentTimeMillis()); 191 mDatabase.update(TEST_TABLE, values, "i = " + i, null); 192 break; 193 case DELETE: 194 mDatabase.delete(TEST_TABLE, "i = 1", null); 195 break; 196 } 197 } 198 mDatabase.setTransactionSuccessful(); 199 // do a query before commit - should still have 5 rows 200 // this query should run in a different thread to force it to use a different database 201 // connection 202 Thread t = new Thread() { 203 @Override public void run() { 204 String sql = "select count(*) from " + TEST_TABLE; 205 SQLiteStatement stmt = getDb().compileStatement(sql); 206 assertEquals(expectedValue, stmt.simpleQueryForLong()); 207 stmt.close(); 208 Cursor c = getDb().rawQuery(sql, null); 209 assertEquals(1, c.getCount()); 210 c.moveToFirst(); 211 assertEquals(expectedValue, c.getLong(0)); 212 c.close(); 213 } 214 }; 215 t.start(); 216 // wait until the above thread is done 217 t.join(); 218 // commit and then query. should see changes from the transaction 219 mDatabase.endTransaction(); 220 stmt = mDatabase.compileStatement(sql); 221 final int expectedValue2 = (stmtType == DELETE) ? 9 : 10; 222 assertEquals(expectedValue2, stmt.simpleQueryForLong()); 223 stmt.close(); 224 c = mDatabase.rawQuery(sql, null); 225 assertEquals(1, c.getCount()); 226 c.moveToFirst(); 227 assertEquals(expectedValue2, c.getLong(0)); 228 c.close(); 229 } 230 private synchronized SQLiteDatabase getDb() { 231 return mDatabase; 232 } 233 234 /** 235 * Test to ensure that readers are able to read the database data (old versions) 236 * EVEN WHEN the writer is in a transaction on the same database. 237 *<p> 238 * This test starts 1 Writer and 2 Readers and sets up connection pool for readers 239 * by calling the method {@link SQLiteDatabase#enableWriteAheadLogging()}. 240 * <p> 241 * Writer does the following in a tight loop 242 * <pre> 243 * begin transaction 244 * insert into table_1 245 * insert into table_2 246 * commit 247 * </pre> 248 * <p> 249 * As long a the writer is alive, Readers do the following in a tight loop at the same time 250 * <pre> 251 * Reader_K does "select count(*) from table_K" where K = 1 or 2 252 * </pre> 253 * <p> 254 * The test is run for TIME_TO_RUN_WAL_TEST_FOR sec. 255 * <p> 256 * The test is repeated for different connection-pool-sizes (1..3) 257 * <p> 258 * And at the end of of each test, the following statistics are printed 259 * <ul> 260 * <li>connection-pool-size</li> 261 * <li>number-of-transactions by writer</li> 262 * <li>number of reads by reader_K while the writer is IN or NOT-IN xaction</li> 263 * </ul> 264 */ 265 @LargeTest 266 @Suppress // run this test only if you need to collect the numbers from this test 267 public void testConcurrencyEffectsOfConnPool() throws Exception { 268 // run the test with sqlite WAL enable 269 runConnectionPoolTest(true); 270 271 // run the same test WITHOUT sqlite WAL enabled 272 runConnectionPoolTest(false); 273 } 274 275 private void runConnectionPoolTest(boolean useWal) throws Exception { 276 int M = 3; 277 StringBuilder[] buff = new StringBuilder[M]; 278 for (int i = 0; i < M; i++) { 279 if (useWal) { 280 // set up connection pool 281 mDatabase.enableWriteAheadLogging(); 282 mDatabase.mConnectionPool.setMaxPoolSize(i + 1); 283 } else { 284 mDatabase.disableWriteAheadLogging(); 285 } 286 mDatabase.execSQL("CREATE TABLE t1 (i int, j int);"); 287 mDatabase.execSQL("CREATE TABLE t2 (i int, j int);"); 288 mDatabase.beginTransaction(); 289 for (int k = 0; k < 5; k++) { 290 mDatabase.execSQL("insert into t1 values(?,?);", new String[] {k+"", k+""}); 291 mDatabase.execSQL("insert into t2 values(?,?);", new String[] {k+"", k+""}); 292 } 293 mDatabase.setTransactionSuccessful(); 294 mDatabase.endTransaction(); 295 296 // start a writer 297 Writer w = new Writer(mDatabase); 298 299 // initialize an array of counters to be passed to the readers 300 Reader r1 = new Reader(mDatabase, "t1", w, 0); 301 Reader r2 = new Reader(mDatabase, "t2", w, 1); 302 w.start(); 303 r1.start(); 304 r2.start(); 305 306 // wait for all threads to die 307 w.join(); 308 r1.join(); 309 r2.join(); 310 311 // print the stats 312 int[][] counts = getCounts(); 313 buff[i] = new StringBuilder(); 314 buff[i].append("connpool-size = "); 315 buff[i].append(i + 1); 316 buff[i].append(", num xacts by writer = "); 317 buff[i].append(getNumXacts()); 318 buff[i].append(", num-reads-in-xact/NOT-in-xact by reader1 = "); 319 buff[i].append(counts[0][1] + "/" + counts[0][0]); 320 buff[i].append(", by reader2 = "); 321 buff[i].append(counts[1][1] + "/" + counts[1][0]); 322 323 Log.i(TAG, "done testing for conn-pool-size of " + (i+1)); 324 325 dbTeardown(); 326 dbSetUp(); 327 } 328 Log.i(TAG, "duration of test " + TIME_TO_RUN_WAL_TEST_FOR + " sec"); 329 for (int i = 0; i < M; i++) { 330 Log.i(TAG, buff[i].toString()); 331 } 332 } 333 334 private boolean inXact = false; 335 private int numXacts; 336 private static final int TIME_TO_RUN_WAL_TEST_FOR = 15; // num sec this test should run 337 private int[][] counts = new int[2][2]; 338 339 private synchronized boolean inXact() { 340 return inXact; 341 } 342 343 private synchronized void setInXactFlag(boolean flag) { 344 inXact = flag; 345 } 346 347 private synchronized void setCounts(int readerNum, int[] numReads) { 348 counts[readerNum][0] = numReads[0]; 349 counts[readerNum][1] = numReads[1]; 350 } 351 352 private synchronized int[][] getCounts() { 353 return counts; 354 } 355 356 private synchronized void setNumXacts(int num) { 357 numXacts = num; 358 } 359 360 private synchronized int getNumXacts() { 361 return numXacts; 362 } 363 364 private class Writer extends Thread { 365 private SQLiteDatabase db = null; 366 public Writer(SQLiteDatabase db) { 367 this.db = db; 368 } 369 @Override public void run() { 370 // in a loop, for N sec, do the following 371 // BEGIN transaction 372 // insert into table t1, t2 373 // Commit 374 long now = System.currentTimeMillis(); 375 int k; 376 for (k = 0;(System.currentTimeMillis() - now) / 1000 < TIME_TO_RUN_WAL_TEST_FOR; k++) { 377 db.beginTransactionNonExclusive(); 378 setInXactFlag(true); 379 for (int i = 0; i < 10; i++) { 380 db.execSQL("insert into t1 values(?,?);", new String[] {i+"", i+""}); 381 db.execSQL("insert into t2 values(?,?);", new String[] {i+"", i+""}); 382 } 383 db.setTransactionSuccessful(); 384 setInXactFlag(false); 385 db.endTransaction(); 386 } 387 setNumXacts(k); 388 } 389 } 390 391 private class Reader extends Thread { 392 private SQLiteDatabase db = null; 393 private String table = null; 394 private Writer w = null; 395 private int readerNum; 396 private int[] numReads = new int[2]; 397 public Reader(SQLiteDatabase db, String table, Writer w, int readerNum) { 398 this.db = db; 399 this.table = table; 400 this.w = w; 401 this.readerNum = readerNum; 402 } 403 @Override public void run() { 404 // while the write is alive, in a loop do the query on a table 405 while (w.isAlive()) { 406 for (int i = 0; i < 10; i++) { 407 DatabaseUtils.longForQuery(db, "select count(*) from " + this.table, null); 408 // update count of reads 409 numReads[inXact() ? 1 : 0] += 1; 410 } 411 } 412 setCounts(readerNum, numReads); 413 } 414 } 415 416 public static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram { 417 private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) { 418 super(db, sql); 419 } 420 public static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) { 421 db.lock(); 422 try { 423 return new ClassToTestSqlCompilationAndCaching(db, sql); 424 } finally { 425 db.unlock(); 426 } 427 } 428 } 429 430 @SmallTest 431 public void testLruCachingOfSqliteCompiledSqlObjs() { 432 createTableAndClearCache(); 433 // set cache size 434 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE; 435 mDatabase.setMaxSqlCacheSize(N); 436 437 // do N+1 queries - and when the 0th entry is removed from LRU cache due to the 438 // insertion of (N+1)th entry, make sure 0th entry is closed 439 ArrayList<Integer> stmtObjs = new ArrayList<Integer>(); 440 ArrayList<String> sqlStrings = new ArrayList<String>(); 441 int stmt0 = 0; 442 for (int i = 0; i < N+1; i++) { 443 String s = "insert into test values(" + i + ",?);"; 444 sqlStrings.add(s); 445 ClassToTestSqlCompilationAndCaching c = 446 ClassToTestSqlCompilationAndCaching.create(mDatabase, s); 447 int n = c.getSqlStatementId(); 448 stmtObjs.add(i, n); 449 if (i == 0) { 450 // save the statementId of this obj. we want to make sure it is thrown out of 451 // the cache at the end of this test. 452 stmt0 = n; 453 } 454 c.close(); 455 } 456 // is 0'th entry out of the cache? it should be in the list of statementIds 457 // corresponding to the pre-compiled sql statements to be finalized. 458 assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0)); 459 for (int i = 1; i < N+1; i++) { 460 SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i)); 461 assertNotNull(compSql); 462 assertTrue(stmtObjs.contains(compSql.nStatement)); 463 } 464 } 465 466 @MediumTest 467 public void testDbCloseReleasingAllCachedSql() { 468 mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " + 469 "num1 INTEGER, num2 INTEGER, image BLOB);"); 470 final String statement = "DELETE FROM test WHERE _id=?;"; 471 SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement); 472 statementDoNotClose.bindLong(1, 1); 473 /* do not close statementDoNotClose object. 474 * That should leave it in SQLiteDatabase.mPrograms. 475 * mDatabase.close() in tearDown() should release it. 476 */ 477 } 478 479 private void createTableAndClearCache() { 480 mDatabase.disableWriteAheadLogging(); 481 mDatabase.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE); 482 mDatabase.execSQL("CREATE TABLE " + TEST_TABLE + " (i int, j int);"); 483 mDatabase.enableWriteAheadLogging(); 484 mDatabase.lock(); 485 // flush the above statement from cache and close all the pending statements to be released 486 mDatabase.deallocCachedSqlStatements(); 487 mDatabase.closePendingStatements(); 488 mDatabase.unlock(); 489 assertEquals(0, mDatabase.getQueuedUpStmtList().size()); 490 } 491 492 /** 493 * test to make sure the statement finalizations are not done right away but 494 * piggy-backed onto the next sql statement execution on the same database. 495 */ 496 @SmallTest 497 public void testStatementClose() { 498 createTableAndClearCache(); 499 // fill up statement cache in mDatabase 500 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE; 501 mDatabase.setMaxSqlCacheSize(N); 502 SQLiteStatement stmt; 503 int stmt0Id = 0; 504 for (int i = 0; i < N; i ++) { 505 ClassToTestSqlCompilationAndCaching c = 506 ClassToTestSqlCompilationAndCaching.create(mDatabase, 507 "insert into test values(" + i + ", ?);"); 508 // keep track of 0th entry 509 if (i == 0) { 510 stmt0Id = c.getSqlStatementId(); 511 } 512 c.close(); 513 } 514 515 // add one more to the cache - and the above 'stmt0Id' should fall out of cache 516 ClassToTestSqlCompilationAndCaching stmt1 = 517 ClassToTestSqlCompilationAndCaching.create(mDatabase, 518 "insert into test values(100, ?);"); 519 stmt1.close(); 520 521 // the above close() should have queuedUp the statement for finalization 522 ArrayList<Integer> statementIds = mDatabase.getQueuedUpStmtList(); 523 assertTrue(statementIds.contains(stmt0Id)); 524 525 // execute something to see if this statement gets finalized 526 mDatabase.execSQL("delete from test where i = 10;"); 527 statementIds = mDatabase.getQueuedUpStmtList(); 528 assertFalse(statementIds.contains(stmt0Id)); 529 } 530 531 /** 532 * same as above - except that the statement to be finalized is from Thread # 1. 533 * and it is eventually finalized in Thread # 2 when it executes a SQL statement. 534 * @throws InterruptedException 535 */ 536 @LargeTest 537 public void testStatementCloseDiffThread() throws InterruptedException { 538 createTableAndClearCache(); 539 final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE; 540 mDatabase.setMaxSqlCacheSize(N); 541 // fill up statement cache in mDatabase in a thread 542 Thread t1 = new Thread() { 543 @Override public void run() { 544 SQLiteStatement stmt; 545 for (int i = 0; i < N; i++) { 546 ClassToTestSqlCompilationAndCaching c = 547 ClassToTestSqlCompilationAndCaching.create(getDb(), 548 "insert into test values(" + i + ", ?);"); 549 // keep track of 0th entry 550 if (i == 0) { 551 stmt0Id = c.getSqlStatementId(); 552 } 553 c.close(); 554 } 555 } 556 }; 557 t1.start(); 558 // wait for the thread to finish 559 t1.join(); 560 // mDatabase shouldn't have any statements to be released 561 assertEquals(0, mDatabase.getQueuedUpStmtList().size()); 562 563 // add one more to the cache - and the above 'stmt0Id' should fall out of cache 564 // just for the heck of it, do it in a separate thread 565 Thread t2 = new Thread() { 566 @Override public void run() { 567 ClassToTestSqlCompilationAndCaching stmt1 = 568 ClassToTestSqlCompilationAndCaching.create(getDb(), 569 "insert into test values(100, ?);"); 570 stmt1.bindLong(1, 1); 571 stmt1.close(); 572 } 573 }; 574 t2.start(); 575 t2.join(); 576 577 // close() in the above thread should have queuedUp the stmt0Id for finalization 578 ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList(); 579 assertTrue(statementIds.contains(getStmt0Id())); 580 assertEquals(1, statementIds.size()); 581 582 // execute something to see if this statement gets finalized 583 // again do it in a separate thread 584 Thread t3 = new Thread() { 585 @Override public void run() { 586 getDb().execSQL("delete from test where i = 10;"); 587 } 588 }; 589 t3.start(); 590 t3.join(); 591 592 // is the statement finalized? 593 statementIds = getDb().getQueuedUpStmtList(); 594 assertFalse(statementIds.contains(getStmt0Id())); 595 } 596 597 private volatile int stmt0Id = 0; 598 private synchronized int getStmt0Id() { 599 return this.stmt0Id; 600 } 601 602 /** 603 * same as above - except that the queue of statements to be finalized are finalized 604 * by database close() operation. 605 */ 606 @LargeTest 607 public void testStatementCloseByDbClose() throws InterruptedException { 608 createTableAndClearCache(); 609 // fill up statement cache in mDatabase in a thread 610 Thread t1 = new Thread() { 611 @Override public void run() { 612 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE; 613 getDb().setMaxSqlCacheSize(N); 614 SQLiteStatement stmt; 615 for (int i = 0; i < N; i ++) { 616 ClassToTestSqlCompilationAndCaching c = 617 ClassToTestSqlCompilationAndCaching.create(getDb(), 618 "insert into test values(" + i + ", ?);"); 619 // keep track of 0th entry 620 if (i == 0) { 621 stmt0Id = c.getSqlStatementId(); 622 } 623 c.close(); 624 } 625 } 626 }; 627 t1.start(); 628 // wait for the thread to finish 629 t1.join(); 630 631 // add one more to the cache - and the above 'stmt0Id' should fall out of cache 632 // just for the heck of it, do it in a separate thread 633 Thread t2 = new Thread() { 634 @Override public void run() { 635 ClassToTestSqlCompilationAndCaching stmt1 = 636 ClassToTestSqlCompilationAndCaching.create(getDb(), 637 "insert into test values(100, ?);"); 638 stmt1.bindLong(1, 1); 639 stmt1.close(); 640 } 641 }; 642 t2.start(); 643 t2.join(); 644 645 // close() in the above thread should have queuedUp the statement for finalization 646 ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList(); 647 assertTrue(getStmt0Id() > 0); 648 assertTrue(statementIds.contains(stmt0Id)); 649 assertEquals(1, statementIds.size()); 650 651 // close the database. everything from mClosedStatementIds in mDatabase 652 // should be finalized and cleared from the list 653 // again do it in a separate thread 654 Thread t3 = new Thread() { 655 @Override public void run() { 656 getDb().close(); 657 } 658 }; 659 t3.start(); 660 t3.join(); 661 662 // check mClosedStatementIds in mDatabase. it should be empty 663 statementIds = getDb().getQueuedUpStmtList(); 664 assertEquals(0, statementIds.size()); 665 } 666 667 /** 668 * This test tests usage execSQL() to begin transaction works in the following way 669 * Thread #1 does 670 * execSQL("begin transaction"); 671 * insert() 672 * Thread # 2 673 * query() 674 * Thread#1 ("end transaction") 675 * Thread # 2 query will execute - because java layer will not have locked the SQLiteDatabase 676 * object and sqlite will consider this query to be part of the transaction. 677 * 678 * but if thread # 1 uses beginTransaction() instead of execSQL() to start transaction, 679 * then Thread # 2's query will have been blocked by java layer 680 * until Thread#1 ends transaction. 681 * 682 * @throws InterruptedException 683 */ 684 @SmallTest 685 public void testExecSqlToStartAndEndTransaction() throws InterruptedException { 686 runExecSqlToStartAndEndTransaction("END"); 687 // same as above, instead now do "COMMIT" or "ROLLBACK" instead of "END" transaction 688 runExecSqlToStartAndEndTransaction("COMMIT"); 689 runExecSqlToStartAndEndTransaction("ROLLBACK"); 690 } 691 private void runExecSqlToStartAndEndTransaction(String str) throws InterruptedException { 692 createTableAndClearCache(); 693 // disable WAL just so queries and updates use the same database connection 694 mDatabase.disableWriteAheadLogging(); 695 mDatabase.execSQL("BEGIN transaction"); 696 // even though mDatabase.beginTransaction() is not called to start transaction, 697 // mDatabase connection should now be in transaction as a result of 698 // mDatabase.execSQL("BEGIN transaction") 699 // but mDatabase.mLock should not be held by any thread 700 assertTrue(mDatabase.inTransaction()); 701 assertFalse(mDatabase.isDbLockedByCurrentThread()); 702 assertFalse(mDatabase.isDbLockedByOtherThreads()); 703 assertTrue(mDatabase.amIInTransaction()); 704 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 705 assertTrue(mDatabase.inTransaction()); 706 assertFalse(mDatabase.isDbLockedByCurrentThread()); 707 assertFalse(mDatabase.isDbLockedByOtherThreads()); 708 assertTrue(mDatabase.amIInTransaction()); 709 Thread t = new Thread() { 710 @Override public void run() { 711 assertTrue(mDatabase.amIInTransaction()); 712 assertEquals(999, DatabaseUtils.longForQuery(getDb(), 713 "select j from " + TEST_TABLE + " WHERE i = 10", null)); 714 assertTrue(getDb().inTransaction()); 715 assertFalse(getDb().isDbLockedByCurrentThread()); 716 assertFalse(getDb().isDbLockedByOtherThreads()); 717 assertTrue(mDatabase.amIInTransaction()); 718 } 719 }; 720 t.start(); 721 t.join(); 722 assertTrue(mDatabase.amIInTransaction()); 723 assertTrue(mDatabase.inTransaction()); 724 assertFalse(mDatabase.isDbLockedByCurrentThread()); 725 assertFalse(mDatabase.isDbLockedByOtherThreads()); 726 mDatabase.execSQL(str); 727 assertFalse(mDatabase.amIInTransaction()); 728 assertFalse(mDatabase.inTransaction()); 729 assertFalse(mDatabase.isDbLockedByCurrentThread()); 730 assertFalse(mDatabase.isDbLockedByOtherThreads()); 731 } 732 733 /** 734 * test the following 735 * http://b/issue?id=2871037 736 * Cursor cursor = db.query(...); 737 * // with WAL enabled, the above uses a pooled database connection 738 * db.beginTransaction() 739 * try { 740 * db.insert(......); 741 * cursor.requery(); 742 * // since the cursor uses pooled database connection, the above requery 743 * // will not return the results that were inserted above since the insert is 744 * // done using main database connection AND the transaction is not committed yet. 745 * // fix is to make the above cursor use the main database connection - and NOT 746 * // the pooled database connection 747 * db.setTransactionSuccessful() 748 * } finally { 749 * db.endTransaction() 750 * } 751 * 752 * @throws InterruptedException 753 */ 754 @SmallTest 755 public void testTransactionAndWalInterplay1() throws InterruptedException { 756 createTableAndClearCache(); 757 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 758 String sql = "select * from " + TEST_TABLE; 759 Cursor c = mDatabase.rawQuery(sql, null); 760 // should have 1 row in the table 761 assertEquals(1, c.getCount()); 762 mDatabase.beginTransactionNonExclusive(); 763 try { 764 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);"); 765 assertEquals(2, DatabaseUtils.longForQuery(mDatabase, 766 "select count(*) from " + TEST_TABLE, null)); 767 // requery on the previously opened cursor 768 // cursor should now use the main database connection and see 2 rows 769 c.requery(); 770 assertEquals(2, c.getCount()); 771 mDatabase.setTransactionSuccessful(); 772 } finally { 773 mDatabase.endTransaction(); 774 } 775 c.close(); 776 777 // do the same test but now do the requery in a separate thread. 778 createTableAndClearCache(); 779 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 780 final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null); 781 // should have 1 row in the table 782 assertEquals(1, c1.getCount()); 783 mDatabase.beginTransactionNonExclusive(); 784 try { 785 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);"); 786 assertEquals(2, DatabaseUtils.longForQuery(mDatabase, 787 "select count(*) from " + TEST_TABLE, null)); 788 // query in a different thread. that causes the cursor to use a pooled connection 789 // and since this thread hasn't committed its changes, the cursor should still see only 790 // 1 row 791 Thread t = new Thread() { 792 @Override public void run() { 793 c1.requery(); 794 assertEquals(1, c1.getCount()); 795 } 796 }; 797 t.start(); 798 t.join(); 799 // should be 2 rows now - including the the row inserted above 800 mDatabase.setTransactionSuccessful(); 801 } finally { 802 mDatabase.endTransaction(); 803 } 804 c1.close(); 805 } 806 807 /** 808 * This test is same as {@link #testTransactionAndWalInterplay1()} except the following: 809 * instead of mDatabase.beginTransactionNonExclusive(), use execSQL("BEGIN transaction") 810 * and instead of mDatabase.endTransaction(), use execSQL("END"); 811 */ 812 @SmallTest 813 public void testTransactionAndWalInterplay2() throws InterruptedException { 814 createTableAndClearCache(); 815 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 816 String sql = "select * from " + TEST_TABLE; 817 Cursor c = mDatabase.rawQuery(sql, null); 818 // should have 1 row in the table 819 assertEquals(1, c.getCount()); 820 mDatabase.execSQL("BEGIN transaction"); 821 try { 822 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);"); 823 assertEquals(2, DatabaseUtils.longForQuery(mDatabase, 824 "select count(*) from " + TEST_TABLE, null)); 825 // requery on the previously opened cursor 826 // cursor should now use the main database connection and see 2 rows 827 c.requery(); 828 assertEquals(2, c.getCount()); 829 } finally { 830 mDatabase.execSQL("commit;"); 831 } 832 c.close(); 833 834 // do the same test but now do the requery in a separate thread. 835 createTableAndClearCache(); 836 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 837 final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null); 838 // should have 1 row in the table 839 assertEquals(1, c1.getCount()); 840 mDatabase.execSQL("BEGIN transaction"); 841 try { 842 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);"); 843 assertEquals(2, DatabaseUtils.longForQuery(mDatabase, 844 "select count(*) from " + TEST_TABLE, null)); 845 // query in a different thread. but since the transaction is started using 846 // execSQ() instead of beginTransaction(), cursor's query is considered part of 847 // the same transaction - and hence it should see the above inserted row 848 Thread t = new Thread() { 849 @Override public void run() { 850 c1.requery(); 851 assertEquals(1, c1.getCount()); 852 } 853 }; 854 t.start(); 855 t.join(); 856 // should be 2 rows now - including the the row inserted above 857 } finally { 858 mDatabase.execSQL("commit"); 859 } 860 c1.close(); 861 } 862 863 /** 864 * This test is same as {@link #testTransactionAndWalInterplay2()} except the following: 865 * instead of committing the data, do rollback and make sure the data seen by the query 866 * within the transaction is now gone. 867 */ 868 @SmallTest 869 public void testTransactionAndWalInterplay3() { 870 createTableAndClearCache(); 871 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 872 String sql = "select * from " + TEST_TABLE; 873 Cursor c = mDatabase.rawQuery(sql, null); 874 // should have 1 row in the table 875 assertEquals(1, c.getCount()); 876 mDatabase.execSQL("BEGIN transaction"); 877 try { 878 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);"); 879 assertEquals(2, DatabaseUtils.longForQuery(mDatabase, 880 "select count(*) from " + TEST_TABLE, null)); 881 // requery on the previously opened cursor 882 // cursor should now use the main database connection and see 2 rows 883 c.requery(); 884 assertEquals(2, c.getCount()); 885 } finally { 886 // rollback the change 887 mDatabase.execSQL("rollback;"); 888 } 889 // since the change is rolled back, do the same query again and should now find only 1 row 890 c.requery(); 891 assertEquals(1, c.getCount()); 892 assertEquals(1, DatabaseUtils.longForQuery(mDatabase, 893 "select count(*) from " + TEST_TABLE, null)); 894 c.close(); 895 } 896 897 @SmallTest 898 public void testAttachDb() { 899 String newDb = "/sdcard/mydata.db"; 900 File f = new File(newDb); 901 if (f.exists()) { 902 f.delete(); 903 } 904 assertFalse(f.exists()); 905 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(newDb, null); 906 db.execSQL("create table test1 (i int);"); 907 db.execSQL("insert into test1 values(1);"); 908 db.execSQL("insert into test1 values(11);"); 909 Cursor c = null; 910 try { 911 c = db.rawQuery("select * from test1", null); 912 int count = c.getCount(); 913 Log.i(TAG, "count: " + count); 914 assertEquals(2, count); 915 } finally { 916 c.close(); 917 db.close(); 918 c = null; 919 } 920 921 mDatabase.execSQL("attach database ? as newDb" , new String[]{newDb}); 922 Cursor c1 = null; 923 try { 924 c1 = mDatabase.rawQuery("select * from newDb.test1", null); 925 assertEquals(2, c1.getCount()); 926 } catch (Exception e) { 927 fail("unexpected exception: " + e.getMessage()); 928 } finally { 929 if (c1 != null) { 930 c1.close(); 931 } 932 } 933 List<Pair<String, String>> dbs = mDatabase.getAttachedDbs(); 934 for (Pair<String, String> p: dbs) { 935 Log.i(TAG, "attached dbs: " + p.first + " : " + p.second); 936 } 937 assertEquals(2, dbs.size()); 938 } 939 940 /** 941 * http://b/issue?id=2943028 942 * SQLiteOpenHelper maintains a Singleton even if it is in bad state. 943 */ 944 @SmallTest 945 public void testCloseAndReopen() { 946 mDatabase.close(); 947 TestOpenHelper helper = new TestOpenHelper(getContext(), DB_NAME, null, 948 CURRENT_DATABASE_VERSION, new DefaultDatabaseErrorHandler()); 949 mDatabase = helper.getWritableDatabase(); 950 createTableAndClearCache(); 951 mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);"); 952 Cursor c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null); 953 assertEquals(1, c.getCount()); 954 c.close(); 955 mDatabase.close(); 956 assertFalse(mDatabase.isOpen()); 957 mDatabase = helper.getReadableDatabase(); 958 assertTrue(mDatabase.isOpen()); 959 c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null); 960 assertEquals(1, c.getCount()); 961 c.close(); 962 } 963 private class TestOpenHelper extends SQLiteOpenHelper { 964 public TestOpenHelper(Context context, String name, CursorFactory factory, int version, 965 DatabaseErrorHandler errorHandler) { 966 super(context, name, factory, version, errorHandler); 967 } 968 @Override public void onCreate(SQLiteDatabase db) {} 969 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} 970 } 971 } 972