1 /* 2 * Copyright (C) 2010 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; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.database.AbstractWindowedCursor; 22 import android.database.Cursor; 23 import android.database.CursorWindow; 24 import android.platform.test.annotations.Presubmit; 25 import android.test.AndroidTestCase; 26 27 import androidx.test.filters.LargeTest; 28 29 import java.io.File; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.Set; 33 34 @Presubmit 35 @LargeTest 36 public class SQLiteCursorTest extends AndroidTestCase { 37 private static final String TABLE_NAME = "testCursor"; 38 private SQLiteDatabase mDatabase; 39 private File mDatabaseFile; 40 41 @Override setUp()42 protected void setUp() throws Exception { 43 super.setUp(); 44 45 File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); 46 mDatabaseFile = new File(dbDir, "sqlitecursor_test.db"); 47 if (mDatabaseFile.exists()) { 48 mDatabaseFile.delete(); 49 } 50 mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); 51 assertNotNull(mDatabase); 52 // create a test table 53 mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);"); 54 } 55 56 @Override tearDown()57 protected void tearDown() throws Exception { 58 mDatabase.close(); 59 mDatabaseFile.delete(); 60 super.tearDown(); 61 } 62 63 /** 64 * this test could take a while to execute. so, designate it as LargeTest 65 */ testFillWindow()66 public void testFillWindow() { 67 // create schema 68 final String testTable = "testV"; 69 mDatabase.beginTransaction(); 70 mDatabase.execSQL("CREATE TABLE " + testTable + " (col1 int, desc text not null);"); 71 mDatabase.setTransactionSuccessful(); 72 mDatabase.endTransaction(); 73 74 // populate the table with data 75 // create a big string that will almost fit a page but not quite. 76 // since sqlite wants to make sure each row is in a page, this string will allocate 77 // a new database page for each row. 78 StringBuilder buff = new StringBuilder(); 79 for (int i = 0; i < 500; i++) { 80 buff.append(i % 10 + ""); 81 } 82 ContentValues values = new ContentValues(); 83 values.put("desc", buff.toString()); 84 85 // insert more than 1MB of data in the table. this should ensure that the entire tabledata 86 // will need more than one CursorWindow 87 int N = 5000; 88 Set<Integer> rows = new HashSet<Integer>(); 89 mDatabase.beginTransaction(); 90 for (int j = 0; j < N; j++) { 91 values.put("col1", j); 92 mDatabase.insert(testTable, null, values); 93 rows.add(j); // store in a hashtable so we can verify the results from cursor later on 94 } 95 mDatabase.setTransactionSuccessful(); 96 mDatabase.endTransaction(); 97 assertEquals(N, rows.size()); 98 Cursor c1 = mDatabase.rawQuery("select * from " + testTable, null); 99 assertEquals(N, c1.getCount()); 100 c1.close(); 101 102 // scroll through ALL data in the table using a cursor. should cause multiple calls to 103 // native_fill_window (and re-fills of the CursorWindow object) 104 Cursor c = mDatabase.query(testTable, new String[]{"col1", "desc"}, 105 null, null, null, null, null); 106 int i = 0; 107 while (c.moveToNext()) { 108 int val = c.getInt(0); 109 assertTrue(rows.contains(val)); 110 assertTrue(rows.remove(val)); 111 } 112 // did I see all the rows in the table? 113 assertTrue(rows.isEmpty()); 114 115 // change data and make sure the cursor picks up new data & count 116 rows = new HashSet<Integer>(); 117 mDatabase.beginTransaction(); 118 int M = N + 1000; 119 for (int j = 0; j < M; j++) { 120 rows.add(j); 121 if (j < N) { 122 continue; 123 } 124 values.put("col1", j); 125 mDatabase.insert(testTable, null, values); 126 } 127 mDatabase.setTransactionSuccessful(); 128 mDatabase.endTransaction(); 129 assertEquals(M, rows.size()); 130 c.requery(); 131 i = 0; 132 while (c.moveToNext()) { 133 int val = c.getInt(0); 134 assertTrue(rows.contains(val)); 135 assertTrue(rows.remove(val)); 136 } 137 // did I see all data from the modified table 138 assertTrue(rows.isEmpty()); 139 140 // move cursor back to 1st row and scroll to about halfway in the result set 141 // and then delete 75% of data - and then do requery 142 c.moveToFirst(); 143 int K = N / 2; 144 for (int p = 0; p < K && c.moveToNext(); p++) { 145 // nothing to do - just scrolling to about half-point in the resultset 146 } 147 mDatabase.beginTransaction(); 148 mDatabase.delete(testTable, "col1 < ?", new String[]{(3 * M / 4) + ""}); 149 mDatabase.setTransactionSuccessful(); 150 mDatabase.endTransaction(); 151 c.requery(); 152 assertEquals(M / 4, c.getCount()); 153 while (c.moveToNext()) { 154 // just move the cursor to next row - to make sure it can go through the entire 155 // resultset without any problems 156 } 157 c.close(); 158 } 159 testCustomWindowSize()160 public void testCustomWindowSize() { 161 mDatabase.execSQL("CREATE TABLE Tst (Txt BLOB NOT NULL);"); 162 byte[] testArr = new byte[10000]; 163 Arrays.fill(testArr, (byte) 1); 164 for (int i = 0; i < 10; i++) { 165 mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{testArr}); 166 } 167 Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null); 168 // With default window size, all rows should fit in RAM 169 AbstractWindowedCursor ac = (AbstractWindowedCursor) cursor; 170 int n = 0; 171 while (ac.moveToNext()) { 172 n++; 173 assertEquals(10, ac.getWindow().getNumRows()); 174 } 175 assertEquals("All rows should be visited", 10, n); 176 177 // Now reduce window size, so that only 1 row can fit 178 cursor = mDatabase.rawQuery("SELECT * FROM TST", null); 179 CursorWindow cw = new CursorWindow("test", 11000); 180 ac = (AbstractWindowedCursor) cursor; 181 ac.setWindow(cw); 182 ac.move(-10); 183 n = 0; 184 while (ac.moveToNext()) { 185 n++; 186 assertEquals(1, cw.getNumRows()); 187 } 188 assertEquals("All rows should be visited", 10, n); 189 } 190 }