/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database.sqlite; import android.content.Context; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import java.io.File; import java.util.Random; import java.util.concurrent.locks.ReentrantLock; public class SQLiteStatementTest extends AndroidTestCase { private SQLiteDatabase mDatabase; private File mDatabaseFile; @Override protected void setUp() throws Exception { super.setUp(); File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); mDatabaseFile = new File(dbDir, "database_test.db"); if (mDatabaseFile.exists()) { mDatabaseFile.delete(); } mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); assertNotNull(mDatabase); } @Override protected void tearDown() throws Exception { mDatabase.close(); mDatabaseFile.delete(); super.tearDown(); } /** * Start 2 threads to repeatedly execute the above SQL statement. * Even though 2 threads are executing the same SQL, they each should get their own copy of * prepared SQL statement id and there SHOULD NOT be an error from sqlite or android. * @throws InterruptedException thrown if the test threads started by this test are interrupted */ @LargeTest public void testUseOfSameSqlStatementBy2Threads() throws InterruptedException { mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);"); final String stmt = "SELECT * FROM test_pstmt WHERE i = ?"; class RunStmtThread extends Thread { @Override public void run() { // do it enough times to make sure there are no corner cases going untested for (int i = 0; i < 1000; i++) { SQLiteStatement s1 = mDatabase.compileStatement(stmt); s1.bindLong(1, i); s1.execute(); s1.close(); } } } RunStmtThread t1 = new RunStmtThread(); t1.start(); RunStmtThread t2 = new RunStmtThread(); t2.start(); while (t1.isAlive() || t2.isAlive()) { Thread.sleep(10); } } /** * A simple test: start 2 threads to repeatedly execute the same {@link SQLiteStatement}. * The 2 threads take turns to use the {@link SQLiteStatement}; i.e., it is NOT in use * by both the threads at the same time. * * @throws InterruptedException thrown if the test threads started by this test are interrupted */ @LargeTest public void testUseOfSameSqliteStatementBy2Threads() throws InterruptedException { mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);"); final String stmt = "SELECT * FROM test_pstmt WHERE i = ?"; final SQLiteStatement s1 = mDatabase.compileStatement(stmt); class RunStmtThread extends Thread { @Override public void run() { // do it enough times to make sure there are no corner cases going untested for (int i = 0; i < 1000; i++) { lock(); try { s1.bindLong(1, i); s1.execute(); } finally { unlock(); } Thread.yield(); } } } RunStmtThread t1 = new RunStmtThread(); t1.start(); RunStmtThread t2 = new RunStmtThread(); t2.start(); while (t1.isAlive() || t2.isAlive()) { Thread.sleep(10); } } /** Synchronize on this when accessing the SqliteStatemet in the above */ private final ReentrantLock mLock = new ReentrantLock(true); private void lock() { mLock.lock(); } private void unlock() { mLock.unlock(); } /** * Tests the following: a {@link SQLiteStatement} object should not refer to a * pre-compiled SQL statement id except in during the period of binding the arguments * and executing the SQL statement. */ @LargeTest public void testReferenceToPrecompiledStatementId() { mDatabase.execSQL("create table t (i int, j text);"); verifyReferenceToPrecompiledStatementId(false); verifyReferenceToPrecompiledStatementId(true); // a small stress test to make sure there are no side effects of // the acquire & release of pre-compiled statement id by SQLiteStatement object. for (int i = 0; i < 100; i++) { verifyReferenceToPrecompiledStatementId(false); verifyReferenceToPrecompiledStatementId(true); } } @SuppressWarnings("deprecation") private void verifyReferenceToPrecompiledStatementId(boolean wal) { if (wal) { mDatabase.enableWriteAheadLogging(); } else { mDatabase.disableWriteAheadLogging(); } // test with INSERT statement - doesn't use connection pool, if WAL is set SQLiteStatement stmt = mDatabase.compileStatement("insert into t values(?,?);"); assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); // sql statement should not be compiled yet assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); int colValue = new Random().nextInt(); stmt.bindLong(1, colValue); // verify that the sql statement is still not compiled assertEquals(0, stmt.getSqlStatementId()); // should still be using the mDatabase connection - verify assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); stmt.bindString(2, "blah" + colValue); // verify that the sql statement is still not compiled assertEquals(0, stmt.getSqlStatementId()); assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); stmt.executeInsert(); // now that the statement is executed, pre-compiled statement should be released assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); stmt.close(); // pre-compiled SQL statement should still remain released from this object assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); // but the database handle should still be the same assertEquals(mDatabase, stmt.mDatabase); // test with a SELECT statement - uses connection pool if WAL is set stmt = mDatabase.compileStatement("select i from t where j=?;"); // sql statement should not be compiled yet assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); stmt.bindString(1, "blah" + colValue); // verify that the sql statement is still not compiled assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); // execute the statement Long l = stmt.simpleQueryForLong(); assertEquals(colValue, l.intValue()); // now that the statement is executed, pre-compiled statement should be released assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); stmt.close(); // pre-compiled SQL statement should still remain released from this object assertEquals(0, stmt.nStatement); assertEquals(0, stmt.getSqlStatementId()); // but the database handle should still remain attached to the statement assertEquals(mDatabase.mNativeHandle, stmt.nHandle); assertEquals(mDatabase, stmt.mDatabase); } }