blob: a11abf6ad14d4d96f6bf459327e6f5f93e4c8339 [file] [log] [blame]
/*
* Copyright (C) 2009 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.cts;
import android.content.Context;
import android.database.AbstractCursor;
import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.database.SQLException;
import android.database.StaleDataException;
import android.database.sqlite.SQLiteBlobTooBigException;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDirectCursorDriver;
import android.test.AndroidTestCase;
import java.util.Arrays;
/**
* Test {@link AbstractCursor}.
*/
public class SQLiteCursorTest extends AndroidTestCase {
private SQLiteDatabase mDatabase;
private static final String[] COLUMNS = new String[] { "_id", "number_1", "number_2" };
private static final String TABLE_NAME = "test";
private static final String TABLE_COLUMNS = " number_1 INTEGER, number_2 INTEGER";
private static final int DEFAULT_TABLE_VALUE_BEGINS = 1;
private static final int TEST_COUNT = 10;
private static final String TEST_SQL = "SELECT * FROM test ORDER BY number_1";
private static final String DATABASE_FILE = "database_test.db";
@Override
protected void setUp() throws Exception {
super.setUp();
getContext().deleteDatabase(DATABASE_FILE);
mDatabase = getContext().openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null);
createTable(TABLE_NAME, TABLE_COLUMNS);
addValuesIntoTable(TABLE_NAME, DEFAULT_TABLE_VALUE_BEGINS, TEST_COUNT);
}
@Override
protected void tearDown() throws Exception {
mDatabase.close();
getContext().deleteDatabase(DATABASE_FILE);
super.tearDown();
}
public void testConstructor() {
SQLiteDirectCursorDriver cursorDriver = new SQLiteDirectCursorDriver(mDatabase,
TEST_SQL, TABLE_NAME, null);
try {
new SQLiteCursor(mDatabase, cursorDriver, TABLE_NAME, null);
fail("constructor didn't throw IllegalArgumentException when SQLiteQuery is null");
} catch (IllegalArgumentException e) {
}
// get SQLiteCursor by querying database
SQLiteCursor cursor = getCursor();
assertNotNull(cursor);
}
public void testClose() {
SQLiteCursor cursor = getCursor();
assertTrue(cursor.moveToFirst());
assertFalse(cursor.isClosed());
assertTrue(cursor.requery());
cursor.close();
assertFalse(cursor.requery());
try {
cursor.moveToFirst();
fail("moveToFirst didn't throw IllegalStateException after closed.");
} catch (IllegalStateException e) {
}
assertTrue(cursor.isClosed());
}
public void testRegisterDataSetObserver() {
SQLiteCursor cursor = getCursor();
MockCursorWindow cursorWindow = new MockCursorWindow(false);
MockObserver observer = new MockObserver();
cursor.setWindow(cursorWindow);
// Before registering, observer can't be notified.
assertFalse(observer.hasInvalidated());
cursor.moveToLast();
assertFalse(cursorWindow.isClosed());
cursor.deactivate();
assertFalse(observer.hasInvalidated());
// deactivate() will close the CursorWindow
assertTrue(cursorWindow.isClosed());
// test registering DataSetObserver
assertTrue(cursor.requery());
cursor.registerDataSetObserver(observer);
assertFalse(observer.hasInvalidated());
cursor.moveToLast();
assertEquals(TEST_COUNT, cursor.getInt(1));
cursor.deactivate();
// deactivate method can invoke invalidate() method, can be observed by DataSetObserver.
assertTrue(observer.hasInvalidated());
try {
cursor.getInt(1);
fail("After deactivating, cursor cannot execute getting value operations.");
} catch (StaleDataException e) {
}
assertTrue(cursor.requery());
cursor.moveToLast();
assertEquals(TEST_COUNT, cursor.getInt(1));
// can't register a same observer twice.
try {
cursor.registerDataSetObserver(observer);
fail("didn't throw IllegalStateException when register existed observer");
} catch (IllegalStateException e) {
}
// after unregistering, observer can't be notified.
cursor.unregisterDataSetObserver(observer);
observer.resetStatus();
assertFalse(observer.hasInvalidated());
cursor.deactivate();
assertFalse(observer.hasInvalidated());
}
public void testRequery() {
final String DELETE = "DELETE FROM " + TABLE_NAME + " WHERE number_1 =";
final String DELETE_1 = DELETE + "1;";
final String DELETE_2 = DELETE + "2;";
mDatabase.execSQL(DELETE_1);
// when cursor is created, it refreshes CursorWindow and populates cursor count
SQLiteCursor cursor = getCursor();
MockObserver observer = new MockObserver();
cursor.registerDataSetObserver(observer);
assertEquals(TEST_COUNT - 1, cursor.getCount());
assertFalse(observer.hasChanged());
mDatabase.execSQL(DELETE_2);
// when getCount() has invoked once, it can no longer refresh CursorWindow.
assertEquals(TEST_COUNT - 1, cursor.getCount());
assertTrue(cursor.requery());
// only after requery, getCount can get most up-to-date counting info now.
assertEquals(TEST_COUNT - 2, cursor.getCount());
assertTrue(observer.hasChanged());
}
public void testRequery2() {
mDatabase.disableWriteAheadLogging();
mDatabase.execSQL("create table testRequery2 (i int);");
mDatabase.execSQL("insert into testRequery2 values(1);");
mDatabase.execSQL("insert into testRequery2 values(2);");
Cursor c = mDatabase.rawQuery("select * from testRequery2 order by i", null);
assertEquals(2, c.getCount());
assertTrue(c.moveToFirst());
assertEquals(1, c.getInt(0));
assertTrue(c.moveToNext());
assertEquals(2, c.getInt(0));
// add more data to the table and requery
mDatabase.execSQL("insert into testRequery2 values(3);");
assertTrue(c.requery());
assertEquals(3, c.getCount());
assertTrue(c.moveToFirst());
assertEquals(1, c.getInt(0));
assertTrue(c.moveToNext());
assertEquals(2, c.getInt(0));
assertTrue(c.moveToNext());
assertEquals(3, c.getInt(0));
// close the database and see if requery throws an exception
mDatabase.close();
assertFalse(c.requery());
}
public void testGetColumnIndex() {
SQLiteCursor cursor = getCursor();
for (int i = 0; i < COLUMNS.length; i++) {
assertEquals(i, cursor.getColumnIndex(COLUMNS[i]));
}
assertTrue(Arrays.equals(COLUMNS, cursor.getColumnNames()));
}
public void testSetSelectionArguments() {
final String SELECTION = "_id > ?";
int TEST_ARG1 = 2;
int TEST_ARG2 = 5;
SQLiteCursor cursor = (SQLiteCursor) mDatabase.query(TABLE_NAME, null, SELECTION,
new String[] { Integer.toString(TEST_ARG1) }, null, null, null);
assertEquals(TEST_COUNT - TEST_ARG1, cursor.getCount());
cursor.setSelectionArguments(new String[] { Integer.toString(TEST_ARG2) });
cursor.requery();
assertEquals(TEST_COUNT - TEST_ARG2, cursor.getCount());
}
public void testRowTooBig() {
mDatabase.execSQL("CREATE TABLE Tst (Txt BLOB NOT NULL);");
byte[] testArr = new byte[10000];
Arrays.fill(testArr, (byte) 1);
for (int i = 0; i < 10; i++) {
mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{testArr});
}
// Now reduce window size, so that no rows can fit
Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
CursorWindow cw = new CursorWindow("test", 5000);
AbstractWindowedCursor ac = (AbstractWindowedCursor) cursor;
ac.setWindow(cw);
try {
ac.moveToNext();
fail("Exception is expected when row exceeds CursorWindow size");
} catch (SQLiteBlobTooBigException expected) {
}
}
public void testFillWindowForwardOnly() {
mDatabase.execSQL("CREATE TABLE Tst (Num Integer NOT NULL);");
mDatabase.beginTransaction();
for (int i = 0; i < 100; i++) {
mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{i});
}
mDatabase.setTransactionSuccessful();
mDatabase.endTransaction();
Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
SQLiteCursor ac = (SQLiteCursor) cursor;
CursorWindow window = new CursorWindow("test", 1000);
ac.setFillWindowForwardOnly(true);
ac.setWindow(window);
assertTrue(ac.moveToFirst());
// Now skip 70 rows and check that the window start position corresponds to row 70
ac.move(70);
assertEquals(70, window.getStartPosition());
}
public void testOnMove() {
// Do not test this API. It is callback which:
// 1. The callback mechanism has been tested in super class
// 2. The functionality is implementation details, no need to test
}
private void createTable(String tableName, String columnNames) {
String sql = "Create TABLE " + tableName + " (_id INTEGER PRIMARY KEY, "
+ columnNames + " );";
mDatabase.execSQL(sql);
}
private void addValuesIntoTable(String tableName, int start, int end) {
for (int i = start; i <= end; i++) {
mDatabase.execSQL("INSERT INTO " + tableName + "(number_1) VALUES ('" + i + "');");
}
}
private SQLiteCursor getCursor() {
SQLiteCursor cursor = (SQLiteCursor) mDatabase.query(TABLE_NAME, null, null,
null, null, null, null);
return cursor;
}
private class MockObserver extends DataSetObserver {
private boolean mHasChanged = false;
private boolean mHasInvalidated = false;
@Override
public void onChanged() {
super.onChanged();
mHasChanged = true;
}
@Override
public void onInvalidated() {
super.onInvalidated();
mHasInvalidated = true;
}
protected void resetStatus() {
mHasChanged = false;
mHasInvalidated = false;
}
protected boolean hasChanged() {
return mHasChanged;
}
protected boolean hasInvalidated () {
return mHasInvalidated;
}
}
private class MockCursorWindow extends CursorWindow {
private boolean mIsClosed = false;
public MockCursorWindow(boolean localWindow) {
super(localWindow);
}
@Override
public void close() {
super.close();
mIsClosed = true;
}
public boolean isClosed() {
return mIsClosed;
}
}
}