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}