1 /* 2 * Copyright (C) 2009 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.cts; 18 19 20 import android.content.Context; 21 import android.database.AbstractCursor; 22 import android.database.AbstractWindowedCursor; 23 import android.database.Cursor; 24 import android.database.CursorWindow; 25 import android.database.DataSetObserver; 26 import android.database.SQLException; 27 import android.database.StaleDataException; 28 import android.database.sqlite.SQLiteBlobTooBigException; 29 import android.database.sqlite.SQLiteCursor; 30 import android.database.sqlite.SQLiteDatabase; 31 import android.database.sqlite.SQLiteDirectCursorDriver; 32 import android.test.AndroidTestCase; 33 34 import java.util.Arrays; 35 36 /** 37 * Test {@link AbstractCursor}. 38 */ 39 public class SQLiteCursorTest extends AndroidTestCase { 40 private SQLiteDatabase mDatabase; 41 private static final String[] COLUMNS = new String[] { "_id", "number_1", "number_2" }; 42 private static final String TABLE_NAME = "test"; 43 private static final String TABLE_COLUMNS = " number_1 INTEGER, number_2 INTEGER"; 44 private static final int DEFAULT_TABLE_VALUE_BEGINS = 1; 45 private static final int TEST_COUNT = 10; 46 private static final String TEST_SQL = "SELECT * FROM test ORDER BY number_1"; 47 private static final String DATABASE_FILE = "database_test.db"; 48 49 @Override setUp()50 protected void setUp() throws Exception { 51 super.setUp(); 52 getContext().deleteDatabase(DATABASE_FILE); 53 mDatabase = getContext().openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null); 54 createTable(TABLE_NAME, TABLE_COLUMNS); 55 addValuesIntoTable(TABLE_NAME, DEFAULT_TABLE_VALUE_BEGINS, TEST_COUNT); 56 } 57 58 @Override tearDown()59 protected void tearDown() throws Exception { 60 mDatabase.close(); 61 getContext().deleteDatabase(DATABASE_FILE); 62 super.tearDown(); 63 } 64 testConstructor()65 public void testConstructor() { 66 SQLiteDirectCursorDriver cursorDriver = new SQLiteDirectCursorDriver(mDatabase, 67 TEST_SQL, TABLE_NAME, null); 68 try { 69 new SQLiteCursor(mDatabase, cursorDriver, TABLE_NAME, null); 70 fail("constructor didn't throw IllegalArgumentException when SQLiteQuery is null"); 71 } catch (IllegalArgumentException e) { 72 } 73 74 // get SQLiteCursor by querying database 75 SQLiteCursor cursor = getCursor(); 76 assertNotNull(cursor); 77 } 78 testClose()79 public void testClose() { 80 SQLiteCursor cursor = getCursor(); 81 assertTrue(cursor.moveToFirst()); 82 assertFalse(cursor.isClosed()); 83 assertTrue(cursor.requery()); 84 cursor.close(); 85 assertFalse(cursor.requery()); 86 try { 87 cursor.moveToFirst(); 88 fail("moveToFirst didn't throw IllegalStateException after closed."); 89 } catch (IllegalStateException e) { 90 } 91 assertTrue(cursor.isClosed()); 92 } 93 testRegisterDataSetObserver()94 public void testRegisterDataSetObserver() { 95 SQLiteCursor cursor = getCursor(); 96 MockCursorWindow cursorWindow = new MockCursorWindow(false); 97 98 MockObserver observer = new MockObserver(); 99 100 cursor.setWindow(cursorWindow); 101 // Before registering, observer can't be notified. 102 assertFalse(observer.hasInvalidated()); 103 cursor.moveToLast(); 104 assertFalse(cursorWindow.isClosed()); 105 cursor.deactivate(); 106 assertFalse(observer.hasInvalidated()); 107 // deactivate() will close the CursorWindow 108 assertTrue(cursorWindow.isClosed()); 109 110 // test registering DataSetObserver 111 assertTrue(cursor.requery()); 112 cursor.registerDataSetObserver(observer); 113 assertFalse(observer.hasInvalidated()); 114 cursor.moveToLast(); 115 assertEquals(TEST_COUNT, cursor.getInt(1)); 116 cursor.deactivate(); 117 // deactivate method can invoke invalidate() method, can be observed by DataSetObserver. 118 assertTrue(observer.hasInvalidated()); 119 120 try { 121 cursor.getInt(1); 122 fail("After deactivating, cursor cannot execute getting value operations."); 123 } catch (StaleDataException e) { 124 } 125 126 assertTrue(cursor.requery()); 127 cursor.moveToLast(); 128 assertEquals(TEST_COUNT, cursor.getInt(1)); 129 130 // can't register a same observer twice. 131 try { 132 cursor.registerDataSetObserver(observer); 133 fail("didn't throw IllegalStateException when register existed observer"); 134 } catch (IllegalStateException e) { 135 } 136 137 // after unregistering, observer can't be notified. 138 cursor.unregisterDataSetObserver(observer); 139 observer.resetStatus(); 140 assertFalse(observer.hasInvalidated()); 141 cursor.deactivate(); 142 assertFalse(observer.hasInvalidated()); 143 } 144 testRequery()145 public void testRequery() { 146 final String DELETE = "DELETE FROM " + TABLE_NAME + " WHERE number_1 ="; 147 final String DELETE_1 = DELETE + "1;"; 148 final String DELETE_2 = DELETE + "2;"; 149 150 mDatabase.execSQL(DELETE_1); 151 // when cursor is created, it refreshes CursorWindow and populates cursor count 152 SQLiteCursor cursor = getCursor(); 153 MockObserver observer = new MockObserver(); 154 cursor.registerDataSetObserver(observer); 155 assertEquals(TEST_COUNT - 1, cursor.getCount()); 156 assertFalse(observer.hasChanged()); 157 158 mDatabase.execSQL(DELETE_2); 159 // when getCount() has invoked once, it can no longer refresh CursorWindow. 160 assertEquals(TEST_COUNT - 1, cursor.getCount()); 161 162 assertTrue(cursor.requery()); 163 // only after requery, getCount can get most up-to-date counting info now. 164 assertEquals(TEST_COUNT - 2, cursor.getCount()); 165 assertTrue(observer.hasChanged()); 166 } 167 testRequery2()168 public void testRequery2() { 169 mDatabase.disableWriteAheadLogging(); 170 mDatabase.execSQL("create table testRequery2 (i int);"); 171 mDatabase.execSQL("insert into testRequery2 values(1);"); 172 mDatabase.execSQL("insert into testRequery2 values(2);"); 173 Cursor c = mDatabase.rawQuery("select * from testRequery2 order by i", null); 174 assertEquals(2, c.getCount()); 175 assertTrue(c.moveToFirst()); 176 assertEquals(1, c.getInt(0)); 177 assertTrue(c.moveToNext()); 178 assertEquals(2, c.getInt(0)); 179 // add more data to the table and requery 180 mDatabase.execSQL("insert into testRequery2 values(3);"); 181 assertTrue(c.requery()); 182 assertEquals(3, c.getCount()); 183 assertTrue(c.moveToFirst()); 184 assertEquals(1, c.getInt(0)); 185 assertTrue(c.moveToNext()); 186 assertEquals(2, c.getInt(0)); 187 assertTrue(c.moveToNext()); 188 assertEquals(3, c.getInt(0)); 189 // close the database and see if requery throws an exception 190 mDatabase.close(); 191 assertFalse(c.requery()); 192 } 193 testGetColumnIndex()194 public void testGetColumnIndex() { 195 SQLiteCursor cursor = getCursor(); 196 197 for (int i = 0; i < COLUMNS.length; i++) { 198 assertEquals(i, cursor.getColumnIndex(COLUMNS[i])); 199 } 200 201 assertTrue(Arrays.equals(COLUMNS, cursor.getColumnNames())); 202 } 203 testSetSelectionArguments()204 public void testSetSelectionArguments() { 205 final String SELECTION = "_id > ?"; 206 int TEST_ARG1 = 2; 207 int TEST_ARG2 = 5; 208 SQLiteCursor cursor = (SQLiteCursor) mDatabase.query(TABLE_NAME, null, SELECTION, 209 new String[] { Integer.toString(TEST_ARG1) }, null, null, null); 210 assertEquals(TEST_COUNT - TEST_ARG1, cursor.getCount()); 211 cursor.setSelectionArguments(new String[] { Integer.toString(TEST_ARG2) }); 212 cursor.requery(); 213 assertEquals(TEST_COUNT - TEST_ARG2, cursor.getCount()); 214 } 215 testRowTooBig()216 public void testRowTooBig() { 217 mDatabase.execSQL("CREATE TABLE Tst (Txt BLOB NOT NULL);"); 218 byte[] testArr = new byte[10000]; 219 Arrays.fill(testArr, (byte) 1); 220 for (int i = 0; i < 10; i++) { 221 mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{testArr}); 222 } 223 224 // Now reduce window size, so that no rows can fit 225 Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null); 226 CursorWindow cw = new CursorWindow("test", 5000); 227 AbstractWindowedCursor ac = (AbstractWindowedCursor) cursor; 228 ac.setWindow(cw); 229 230 try { 231 ac.moveToNext(); 232 fail("Exception is expected when row exceeds CursorWindow size"); 233 } catch (SQLiteBlobTooBigException expected) { 234 } 235 } 236 testFillWindowForwardOnly()237 public void testFillWindowForwardOnly() { 238 mDatabase.execSQL("CREATE TABLE Tst (Num Integer NOT NULL);"); 239 mDatabase.beginTransaction(); 240 for (int i = 0; i < 100; i++) { 241 mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{i}); 242 } 243 mDatabase.setTransactionSuccessful(); 244 mDatabase.endTransaction(); 245 Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null); 246 SQLiteCursor ac = (SQLiteCursor) cursor; 247 CursorWindow window = new CursorWindow("test", 1000); 248 ac.setFillWindowForwardOnly(true); 249 ac.setWindow(window); 250 assertTrue(ac.moveToFirst()); 251 // Now skip 70 rows and check that the window start position corresponds to row 70 252 ac.move(70); 253 assertEquals(70, window.getStartPosition()); 254 } 255 testOnMove()256 public void testOnMove() { 257 // Do not test this API. It is callback which: 258 // 1. The callback mechanism has been tested in super class 259 // 2. The functionality is implementation details, no need to test 260 } 261 createTable(String tableName, String columnNames)262 private void createTable(String tableName, String columnNames) { 263 String sql = "Create TABLE " + tableName + " (_id INTEGER PRIMARY KEY, " 264 + columnNames + " );"; 265 mDatabase.execSQL(sql); 266 } 267 addValuesIntoTable(String tableName, int start, int end)268 private void addValuesIntoTable(String tableName, int start, int end) { 269 for (int i = start; i <= end; i++) { 270 mDatabase.execSQL("INSERT INTO " + tableName + "(number_1) VALUES ('" + i + "');"); 271 } 272 } 273 getCursor()274 private SQLiteCursor getCursor() { 275 SQLiteCursor cursor = (SQLiteCursor) mDatabase.query(TABLE_NAME, null, null, 276 null, null, null, null); 277 return cursor; 278 } 279 280 private class MockObserver extends DataSetObserver { 281 private boolean mHasChanged = false; 282 private boolean mHasInvalidated = false; 283 284 @Override onChanged()285 public void onChanged() { 286 super.onChanged(); 287 mHasChanged = true; 288 } 289 290 @Override onInvalidated()291 public void onInvalidated() { 292 super.onInvalidated(); 293 mHasInvalidated = true; 294 } 295 resetStatus()296 protected void resetStatus() { 297 mHasChanged = false; 298 mHasInvalidated = false; 299 } 300 hasChanged()301 protected boolean hasChanged() { 302 return mHasChanged; 303 } 304 hasInvalidated()305 protected boolean hasInvalidated () { 306 return mHasInvalidated; 307 } 308 } 309 310 private class MockCursorWindow extends CursorWindow { 311 private boolean mIsClosed = false; 312 MockCursorWindow(boolean localWindow)313 public MockCursorWindow(boolean localWindow) { 314 super(localWindow); 315 } 316 317 @Override close()318 public void close() { 319 super.close(); 320 mIsClosed = true; 321 } 322 isClosed()323 public boolean isClosed() { 324 return mIsClosed; 325 } 326 } 327 } 328