1 /* 2 * Copyright (C) 2008 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.cts; 18 19 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.database.Cursor; 23 import android.database.DataSetObserver; 24 import android.database.MergeCursor; 25 import android.database.StaleDataException; 26 import android.database.sqlite.SQLiteDatabase; 27 import android.test.AndroidTestCase; 28 29 import java.io.File; 30 import java.util.Arrays; 31 32 public class MergeCursorTest extends AndroidTestCase { 33 private final int NUMBER_1_COLUMN_INDEX = 1; 34 private static final String TABLE1_NAME = "test1"; 35 private static final String TABLE2_NAME = "test2"; 36 private static final String TABLE3_NAME = "test3"; 37 private static final String TABLE4_NAME = "test4"; 38 private static final String TABLE5_NAME = "test5"; 39 private static final String COLUMN_FOR_NULL_TEST = "Null Field"; 40 41 private SQLiteDatabase mDatabase; 42 private File mDatabaseFile; 43 44 Cursor[] mCursors = null; 45 private static final String TABLE1_COLUMNS = " number_1 INTEGER"; 46 private static final String TABLE2_COLUMNS = " number_1 INTEGER, number_2 INTEGER"; 47 private static final String TABLE3_COLUMNS = " text_1 TEXT, number_3 INTEGER, number_4 REAL"; 48 private static final String TABLE2_COLUMN_NAMES = "_id,number_1,number_2"; 49 private static final String TABLE3_COLUMN_NAMES = "_id,text_1,number_3,number_4"; 50 private static final String TEXT_COLUMN_NAME = "text_1"; 51 private static final int TABLE2_COLUMN_COUNT = 3; 52 private static final int TABLE3_COLUMN_COUNT = 4; 53 private static final int DEFAULT_TABLE_VALUE_BEGINS = 1; 54 private static final int MAX_VALUE = 10; 55 private static final int HALF_VALUE = MAX_VALUE / 2; 56 57 @Override 58 protected void setUp() throws Exception { 59 super.setUp(); 60 setupDatabase(); 61 mCursors = new Cursor[2]; 62 } 63 64 @Override 65 protected void tearDown() throws Exception { 66 for (int i = 0; i < mCursors.length; i++) { 67 if (null != mCursors[i]) { 68 mCursors[i].close(); 69 } 70 } 71 mDatabase.close(); 72 mDatabaseFile.delete(); 73 super.tearDown(); 74 } 75 76 public void testConstructor() { 77 // If each item of mCursors are null, count will be zero. 78 MergeCursor mergeCursor = new MergeCursor(mCursors); 79 assertEquals(0, mergeCursor.getCount()); 80 81 createCursors(); 82 83 // if the items are not null, getCount() will return the sum of all cursors' count. 84 mergeCursor = new MergeCursor(mCursors); 85 assertEquals(mCursors[0].getCount() + mCursors[1].getCount(), mergeCursor.getCount()); 86 } 87 88 public void testOnMove() { 89 createCursors(); 90 MergeCursor mergeCursor = new MergeCursor(mCursors); 91 for (int i = 0; i < MAX_VALUE; i++) { 92 mergeCursor.moveToNext(); 93 //From 1~5, mCursor should be in mCursors[0], larger than 5, it should be in 94 //mCursors[1]. 95 assertEquals(i + 1, mergeCursor.getInt(NUMBER_1_COLUMN_INDEX)); 96 } 97 } 98 99 public void testCursorSwiching() { 100 mDatabase.execSQL("CREATE TABLE " + TABLE5_NAME + " (_id INTEGER PRIMARY KEY," 101 + TABLE3_COLUMNS + ");"); 102 String sql = "INSERT INTO " + TABLE5_NAME + " (" + TEXT_COLUMN_NAME + ") VALUES ('TEXT')"; 103 mDatabase.execSQL(sql); 104 105 Cursor[] cursors = new Cursor[2]; 106 cursors[0] = mDatabase.query(TABLE5_NAME, null, null, null, null, null, null); 107 assertEquals(1, cursors[0].getCount()); 108 createCursors(); 109 cursors[1] = mCursors[1]; 110 assertTrue(cursors[1].getCount() > 0); 111 MergeCursor mergeCursor = new MergeCursor(cursors); 112 // MergeCursor should points to cursors[0] after moveToFirst. 113 mergeCursor.moveToFirst(); 114 115 String[] tableColumns = TABLE3_COLUMN_NAMES.split("[,]"); 116 assertEquals(TABLE3_COLUMN_COUNT, mergeCursor.getColumnCount()); 117 assertTrue(Arrays.equals(tableColumns, mergeCursor.getColumnNames())); 118 119 // MergeCursor should points to cursors[1] moveToNext. 120 mergeCursor.moveToNext(); 121 tableColumns = TABLE2_COLUMN_NAMES.split("[,]"); 122 assertEquals(TABLE2_COLUMN_COUNT, mergeCursor.getColumnCount()); 123 assertTrue(Arrays.equals(tableColumns, mergeCursor.getColumnNames())); 124 } 125 126 public void testGetValues() { 127 byte NUMBER_BLOB_UNIT = 99; 128 String[] TEST_STRING = new String[] {"Test String1", "Test String2"}; 129 String[] tableNames = new String[] {TABLE3_NAME, TABLE4_NAME}; 130 131 final double NUMBER_DOUBLE = Double.MAX_VALUE; 132 final double NUMBER_FLOAT = (float) NUMBER_DOUBLE; 133 final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL; 134 final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER; 135 final long NUMBER_SHORT = (short) NUMBER_INTEGER; 136 137 // create tables 138 byte[][] originalBlobs = new byte[2][]; 139 for (int i = 0; i < 2; i++) { 140 // insert blob and other values 141 originalBlobs[i] = new byte[1000]; 142 Arrays.fill(originalBlobs[i], (byte) (NUMBER_BLOB_UNIT - i)); 143 buildDatabaseWithTestValues(TEST_STRING[i], NUMBER_DOUBLE - i, NUMBER_LONG_INTEGER - i, 144 originalBlobs[i], tableNames[i]); 145 // Get cursors. 146 mCursors[i] = mDatabase.query(tableNames[i], null, null, null, null, null, null); 147 } 148 149 MergeCursor mergeCursor = new MergeCursor(mCursors); 150 assertEquals(4, mergeCursor.getCount()); 151 String[] testColumns = new String[] {"_id", "string_text", "double_number", "int_number", 152 "blob_data"}; 153 // Test getColumnNames(). 154 assertTrue(Arrays.equals(testColumns, mergeCursor.getColumnNames())); 155 156 int columnBlob = mCursors[0].getColumnIndexOrThrow("blob_data"); 157 int columnString = mCursors[0].getColumnIndexOrThrow("string_text"); 158 int columnDouble = mCursors[0].getColumnIndexOrThrow("double_number"); 159 int columnInteger = mCursors[0].getColumnIndexOrThrow("int_number"); 160 161 // Test values. 162 for (int i = 0; i < 2; i++) { 163 mergeCursor.moveToNext(); 164 assertEquals(5, mergeCursor.getColumnCount()); 165 166 // Test getting value methods. 167 byte[] targetBlob = mergeCursor.getBlob(columnBlob); 168 assertTrue(Arrays.equals(originalBlobs[i], targetBlob)); 169 170 assertEquals(TEST_STRING[i], mergeCursor.getString(columnString)); 171 assertEquals(NUMBER_DOUBLE - i, mergeCursor.getDouble(columnDouble), 0.000000000001); 172 assertEquals(NUMBER_FLOAT - i, mergeCursor.getFloat(columnDouble), 0.000000000001f); 173 assertEquals(NUMBER_LONG_INTEGER - i, mergeCursor.getLong(columnInteger)); 174 assertEquals(NUMBER_INTEGER - i, mergeCursor.getInt(columnInteger)); 175 assertEquals(NUMBER_SHORT - i, mergeCursor.getShort(columnInteger)); 176 177 // Test isNull(int). 178 assertFalse(mergeCursor.isNull(columnBlob)); 179 mergeCursor.moveToNext(); 180 assertEquals(COLUMN_FOR_NULL_TEST, mergeCursor.getString(columnString)); 181 assertTrue(mergeCursor.isNull(columnBlob)); 182 } 183 } 184 185 public void testContentObsererOperations() throws IllegalStateException { 186 createCursors(); 187 MergeCursor mergeCursor = new MergeCursor(mCursors); 188 ContentObserver observer = new ContentObserver(null) {}; 189 190 // Can't unregister a Observer before it has been registered. 191 try { 192 mergeCursor.unregisterContentObserver(observer); 193 fail("testUnregisterContentObserver failed"); 194 } catch (IllegalStateException e) { 195 // expected 196 } 197 198 mergeCursor.registerContentObserver(observer); 199 200 // Can't register a same observer twice before unregister it. 201 try { 202 mergeCursor.registerContentObserver(observer); 203 fail("testRegisterContentObserver failed"); 204 } catch (IllegalStateException e) { 205 // expected 206 } 207 208 mergeCursor.unregisterContentObserver(observer); 209 // one Observer can be registered again after it has been unregistered. 210 mergeCursor.registerContentObserver(observer); 211 212 mergeCursor.unregisterContentObserver(observer); 213 214 try { 215 mergeCursor.unregisterContentObserver(observer); 216 fail("testUnregisterContentObserver failed"); 217 } catch (IllegalStateException e) { 218 // expected 219 } 220 } 221 222 public void testDeactivate() throws IllegalStateException { 223 createCursors(); 224 MergeCursor mergeCursor = new MergeCursor(mCursors); 225 MockObserver observer = new MockObserver(); 226 227 // one DataSetObserver can't unregistered before it had been registered. 228 try { 229 mergeCursor.unregisterDataSetObserver(observer); 230 fail("testUnregisterDataSetObserver failed"); 231 } catch (IllegalStateException e) { 232 // expected 233 } 234 235 // Before registering, observer can't be notified. 236 assertFalse(observer.hasInvalidated()); 237 mergeCursor.moveToLast(); 238 mergeCursor.deactivate(); 239 assertFalse(observer.hasInvalidated()); 240 241 // Test with registering DataSetObserver 242 assertTrue(mergeCursor.requery()); 243 mergeCursor.registerDataSetObserver(observer); 244 assertFalse(observer.hasInvalidated()); 245 mergeCursor.moveToLast(); 246 assertEquals(MAX_VALUE, mergeCursor.getInt(NUMBER_1_COLUMN_INDEX)); 247 mergeCursor.deactivate(); 248 // deactivate method can invoke invalidate() method, can be observed by DataSetObserver. 249 assertTrue(observer.hasInvalidated()); 250 // After deactivating, the cursor can not provide values from database record. 251 try { 252 mergeCursor.getInt(NUMBER_1_COLUMN_INDEX); 253 fail("After deactivating, cursor cannot execute getting value operations."); 254 } catch (StaleDataException e) { 255 // expected 256 } 257 258 // Can't register a same observer twice before unregister it. 259 try { 260 mergeCursor.registerDataSetObserver(observer); 261 fail("testRegisterDataSetObserver failed"); 262 } catch (IllegalStateException e) { 263 // expected 264 } 265 266 // After runegistering, observer can't be notified. 267 mergeCursor.unregisterDataSetObserver(observer); 268 observer.resetStatus(); 269 assertFalse(observer.hasInvalidated()); 270 mergeCursor.moveToLast(); 271 mergeCursor.deactivate(); 272 assertFalse(observer.hasInvalidated()); 273 274 // one DataSetObserver can't be unregistered twice continuously. 275 try { 276 mergeCursor.unregisterDataSetObserver(observer); 277 fail("testUnregisterDataSetObserver failed"); 278 } catch (IllegalStateException e) { 279 // expected 280 } 281 } 282 283 public void testRequery() { 284 final String TEST_VALUE1 = Integer.toString(MAX_VALUE + 1); 285 final String TEST_VALUE2 = Integer.toString(MAX_VALUE + 2); 286 createCursors(); 287 MergeCursor mergeCursor = new MergeCursor(mCursors); 288 int cursor1Count = mCursors[0].getCount(); 289 int cursor2Count = mCursors[0].getCount(); 290 291 mDatabase.execSQL("INSERT INTO " + TABLE1_NAME + " (number_1) VALUES ('" + TEST_VALUE1 292 + "');"); 293 assertEquals(cursor1Count + cursor2Count, mergeCursor.getCount()); 294 assertTrue(mergeCursor.requery()); 295 cursor1Count += 1; 296 assertEquals(cursor1Count + cursor2Count, mergeCursor.getCount()); 297 mDatabase.execSQL("INSERT INTO " + TABLE2_NAME + " (number_1) VALUES ('" + TEST_VALUE2 298 + "');"); 299 cursor2Count += 1; 300 assertTrue(mergeCursor.requery()); 301 assertEquals(cursor1Count + cursor2Count, mergeCursor.getCount()); 302 303 mergeCursor.close(); 304 assertFalse(mergeCursor.requery()); 305 } 306 307 private void buildDatabaseWithTestValues(String text, double doubleNumber, long intNumber, 308 byte[] blob, String tablename) { 309 Object[] args = new Object[4]; 310 args[0] = text; 311 args[1] = doubleNumber; 312 args[2] = intNumber; 313 args[3] = blob; 314 mDatabase.execSQL("CREATE TABLE " + tablename + " (_id INTEGER PRIMARY KEY," 315 + "string_text TEXT, double_number REAL, int_number INTEGER, blob_data BLOB);"); 316 317 // Insert record in giving table. 318 String sql = "INSERT INTO " + tablename + " (string_text, double_number, int_number," 319 + " blob_data) VALUES (?,?,?,?)"; 320 mDatabase.execSQL(sql, args); 321 // insert null blob. 322 sql = "INSERT INTO " + tablename + " (string_text) VALUES ('" + COLUMN_FOR_NULL_TEST + "')"; 323 mDatabase.execSQL(sql); 324 } 325 326 private void setupDatabase() { 327 File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE); 328 mDatabaseFile = new File(dbDir, "database_test.db"); 329 if (mDatabaseFile.exists()) { 330 mDatabaseFile.delete(); 331 } 332 mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); 333 assertNotNull(mDatabaseFile); 334 createTable(TABLE1_NAME, TABLE1_COLUMNS); 335 createTable(TABLE2_NAME, TABLE2_COLUMNS); 336 addValuesIntoTable(TABLE1_NAME, DEFAULT_TABLE_VALUE_BEGINS, HALF_VALUE); 337 addValuesIntoTable(TABLE2_NAME, HALF_VALUE + 1, MAX_VALUE); 338 } 339 340 private void createTable(String tableName, String columnNames) { 341 String sql = "Create TABLE " + tableName + " (_id INTEGER PRIMARY KEY, " + columnNames 342 + " );"; 343 mDatabase.execSQL(sql); 344 } 345 346 private void addValuesIntoTable(String tableName, int start, int end) { 347 for (int i = start; i <= end; i++) { 348 mDatabase.execSQL("INSERT INTO " + tableName + "(number_1) VALUES ('" 349 + i + "');"); 350 } 351 } 352 353 private Cursor getCursor(String tableName, String selection, String[] columnNames) { 354 return mDatabase.query(tableName, columnNames, selection, null, null, null, "number_1"); 355 } 356 357 private void createCursors() { 358 mCursors[0] = getCursor(TABLE1_NAME, null, null); 359 mCursors[1] = getCursor(TABLE2_NAME, null, null); 360 } 361 362 private class MockObserver extends DataSetObserver { 363 private boolean mHasChanged = false; 364 private boolean mHasInvalidated = false; 365 366 @Override 367 public void onChanged() { 368 super.onChanged(); 369 mHasChanged = true; 370 } 371 372 @Override 373 public void onInvalidated() { 374 super.onInvalidated(); 375 mHasInvalidated = true; 376 } 377 378 public void resetStatus() { 379 mHasChanged = false; 380 mHasInvalidated = false; 381 } 382 383 public boolean hasChanged() { 384 return mHasChanged; 385 } 386 387 public boolean hasInvalidated () { 388 return mHasInvalidated; 389 } 390 } 391 } 392