blob: 955336af19aa3bdcfc22383dc42a2d9f74ea8bb3 [file] [log] [blame]
/*
* 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);
}
}