1 package android.database; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP; 4 import static com.google.common.truth.Truth.assertThat; 5 import static java.util.concurrent.TimeUnit.SECONDS; 6 import static org.junit.Assert.assertThrows; 7 8 import android.content.ContentValues; 9 import android.database.sqlite.SQLiteConstraintException; 10 import android.database.sqlite.SQLiteDatabase; 11 import android.database.sqlite.SQLiteException; 12 import androidx.test.core.app.ApplicationProvider; 13 import androidx.test.ext.junit.runners.AndroidJUnit4; 14 import androidx.test.filters.SdkSuppress; 15 import androidx.test.filters.Suppress; 16 import com.google.common.base.Ascii; 17 import com.google.common.base.Throwables; 18 import com.google.common.io.ByteStreams; 19 import java.io.File; 20 import java.io.PrintStream; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 import java.util.concurrent.ExecutorService; 24 import java.util.concurrent.Executors; 25 import org.junit.After; 26 import org.junit.Before; 27 import org.junit.Test; 28 import org.junit.runner.RunWith; 29 import org.robolectric.annotation.Config; 30 import org.robolectric.annotation.internal.DoNotInstrument; 31 32 /** Compatibility test for {@link android.database.sqlite.SQLiteDatabase} */ 33 @DoNotInstrument 34 @RunWith(AndroidJUnit4.class) 35 public class SQLiteDatabaseTest { 36 37 private SQLiteDatabase database; 38 private File databasePath; 39 40 @Before setUp()41 public void setUp() { 42 databasePath = ApplicationProvider.getApplicationContext().getDatabasePath("database.db"); 43 databasePath.getParentFile().mkdirs(); 44 45 database = SQLiteDatabase.openOrCreateDatabase(databasePath, null); 46 database.execSQL( 47 "CREATE TABLE table_name (\n" 48 + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" 49 + " first_column VARCHAR(255),\n" 50 + " second_column BINARY,\n" 51 + " name VARCHAR(255),\n" 52 + " big_int INTEGER\n" 53 + ");"); 54 } 55 56 @After tearDown()57 public void tearDown() { 58 database.close(); 59 assertThat(databasePath.delete()).isTrue(); 60 } 61 62 @Test shouldGetBlobFromString()63 public void shouldGetBlobFromString() { 64 String s = "this is a string"; 65 ContentValues values = new ContentValues(); 66 values.put("first_column", s); 67 68 database.insert("table_name", null, values); 69 70 try (Cursor data = 71 database.query("table_name", new String[] {"first_column"}, null, null, null, null, null)) { 72 assertThat(data.getCount()).isEqualTo(1); 73 data.moveToFirst(); 74 byte[] columnBytes = data.getBlob(0); 75 byte[] expected = Arrays.copyOf(s.getBytes(), s.length() + 1); // include zero-terminal 76 assertThat(columnBytes).isEqualTo(expected); 77 } 78 } 79 80 @Test shouldGetBlobFromNullString()81 public void shouldGetBlobFromNullString() { 82 ContentValues values = new ContentValues(); 83 values.put("first_column", (String) null); 84 database.insert("table_name", null, values); 85 86 try (Cursor data = 87 database.query("table_name", new String[] {"first_column"}, null, null, null, null, null)) { 88 assertThat(data.getCount()).isEqualTo(1); 89 data.moveToFirst(); 90 assertThat(data.getBlob(0)).isEqualTo(null); 91 } 92 } 93 94 @Test shouldGetBlobFromEmptyString()95 public void shouldGetBlobFromEmptyString() { 96 ContentValues values = new ContentValues(); 97 values.put("first_column", ""); 98 database.insert("table_name", null, values); 99 100 try (Cursor data = 101 database.query("table_name", new String[] {"first_column"}, null, null, null, null, null)) { 102 assertThat(data.getCount()).isEqualTo(1); 103 data.moveToFirst(); 104 assertThat(data.getBlob(0)).isEqualTo(new byte[] {0}); 105 } 106 } 107 108 @Test shouldThrowWhenForeignKeysConstraintIsViolated()109 public void shouldThrowWhenForeignKeysConstraintIsViolated() { 110 database.execSQL( 111 "CREATE TABLE artist(\n" 112 + " artistid INTEGER PRIMARY KEY, \n" 113 + " artistname TEXT\n" 114 + ");\n"); 115 116 database.execSQL( 117 "CREATE TABLE track(\n" 118 + " trackid INTEGER, \n" 119 + " trackname TEXT, \n" 120 + " trackartist INTEGER,\n" 121 + " FOREIGN KEY(trackartist) REFERENCES artist(artistid)\n" 122 + ");"); 123 124 database.execSQL("PRAGMA foreign_keys=ON"); 125 database.execSQL("INSERT into artist (artistid, artistname) VALUES (1, 'Kanye')"); 126 database.execSQL( 127 "INSERT into track (trackid, trackname, trackartist) VALUES (1, 'Good Life', 1)"); 128 SQLiteConstraintException ex = 129 assertThrows(SQLiteConstraintException.class, () -> database.execSQL("delete from artist")); 130 assertThat(Ascii.toLowerCase(Throwables.getStackTraceAsString(ex))).contains("foreign key"); 131 } 132 133 @Test shouldDeleteWithLikeEscape()134 public void shouldDeleteWithLikeEscape() { 135 ContentValues values = new ContentValues(); 136 values.put("first_column", "test"); 137 database.insert("table_name", null, values); 138 String select = "first_column LIKE ? ESCAPE ?"; 139 String[] selectArgs = { 140 "test", Character.toString('\\'), 141 }; 142 assertThat(database.delete("table_name", select, selectArgs)).isEqualTo(1); 143 } 144 145 @Test shouldThrowsExceptionWhenQueryingUsingExecSQL()146 public void shouldThrowsExceptionWhenQueryingUsingExecSQL() { 147 SQLiteException e = assertThrows(SQLiteException.class, () -> database.execSQL("select 1")); 148 assertThat(e) 149 .hasMessageThat() 150 .contains("Queries can be performed using SQLiteDatabase query or rawQuery methods only."); 151 } 152 153 @Test close_withExclusiveLockingMode()154 public void close_withExclusiveLockingMode() { 155 database.rawQuery("PRAGMA locking_mode = EXCLUSIVE", new String[0]).close(); 156 ContentValues values = new ContentValues(); 157 values.put("first_column", ""); 158 database.insert("table_name", null, values); 159 database.close(); 160 161 database = SQLiteDatabase.openOrCreateDatabase(databasePath, null); 162 database.insert("table_name", null, values); 163 } 164 165 static class MyCursorWindow extends CursorWindow { MyCursorWindow(String name)166 public MyCursorWindow(String name) { 167 super(name); 168 } 169 170 /** Make the finalize method public */ 171 @Override finalize()172 public void finalize() throws Throwable { 173 super.finalize(); 174 } 175 } 176 177 // TODO(hoisie): This test crashes in emulators, enable when it is fixed in Android. 178 // Use Suppress here to stop it from running on emulators, but not on Robolectric 179 @Suppress 180 @Test cursorWindow_finalize_concurrentStressTest()181 public void cursorWindow_finalize_concurrentStressTest() throws Throwable { 182 final PrintStream originalErr = System.err; 183 // discard stderr output for this test to prevent CloseGuard logspam. 184 System.setErr(new PrintStream(ByteStreams.nullOutputStream())); 185 try { 186 ExecutorService executor = Executors.newFixedThreadPool(4); 187 for (int i = 0; i < 1000; i++) { 188 final MyCursorWindow cursorWindow = new MyCursorWindow(String.valueOf(i)); 189 for (int j = 0; j < 4; j++) { 190 executor.execute( 191 () -> { 192 try { 193 cursorWindow.finalize(); 194 } catch (Throwable e) { 195 throw new AssertionError(e); 196 } 197 }); 198 } 199 } 200 executor.shutdown(); 201 executor.awaitTermination(100, SECONDS); 202 } finally { 203 System.setErr(originalErr); 204 } 205 } 206 207 @Test 208 @Config(minSdk = LOLLIPOP) 209 @SdkSuppress(minSdkVersion = LOLLIPOP) collate_unicode()210 public void collate_unicode() { 211 String[] names = new String[] {"aaa", "abc", "ABC", "bbb"}; 212 for (String name : names) { 213 ContentValues values = new ContentValues(); 214 values.put("name", name); 215 database.insert("table_name", null, values); 216 } 217 Cursor c = 218 database.rawQuery("SELECT name from table_name ORDER BY name COLLATE UNICODE ASC", null); 219 c.moveToFirst(); 220 ArrayList<String> sorted = new ArrayList<>(); 221 while (!c.isAfterLast()) { 222 sorted.add(c.getString(0)); 223 c.moveToNext(); 224 } 225 c.close(); 226 assertThat(sorted).containsExactly("aaa", "abc", "ABC", "bbb").inOrder(); 227 } 228 229 @Test 230 @Config(minSdk = LOLLIPOP) 231 @SdkSuppress(minSdkVersion = LOLLIPOP) regex_selection()232 public void regex_selection() { 233 ContentValues values = new ContentValues(); 234 values.put("first_column", "test"); 235 database.insert("table_name", null, values); 236 String select = "first_column regexp ?"; 237 String[] selectArgs = { 238 "test", 239 }; 240 assertThat(database.delete("table_name", select, selectArgs)).isEqualTo(1); 241 } 242 } 243