blob: 5ef8d11c73746aaded0935df62698e2d9c7c022a [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;
Vasu Noricc1eaf62011-03-14 19:22:16 -070034import android.util.Pair;
Vasu Nori6c354da2010-04-26 23:33:39 -070035
36import java.io.File;
Vasu Nori75010102010-07-01 16:23:06 -070037import java.util.ArrayList;
Vasu Noricc1eaf62011-03-14 19:22:16 -070038import java.util.List;
Vasu Nori6c354da2010-04-26 23:33:39 -070039
40public class SQLiteDatabaseTest extends AndroidTestCase {
41 private static final String TAG = "DatabaseGeneralTest";
Vasu Norice38b982010-07-22 13:57:13 -070042 private static final String TEST_TABLE = "test";
Vasu Nori6c354da2010-04-26 23:33:39 -070043 private static final int CURRENT_DATABASE_VERSION = 42;
44 private SQLiteDatabase mDatabase;
45 private File mDatabaseFile;
Vasu Norice38b982010-07-22 13:57:13 -070046 private static final int INSERT = 1;
47 private static final int UPDATE = 2;
48 private static final int DELETE = 3;
Vasu Noricc6f5492010-08-23 17:05:25 -070049 private static final String DB_NAME = "database_test.db";
Vasu Nori6c354da2010-04-26 23:33:39 -070050
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);
Vasu Noricc6f5492010-08-23 17:05:25 -070070 mDatabaseFile = new File(dbDir, DB_NAME);
Vasu Nori6c354da2010-04-26 23:33:39 -070071 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() {
Vasu Nori2827d6d2010-07-04 00:26:18 -070081 mDatabase.disableWriteAheadLogging();
Vasu Nori6c354da2010-04-26 23:33:39 -070082 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
Vasu Nori65a88832010-07-16 15:14:08 -070092 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
Vasu Nori6c354da2010-04-26 23:33:39 -0700140 /**
Vasu Norice38b982010-07-22 13:57:13 -0700141 * 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 /**
Vasu Nori6c354da2010-04-26 23:33:39 -0700235 * 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
Vasu Nori75010102010-07-01 16:23:06 -0700266 @Suppress // run this test only if you need to collect the numbers from this test
Vasu Nori6c354da2010-04-26 23:33:39 -0700267 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();
Vasu Noriffe06122010-09-27 12:32:57 -0700282 mDatabase.mConnectionPool.setMaxPoolSize(i + 1);
Vasu Norice38b982010-07-22 13:57:13 -0700283 } else {
284 mDatabase.disableWriteAheadLogging();
Vasu Nori6c354da2010-04-26 23:33:39 -0700285 }
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;
Vasu Nori57feb5d2010-06-22 10:39:04 -0700336 private static final int TIME_TO_RUN_WAL_TEST_FOR = 15; // num sec this test should run
Vasu Nori6c354da2010-04-26 23:33:39 -0700337 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 }
Vasu Nori75010102010-07-01 16:23:06 -0700415
Vasu Nori65a88832010-07-16 15:14:08 -0700416 public static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram {
Vasu Norie25539f2010-07-08 17:06:13 -0700417 private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) {
418 super(db, sql);
419 }
Vasu Nori65a88832010-07-16 15:14:08 -0700420 public static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) {
Vasu Norie25539f2010-07-08 17:06:13 -0700421 db.lock();
422 try {
423 return new ClassToTestSqlCompilationAndCaching(db, sql);
424 } finally {
425 db.unlock();
426 }
427 }
428 }
429
Vasu Norib729dcc2010-09-14 11:35:49 -0700430 @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
Vasu Nori59d60422010-07-03 15:37:21 -0700466 @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);
Vasu Nori2827d6d2010-07-04 00:26:18 -0700472 statementDoNotClose.bindLong(1, 1);
Vasu Nori59d60422010-07-03 15:37:21 -0700473 /* do not close statementDoNotClose object.
474 * That should leave it in SQLiteDatabase.mPrograms.
475 * mDatabase.close() in tearDown() should release it.
476 */
477 }
478
Vasu Norice38b982010-07-22 13:57:13 -0700479 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
Vasu Norib729dcc2010-09-14 11:35:49 -0700486 mDatabase.deallocCachedSqlStatements();
Vasu Norice38b982010-07-22 13:57:13 -0700487 mDatabase.closePendingStatements();
488 mDatabase.unlock();
489 assertEquals(0, mDatabase.getQueuedUpStmtList().size());
490 }
491
Vasu Nori59d60422010-07-03 15:37:21 -0700492 /**
493 * test to make sure the statement finalizations are not done right away but
Vasu Nori57feb5d2010-06-22 10:39:04 -0700494 * piggy-backed onto the next sql statement execution on the same database.
Vasu Nori59d60422010-07-03 15:37:21 -0700495 */
496 @SmallTest
497 public void testStatementClose() {
Vasu Norice38b982010-07-22 13:57:13 -0700498 createTableAndClearCache();
499 // fill up statement cache in mDatabase
Vasu Norie25539f2010-07-08 17:06:13 -0700500 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
Vasu Nori59d60422010-07-03 15:37:21 -0700501 mDatabase.setMaxSqlCacheSize(N);
502 SQLiteStatement stmt;
503 int stmt0Id = 0;
504 for (int i = 0; i < N; i ++) {
Vasu Norie25539f2010-07-08 17:06:13 -0700505 ClassToTestSqlCompilationAndCaching c =
506 ClassToTestSqlCompilationAndCaching.create(mDatabase,
507 "insert into test values(" + i + ", ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700508 // keep track of 0th entry
509 if (i == 0) {
Vasu Norie25539f2010-07-08 17:06:13 -0700510 stmt0Id = c.getSqlStatementId();
Vasu Nori59d60422010-07-03 15:37:21 -0700511 }
Vasu Norie25539f2010-07-08 17:06:13 -0700512 c.close();
Vasu Nori59d60422010-07-03 15:37:21 -0700513 }
514
515 // add one more to the cache - and the above 'stmt0Id' should fall out of cache
Vasu Norie25539f2010-07-08 17:06:13 -0700516 ClassToTestSqlCompilationAndCaching stmt1 =
517 ClassToTestSqlCompilationAndCaching.create(mDatabase,
518 "insert into test values(100, ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700519 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();
Vasu Norice38b982010-07-22 13:57:13 -0700528 assertFalse(statementIds.contains(stmt0Id));
Vasu Nori59d60422010-07-03 15:37:21 -0700529 }
530
531 /**
532 * same as above - except that the statement to be finalized is from Thread # 1.
Vasu Nori57feb5d2010-06-22 10:39:04 -0700533 * and it is eventually finalized in Thread # 2 when it executes a SQL statement.
Vasu Nori59d60422010-07-03 15:37:21 -0700534 * @throws InterruptedException
535 */
536 @LargeTest
537 public void testStatementCloseDiffThread() throws InterruptedException {
Vasu Norice38b982010-07-22 13:57:13 -0700538 createTableAndClearCache();
539 final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
540 mDatabase.setMaxSqlCacheSize(N);
Vasu Nori59d60422010-07-03 15:37:21 -0700541 // fill up statement cache in mDatabase in a thread
542 Thread t1 = new Thread() {
543 @Override public void run() {
Vasu Nori59d60422010-07-03 15:37:21 -0700544 SQLiteStatement stmt;
Vasu Norice38b982010-07-22 13:57:13 -0700545 for (int i = 0; i < N; i++) {
Vasu Norie25539f2010-07-08 17:06:13 -0700546 ClassToTestSqlCompilationAndCaching c =
Vasu Norice38b982010-07-22 13:57:13 -0700547 ClassToTestSqlCompilationAndCaching.create(getDb(),
Vasu Norie25539f2010-07-08 17:06:13 -0700548 "insert into test values(" + i + ", ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700549 // keep track of 0th entry
550 if (i == 0) {
Vasu Norie25539f2010-07-08 17:06:13 -0700551 stmt0Id = c.getSqlStatementId();
Vasu Nori59d60422010-07-03 15:37:21 -0700552 }
Vasu Norie25539f2010-07-08 17:06:13 -0700553 c.close();
Vasu Nori59d60422010-07-03 15:37:21 -0700554 }
555 }
556 };
557 t1.start();
558 // wait for the thread to finish
559 t1.join();
Vasu Norice38b982010-07-22 13:57:13 -0700560 // mDatabase shouldn't have any statements to be released
561 assertEquals(0, mDatabase.getQueuedUpStmtList().size());
Vasu Nori59d60422010-07-03 15:37:21 -0700562
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() {
Vasu Norie25539f2010-07-08 17:06:13 -0700567 ClassToTestSqlCompilationAndCaching stmt1 =
Vasu Norice38b982010-07-22 13:57:13 -0700568 ClassToTestSqlCompilationAndCaching.create(getDb(),
569 "insert into test values(100, ?);");
570 stmt1.bindLong(1, 1);
Vasu Nori59d60422010-07-03 15:37:21 -0700571 stmt1.close();
572 }
573 };
574 t2.start();
575 t2.join();
576
Vasu Norice38b982010-07-22 13:57:13 -0700577 // close() in the above thread should have queuedUp the stmt0Id for finalization
578 ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
579 assertTrue(statementIds.contains(getStmt0Id()));
Vasu Nori59d60422010-07-03 15:37:21 -0700580 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() {
Vasu Norice38b982010-07-22 13:57:13 -0700586 getDb().execSQL("delete from test where i = 10;");
Vasu Nori59d60422010-07-03 15:37:21 -0700587 }
588 };
589 t3.start();
590 t3.join();
591
592 // is the statement finalized?
Vasu Norice38b982010-07-22 13:57:13 -0700593 statementIds = getDb().getQueuedUpStmtList();
594 assertFalse(statementIds.contains(getStmt0Id()));
Vasu Nori59d60422010-07-03 15:37:21 -0700595 }
596
597 private volatile int stmt0Id = 0;
Vasu Nori59d60422010-07-03 15:37:21 -0700598 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 {
Vasu Norice38b982010-07-22 13:57:13 -0700608 createTableAndClearCache();
Vasu Nori59d60422010-07-03 15:37:21 -0700609 // fill up statement cache in mDatabase in a thread
610 Thread t1 = new Thread() {
611 @Override public void run() {
Vasu Norie25539f2010-07-08 17:06:13 -0700612 int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
Vasu Norice38b982010-07-22 13:57:13 -0700613 getDb().setMaxSqlCacheSize(N);
Vasu Nori59d60422010-07-03 15:37:21 -0700614 SQLiteStatement stmt;
615 for (int i = 0; i < N; i ++) {
Vasu Norie25539f2010-07-08 17:06:13 -0700616 ClassToTestSqlCompilationAndCaching c =
Vasu Norice38b982010-07-22 13:57:13 -0700617 ClassToTestSqlCompilationAndCaching.create(getDb(),
Vasu Norie25539f2010-07-08 17:06:13 -0700618 "insert into test values(" + i + ", ?);");
Vasu Nori59d60422010-07-03 15:37:21 -0700619 // keep track of 0th entry
620 if (i == 0) {
Vasu Norie25539f2010-07-08 17:06:13 -0700621 stmt0Id = c.getSqlStatementId();
Vasu Nori59d60422010-07-03 15:37:21 -0700622 }
Vasu Norie25539f2010-07-08 17:06:13 -0700623 c.close();
Vasu Nori59d60422010-07-03 15:37:21 -0700624 }
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() {
Vasu Norie25539f2010-07-08 17:06:13 -0700635 ClassToTestSqlCompilationAndCaching stmt1 =
Vasu Norice38b982010-07-22 13:57:13 -0700636 ClassToTestSqlCompilationAndCaching.create(getDb(),
Vasu Norie25539f2010-07-08 17:06:13 -0700637 "insert into test values(100, ?);");
Vasu Nori2827d6d2010-07-04 00:26:18 -0700638 stmt1.bindLong(1, 1);
Vasu Nori59d60422010-07-03 15:37:21 -0700639 stmt1.close();
640 }
641 };
642 t2.start();
643 t2.join();
644
645 // close() in the above thread should have queuedUp the statement for finalization
Vasu Norice38b982010-07-22 13:57:13 -0700646 ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
Vasu Nori59d60422010-07-03 15:37:21 -0700647 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() {
Vasu Norice38b982010-07-22 13:57:13 -0700656 getDb().close();
Vasu Nori59d60422010-07-03 15:37:21 -0700657 }
658 };
659 t3.start();
660 t3.join();
661
662 // check mClosedStatementIds in mDatabase. it should be empty
Vasu Norice38b982010-07-22 13:57:13 -0700663 statementIds = getDb().getQueuedUpStmtList();
Vasu Nori59d60422010-07-03 15:37:21 -0700664 assertEquals(0, statementIds.size());
665 }
Vasu Norice38b982010-07-22 13:57:13 -0700666
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
Vasu Noricc6f5492010-08-23 17:05:25 -0700847 // the same transaction - and hence it should see the above inserted row
Vasu Norice38b982010-07-22 13:57:13 -0700848 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:
Vasu Noricc6f5492010-08-23 17:05:25 -0700865 * instead of committing the data, do rollback and make sure the data seen by the query
Vasu Norice38b982010-07-22 13:57:13 -0700866 * within the transaction is now gone.
867 */
868 @SmallTest
Vasu Noricc6f5492010-08-23 17:05:25 -0700869 public void testTransactionAndWalInterplay3() {
Vasu Norice38b982010-07-22 13:57:13 -0700870 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 }
Vasu Noricc6f5492010-08-23 17:05:25 -0700896
Vasu Noricc1eaf62011-03-14 19:22:16 -0700897 @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
Vasu Noricc6f5492010-08-23 17:05:25 -0700940 /**
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 }
Vasu Nori6c354da2010-04-26 23:33:39 -0700971}