SQLiteDatabaseTest.java revision bf6a1c2145b187c23b06243ceaa6f85c24498643
1package com.xtremelabs.robolectric.shadows; 2 3import android.content.ContentValues; 4import android.database.Cursor; 5import android.database.sqlite.SQLiteDatabase; 6import com.xtremelabs.robolectric.Robolectric; 7import com.xtremelabs.robolectric.WithTestDefaultsRunner; 8import org.junit.After; 9import org.junit.Before; 10import org.junit.Test; 11import org.junit.runner.RunWith; 12 13import java.sql.ResultSet; 14import java.sql.SQLException; 15import java.sql.Statement; 16 17import static com.xtremelabs.robolectric.Robolectric.shadowOf; 18import static org.hamcrest.CoreMatchers.equalTo; 19import static org.hamcrest.CoreMatchers.not; 20import static org.junit.Assert.*; 21 22@RunWith(WithTestDefaultsRunner.class) 23public class SQLiteDatabaseTest { 24 25 private SQLiteDatabase database; 26 private ShadowSQLiteDatabase shDatabase; 27 28 @Before 29 public void setUp() throws Exception { 30 database = SQLiteDatabase.openDatabase("path", null, 0); 31 shDatabase = Robolectric.shadowOf(database); 32 database.execSQL("CREATE TABLE table_name (\n" + 33 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + 34 " first_column VARCHAR(255),\n" + 35 " second_column BINARY,\n" + 36 " name VARCHAR(255),\n" + 37 " big_int INTEGER\n" + 38 ");"); 39 40 database.execSQL("CREATE TABLE rawtable (\n" + 41 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + 42 " first_column VARCHAR(255),\n" + 43 " second_column BINARY,\n" + 44 " name VARCHAR(255),\n" + 45 " big_int INTEGER\n" + 46 ");"); 47 48 database.execSQL("CREATE TABLE exectable (\n" + 49 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + 50 " first_column VARCHAR(255),\n" + 51 " second_column BINARY,\n" + 52 " name VARCHAR(255),\n" + 53 " big_int INTEGER\n" + 54 ");"); 55 56 String stringColumnValue = "column_value"; 57 byte[] byteColumnValue = new byte[]{1, 2, 3}; 58 59 ContentValues values = new ContentValues(); 60 61 values.put("first_column", stringColumnValue); 62 values.put("second_column", byteColumnValue); 63 64 database.insert("rawtable", null, values); 65 //////////////////////////////////////////////// 66 String stringColumnValue2 = "column_value2"; 67 byte[] byteColumnValue2 = new byte[]{4, 5, 6}; 68 ContentValues values2 = new ContentValues(); 69 70 values2.put("first_column", stringColumnValue2); 71 values2.put("second_column", byteColumnValue2); 72 73 database.insert("rawtable", null, values2); 74 } 75 76 77 @After 78 public void tearDown() throws Exception { 79 database.close(); 80 } 81 82 @Test() 83 public void testInsertAndQuery() throws Exception { 84 String stringColumnValue = "column_value"; 85 byte[] byteColumnValue = new byte[]{1, 2, 3}; 86 87 ContentValues values = new ContentValues(); 88 89 values.put("first_column", stringColumnValue); 90 values.put("second_column", byteColumnValue); 91 92 database.insert("table_name", null, values); 93 94 Cursor cursor = database.query("table_name", new String[]{"second_column", "first_column"}, null, null, null, null, null); 95 96 assertThat(cursor.moveToFirst(), equalTo(true)); 97 98 byte[] byteValueFromDatabase = cursor.getBlob(0); 99 String stringValueFromDatabase = cursor.getString(1); 100 101 assertThat(stringValueFromDatabase, equalTo(stringColumnValue)); 102 assertThat(byteValueFromDatabase, equalTo(byteColumnValue)); 103 } 104 105 @Test 106 public void testInsertAndRawQuery() throws Exception { 107 String stringColumnValue = "column_value"; 108 byte[] byteColumnValue = new byte[]{1, 2, 3}; 109 110 ContentValues values = new ContentValues(); 111 112 values.put("first_column", stringColumnValue); 113 values.put("second_column", byteColumnValue); 114 115 database.insert("table_name", null, values); 116 117 Cursor cursor = database.rawQuery("select second_column, first_column from table_name", null); 118 119 assertThat(cursor.moveToFirst(), equalTo(true)); 120 121 byte[] byteValueFromDatabase = cursor.getBlob(0); 122 String stringValueFromDatabase = cursor.getString(1); 123 124 assertThat(stringValueFromDatabase, equalTo(stringColumnValue)); 125 assertThat(byteValueFromDatabase, equalTo(byteColumnValue)); 126 } 127 128 @Test(expected = IllegalArgumentException.class) 129 public void testRawQueryThrowsIndex0NullException() throws Exception { 130 database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", new String[]{null}); 131 } 132 133 @Test(expected = IllegalArgumentException.class) 134 public void testRawQueryThrowsIndex0NullException2() throws Exception { 135 database.rawQuery("select second_column, first_column from rawtable", new String[]{null}); 136 } 137 138 @Test 139 public void testRawQueryCount() throws Exception { 140 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", new String[]{"1"}); 141 assertThat(cursor.getCount(), equalTo(1)); 142 } 143 144 @Test 145 public void testRawQueryCount2() throws Exception { 146 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", null); 147 assertThat(cursor.getCount(), equalTo(2)); 148 } 149 150 @Test 151 public void testRawQueryCount3() throws Exception { 152 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", new String[]{}); 153 assertThat(cursor.getCount(), equalTo(2)); 154 } 155 /* 156 * Reason why testRawQueryCount4() and testRawQueryCount5() expects exceptions even though exceptions are not found in Android. 157 * 158 * The code in Android acts inconsistently under API version 2.1_r1 (and perhaps other APIs).. 159 * What happens is that rawQuery() remembers the selectionArgs of previous queries, 160 * and uses them if no selectionArgs are given in subsequent queries. 161 * If they were never given selectionArgs THEN they return empty cursors. 162 * 163 * 164 * if you run { 165 * db.rawQuery("select * from exercise WHERE name = ?",null); //this returns an empty cursor 166 * db.rawQuery("select * from exercise WHERE name = ?",new String[]{}); //this returns an empty cursor 167 * } 168 * 169 * but if you run { 170 * db.rawQuery("select * from exercise WHERE name = ?",new String[]{"Leg Press"}); //this returns 1 exercise named "Leg Press" 171 * db.rawQuery("select * from exercise WHERE name = ?",null); //this too returns 1 exercise named "Leg Press" 172 * db.rawQuery("select * from exercise WHERE name = ?",new String[]{}); //this too returns 1 exercise named "Leg Press" 173 * } 174 * 175 * so SQLite + Android work inconsistently (it maintains state that it should not) 176 * whereas H2 just throws an exception for not supplying the selectionArgs 177 * 178 * So the question is should Robolectric: 179 * 1) throw an exception, the way H2 does. 180 * 2) return an empty Cursor. 181 * 3) mimic Android\SQLite precisely and return inconsistent results based on previous state 182 * 183 * Returning an empty cursor all the time would be bad 184 * because Android doesn't always return an empty cursor. 185 * But just mimicing Android would not be helpful, 186 * since it would be less than obvious where the problem is coming from. 187 * One should just avoid ever calling a statement without selectionArgs (when one has a ? placeholder), 188 * so it is best to throw an Exception to let the programmer know that this isn't going to turn out well if they try to run it under Android. 189 * Because we are running in the context of a test we do not have to mimic Android precisely (if it is more helpful not to!), we just need to help 190 * the testing programmer figure out what is going on. 191 */ 192 193 @Test(expected = Exception.class) 194 public void testRawQueryCount4() throws Exception { 195 //Android and SQLite don't normally throw an exception here. See above explanation as to why Robolectric should. 196 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", null); 197 } 198 199 @Test(expected = Exception.class) 200 public void testRawQueryCount5() throws Exception { 201 //Android and SQLite don't normally throw an exception here. See above explanation as to why Robolectric should. 202 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", new String[]{}); 203 } 204 205 @Test(expected = android.database.sqlite.SQLiteException.class) 206 public void testRawQueryCount8() throws Exception { 207 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", new String[]{"1"}); 208 } 209 210 @Test 211 public void testInsertWithException() { 212 ContentValues values = new ContentValues(); 213 214 assertEquals(-1, database.insert("table_that_doesnt_exist", null, values)); 215 } 216 217 218 @Test 219 public void testEmptyTable() throws Exception { 220 Cursor cursor = database.query("table_name", new String[]{"second_column", "first_column"}, null, null, null, null, null); 221 222 assertThat(cursor.moveToFirst(), equalTo(false)); 223 } 224 225 @Test 226 public void testInsertRowIdGeneration() throws Exception { 227 ContentValues values = new ContentValues(); 228 values.put("name", "Chuck"); 229 230 long id = database.insert("table_name", null, values); 231 232 assertThat(id, not(equalTo(0L))); 233 } 234 235 @Test 236 public void testInsertKeyGeneration() throws Exception { 237 ContentValues values = new ContentValues(); 238 values.put("name", "Chuck"); 239 240 long key = database.insertWithOnConflict("table_name", null, values, SQLiteDatabase.CONFLICT_IGNORE); 241 242 assertThat(key, not(equalTo(0L))); 243 } 244 245 @Test 246 public void testUpdate() throws Exception { 247 addChuck(); 248 249 assertThat(updateName(1234L, "Buster"), equalTo(1)); 250 251 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 252 assertThat(cursor.moveToFirst(), equalTo(true)); 253 assertThat(cursor.getCount(), equalTo(1)); 254 255 assertIdAndName(cursor, 1234L, "Buster"); 256 } 257 258 @Test 259 public void testUpdateNoMatch() throws Exception { 260 addChuck(); 261 262 assertThat(updateName(5678L, "Buster"), equalTo(0)); 263 264 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 265 assertThat(cursor.moveToFirst(), equalTo(true)); 266 assertThat(cursor.getCount(), equalTo(1)); 267 268 assertIdAndName(cursor, 1234L, "Chuck"); 269 } 270 271 @Test 272 public void testUpdateAll() throws Exception { 273 addChuck(); 274 addJulie(); 275 276 assertThat(updateName("Belvedere"), equalTo(2)); 277 278 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 279 assertThat(cursor.moveToFirst(), equalTo(true)); 280 assertThat(cursor.getCount(), equalTo(2)); 281 282 assertIdAndName(cursor, 1234L, "Belvedere"); 283 assertThat(cursor.moveToNext(), equalTo(true)); 284 285 assertIdAndName(cursor, 1235L, "Belvedere"); 286 assertThat(cursor.isLast(), equalTo(true)); 287 assertThat(cursor.moveToNext(), equalTo(false)); 288 assertThat(cursor.isAfterLast(), equalTo(true)); 289 assertThat(cursor.moveToNext(), equalTo(false)); 290 } 291 292 @Test 293 public void testDelete() throws Exception { 294 addChuck(); 295 296 int deleted = database.delete("table_name", "id=1234", null); 297 assertThat(deleted, equalTo(1)); 298 299 assertEmptyDatabase(); 300 } 301 302 @Test 303 public void testDeleteNoMatch() throws Exception { 304 addChuck(); 305 306 int deleted = database.delete("table_name", "id=5678", null); 307 assertThat(deleted, equalTo(0)); 308 309 assertNonEmptyDatabase(); 310 } 311 312 @Test 313 public void testDeleteAll() throws Exception { 314 addChuck(); 315 addJulie(); 316 317 int deleted = database.delete("table_name", "1", null); 318 assertThat(deleted, equalTo(2)); 319 320 assertEmptyDatabase(); 321 } 322 323 @Test 324 public void testExecSQL() throws Exception { 325 Statement statement; 326 ResultSet resultSet; 327 328 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 329 330 statement = shadowOf(database).getConnection().createStatement(); 331 resultSet = statement.executeQuery("SELECT COUNT(*) FROM table_name"); 332 assertThat(resultSet.next(), equalTo(true)); 333 assertThat(resultSet.getInt(1), equalTo(1)); 334 335 statement = shadowOf(database).getConnection().createStatement(); 336 resultSet = statement.executeQuery("SELECT * FROM table_name"); 337 assertThat(resultSet.next(), equalTo(true)); 338 assertThat(resultSet.getInt(1), equalTo(1234)); 339 assertThat(resultSet.getString(4), equalTo("Chuck")); 340 } 341 342 @Test 343 public void testExecSQLParams() throws Exception { 344 Statement statement; 345 ResultSet resultSet; 346 347 database.execSQL("CREATE TABLE `routine` (`id` INTEGER PRIMARY KEY AUTOINCREMENT , `name` VARCHAR , `lastUsed` INTEGER DEFAULT 0 , UNIQUE (`name`)) ", new Object[]{}); 348 database.execSQL("INSERT INTO `routine` (`name` ,`lastUsed` ) VALUES (?,?)", new Object[]{"Leg Press", 0}); 349 database.execSQL("INSERT INTO `routine` (`name` ,`lastUsed` ) VALUES (?,?)", new Object[]{"Bench Press", 1}); 350 351 statement = shadowOf(database).getConnection().createStatement(); 352 resultSet = statement.executeQuery("SELECT COUNT(*) FROM `routine`"); 353 assertThat(resultSet.next(), equalTo(true)); 354 assertThat(resultSet.getInt(1), equalTo(2)); 355 356 statement = shadowOf(database).getConnection().createStatement(); 357 resultSet = statement.executeQuery("SELECT `id`, `name` ,`lastUsed` FROM `routine`"); 358 assertThat(resultSet.next(), equalTo(true)); 359 assertThat(resultSet.getInt(1), equalTo(1)); 360 assertThat(resultSet.getString(2), equalTo("Leg Press")); 361 assertThat(resultSet.getInt(3), equalTo(0)); 362 assertThat(resultSet.next(), equalTo(true)); 363 assertThat(resultSet.getLong(1), equalTo(2L)); 364 assertThat(resultSet.getString(2), equalTo("Bench Press")); 365 assertThat(resultSet.getInt(3), equalTo(1)); 366 } 367 368 @Test(expected = android.database.SQLException.class) 369 public void testExecSQLException() throws Exception { 370 database.execSQL("INSERT INTO table_name;"); // invalid SQL 371 } 372 373 @Test(expected = IllegalArgumentException.class) 374 public void testExecSQLException2() throws Exception { 375 database.execSQL("insert into exectable (first_column) values (?);", null); 376 } 377 378 @Test(expected = IllegalArgumentException.class) 379 public void testExecSQLException4() throws Exception { 380 database.execSQL("insert into exectable (first_column) values ('sdfsfs');", null); 381 } 382 383 @Test(expected = Exception.class) 384 public void testExecSQLException5() throws Exception { 385 //TODO: make this throw android.database.SQLException.class 386 database.execSQL("insert into exectable (first_column) values ('kjhk');", new String[]{"xxxx"}); 387 } 388 389 @Test(expected = Exception.class) 390 public void testExecSQLException6() throws Exception { 391 //TODO: make this throw android.database.SQLException.class 392 database.execSQL("insert into exectable (first_column) values ('kdfd');", new String[]{null}); 393 } 394 395 @Test 396 public void testExecSQL2() throws Exception { 397 database.execSQL("insert into exectable (first_column) values ('eff');", new String[]{}); 398 } 399 400 @Test 401 public void testExecSQLInsertNull() throws Exception { 402 String name = "nullone"; 403 404 database.execSQL("insert into exectable (first_column, name) values (?,?);", new String[]{null, name}); 405 406 Cursor cursor = database.rawQuery("select * from exectable WHERE `name` = ?", new String[]{name}); 407 cursor.moveToFirst(); 408 int firstIndex = cursor.getColumnIndex("first_column"); 409 int nameIndex = cursor.getColumnIndex("name"); 410 assertThat(cursor.getString(nameIndex), equalTo(name)); 411 assertThat(cursor.getString(firstIndex), equalTo(null)); 412 413 } 414 415 @Test(expected = Exception.class) 416 public void testExecSQLInsertNullShouldBeException() throws Exception { 417 //this inserts null in android, but it when it happens it is likely an error. H2 throws an exception. So we'll make Robolectric expect an Exception so that the error can be found. 418 419 database.delete("exectable", null, null); 420 421 Cursor cursor = database.rawQuery("select * from exectable", null); 422 cursor.moveToFirst(); 423 assertThat(cursor.getCount(), equalTo(0)); 424 425 database.execSQL("insert into exectable (first_column) values (?);", new String[]{}); 426 Cursor cursor2 = database.rawQuery("select * from exectable", new String[]{null}); 427 cursor.moveToFirst(); 428 assertThat(cursor2.getCount(), equalTo(1)); 429 430 } 431 432 @Test 433 public void testExecSQLAutoIncrementSQLite() throws Exception { 434 database.execSQL("CREATE TABLE auto_table (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255));"); 435 436 ContentValues values = new ContentValues(); 437 values.put("name", "Chuck"); 438 439 long key = database.insert("auto_table", null, values); 440 assertThat(key, not(equalTo(0L))); 441 442 long key2 = database.insert("auto_table", null, values); 443 assertThat(key2, not(equalTo(key))); 444 } 445 446 @Test(expected = IllegalStateException.class) 447 public void testClose() throws Exception { 448 database.close(); 449 450 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 451 } 452 453 @Test 454 public void testIsOpen() throws Exception { 455 assertThat(database.isOpen(), equalTo(true)); 456 database.close(); 457 assertThat(database.isOpen(), equalTo(false)); 458 } 459 460 @Test 461 public void shouldStoreGreatBigHonkinIntegersCorrectly() throws Exception { 462 database.execSQL("INSERT INTO table_name(big_int) VALUES(1234567890123456789);"); 463 Cursor cursor = database.query("table_name", new String[]{"big_int"}, null, null, null, null, null); 464 cursor.moveToFirst(); 465 assertEquals(1234567890123456789L, cursor.getLong(0)); 466 } 467 468 @Test 469 public void testSuccessTransaction() throws SQLException { 470 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 471 database.beginTransaction(); 472 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 473 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 474 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 475 database.setTransactionSuccessful(); 476 assertThat(shDatabase.isTransactionSuccess(), equalTo(true)); 477 database.endTransaction(); 478 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 479 480 Statement statement = shadowOf(database).getConnection().createStatement(); 481 ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM table_name"); 482 assertThat(resultSet.next(), equalTo(true)); 483 assertThat(resultSet.getInt(1), equalTo(1)); 484 } 485 486 @Test 487 public void testFailureTransaction() throws SQLException { 488 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 489 database.beginTransaction(); 490 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 491 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 492 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 493 database.endTransaction(); 494 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 495 496 Statement statement = shadowOf(database).getConnection().createStatement(); 497 ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM table_name"); 498 assertThat(resultSet.next(), equalTo(true)); 499 assertThat(resultSet.getInt(1), equalTo(0)); 500 } 501 502 @Test 503 public void testTransactionAlreadySuccessful() { 504 database.beginTransaction(); 505 database.setTransactionSuccessful(); 506 try { 507 database.setTransactionSuccessful(); 508 fail("didn't receive the expected IllegalStateException"); 509 } catch (IllegalStateException e) { 510 assertThat(e.getMessage(), equalTo("transaction already successfully")); 511 } 512 } 513 514 private void addChuck() { 515 addPerson(1234L, "Chuck"); 516 } 517 518 private void addJulie() { 519 addPerson(1235L, "Julie"); 520 } 521 522 private void addPerson(long id, String name) { 523 ContentValues values = new ContentValues(); 524 values.put("id", id); 525 values.put("name", name); 526 database.insert("table_name", null, values); 527 } 528 529 private int updateName(long id, String name) { 530 ContentValues values = new ContentValues(); 531 values.put("name", name); 532 return database.update("table_name", values, "id=" + id, null); 533 } 534 535 private int updateName(String name) { 536 ContentValues values = new ContentValues(); 537 values.put("name", name); 538 return database.update("table_name", values, null, null); 539 } 540 541 private void assertIdAndName(Cursor cursor, long id, String name) { 542 long idValueFromDatabase; 543 String stringValueFromDatabase; 544 545 idValueFromDatabase = cursor.getLong(0); 546 stringValueFromDatabase = cursor.getString(1); 547 assertThat(idValueFromDatabase, equalTo(id)); 548 assertThat(stringValueFromDatabase, equalTo(name)); 549 } 550 551 private void assertEmptyDatabase() { 552 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 553 assertThat(cursor.moveToFirst(), equalTo(false)); 554 assertThat(cursor.isClosed(), equalTo(false)); 555 assertThat(cursor.getCount(), equalTo(0)); 556 } 557 558 private void assertNonEmptyDatabase() { 559 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 560 assertThat(cursor.moveToFirst(), equalTo(true)); 561 assertThat(cursor.getCount(), not(equalTo(0))); 562 } 563 564 565}