• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package android.database;
2 
3 import static android.os.Build.VERSION_CODES.M;
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.internal.DoNotInstrument;
30 
31 /** Compatibility test for {@link android.database.sqlite.SQLiteDatabase} */
32 @DoNotInstrument
33 @RunWith(AndroidJUnit4.class)
34 public class SQLiteDatabaseTest {
35 
36   private SQLiteDatabase database;
37   private File databasePath;
38 
39   @Before
setUp()40   public void setUp() {
41     databasePath = ApplicationProvider.getApplicationContext().getDatabasePath("database.db");
42     databasePath.getParentFile().mkdirs();
43 
44     database = SQLiteDatabase.openOrCreateDatabase(databasePath, null);
45     database.execSQL(
46         "CREATE TABLE table_name (\n"
47             + "  id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
48             + "  first_column VARCHAR(255),\n"
49             + "  second_column BINARY,\n"
50             + "  name VARCHAR(255),\n"
51             + "  big_int INTEGER\n"
52             + ");");
53   }
54 
55   @After
tearDown()56   public void tearDown() {
57     database.close();
58     assertThat(databasePath.delete()).isTrue();
59   }
60 
61   @Test
shouldGetBlobFromString()62   public void shouldGetBlobFromString() {
63     String s = "this is a string";
64     ContentValues values = new ContentValues();
65     values.put("first_column", s);
66 
67     database.insert("table_name", null, values);
68 
69     try (Cursor data =
70         database.query("table_name", new String[] {"first_column"}, null, null, null, null, null)) {
71       assertThat(data.getCount()).isEqualTo(1);
72       data.moveToFirst();
73       byte[] columnBytes = data.getBlob(0);
74       byte[] expected = Arrays.copyOf(s.getBytes(), s.length() + 1); // include zero-terminal
75       assertThat(columnBytes).isEqualTo(expected);
76     }
77   }
78 
79   @Test
shouldGetBlobFromNullString()80   public void shouldGetBlobFromNullString() {
81     ContentValues values = new ContentValues();
82     values.put("first_column", (String) null);
83     database.insert("table_name", null, values);
84 
85     try (Cursor data =
86         database.query("table_name", new String[] {"first_column"}, null, null, null, null, null)) {
87       assertThat(data.getCount()).isEqualTo(1);
88       data.moveToFirst();
89       assertThat(data.getBlob(0)).isEqualTo(null);
90     }
91   }
92 
93   @Test
shouldGetBlobFromEmptyString()94   public void shouldGetBlobFromEmptyString() {
95     ContentValues values = new ContentValues();
96     values.put("first_column", "");
97     database.insert("table_name", null, values);
98 
99     try (Cursor data =
100         database.query("table_name", new String[] {"first_column"}, null, null, null, null, null)) {
101       assertThat(data.getCount()).isEqualTo(1);
102       data.moveToFirst();
103       assertThat(data.getBlob(0)).isEqualTo(new byte[] {0});
104     }
105   }
106 
107   @Test
shouldThrowWhenForeignKeysConstraintIsViolated()108   public void shouldThrowWhenForeignKeysConstraintIsViolated() {
109     database.execSQL(
110         "CREATE TABLE artist(\n"
111             + "  artistid    INTEGER PRIMARY KEY, \n"
112             + "  artistname  TEXT\n"
113             + ");\n");
114 
115     database.execSQL(
116         "CREATE TABLE track(\n"
117             + "  trackid     INTEGER, \n"
118             + "  trackname   TEXT, \n"
119             + "  trackartist INTEGER,\n"
120             + "  FOREIGN KEY(trackartist) REFERENCES artist(artistid)\n"
121             + ");");
122 
123     database.execSQL("PRAGMA foreign_keys=ON");
124     database.execSQL("INSERT into artist (artistid, artistname) VALUES (1, 'Kanye')");
125     database.execSQL(
126         "INSERT into track (trackid, trackname, trackartist) VALUES (1, 'Good Life', 1)");
127     SQLiteConstraintException ex =
128         assertThrows(SQLiteConstraintException.class, () -> database.execSQL("delete from artist"));
129     assertThat(Ascii.toLowerCase(Throwables.getStackTraceAsString(ex))).contains("foreign key");
130   }
131 
132   @Test
shouldDeleteWithLikeEscape()133   public void shouldDeleteWithLikeEscape() {
134     ContentValues values = new ContentValues();
135     values.put("first_column", "test");
136     database.insert("table_name", null, values);
137     String select = "first_column LIKE ? ESCAPE ?";
138     String[] selectArgs = {
139       "test", Character.toString('\\'),
140     };
141     assertThat(database.delete("table_name", select, selectArgs)).isEqualTo(1);
142   }
143 
144   @Test
shouldThrowsExceptionWhenQueryingUsingExecSQL()145   public void shouldThrowsExceptionWhenQueryingUsingExecSQL() {
146     SQLiteException e = assertThrows(SQLiteException.class, () -> database.execSQL("select 1"));
147     assertThat(e)
148         .hasMessageThat()
149         .contains("Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
150   }
151 
152   @Test
close_withExclusiveLockingMode()153   public void close_withExclusiveLockingMode() {
154     database.rawQuery("PRAGMA locking_mode = EXCLUSIVE", new String[0]).close();
155     ContentValues values = new ContentValues();
156     values.put("first_column", "");
157     database.insert("table_name", null, values);
158     database.close();
159 
160     database = SQLiteDatabase.openOrCreateDatabase(databasePath, null);
161     database.insert("table_name", null, values);
162   }
163 
164   static class MyCursorWindow extends CursorWindow {
MyCursorWindow(String name)165     public MyCursorWindow(String name) {
166       super(name);
167     }
168 
169     /** Make the finalize method public for testing purposes. */
170     @SuppressWarnings("Finalize")
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
collate_unicode()208   public void collate_unicode() {
209     String[] names = new String[] {"aaa", "abc", "ABC", "bbb"};
210     for (String name : names) {
211       ContentValues values = new ContentValues();
212       values.put("name", name);
213       database.insert("table_name", null, values);
214     }
215     Cursor c =
216         database.rawQuery("SELECT name from table_name ORDER BY name COLLATE UNICODE ASC", null);
217     c.moveToFirst();
218     ArrayList<String> sorted = new ArrayList<>();
219     while (!c.isAfterLast()) {
220       sorted.add(c.getString(0));
221       c.moveToNext();
222     }
223     c.close();
224     assertThat(sorted).containsExactly("aaa", "abc", "ABC", "bbb").inOrder();
225   }
226 
227   @Test
regex_selection()228   public void regex_selection() {
229     ContentValues values = new ContentValues();
230     values.put("first_column", "test");
231     database.insert("table_name", null, values);
232     String select = "first_column regexp ?";
233     String[] selectArgs = {
234       "test",
235     };
236     assertThat(database.delete("table_name", select, selectArgs)).isEqualTo(1);
237   }
238 
239   @Test
240   @SdkSuppress(minSdkVersion = M) // This test fails on emulators for SDKs 21 and 22
fts4()241   public void fts4() {
242     database.execSQL(
243         "CREATE VIRTUAL TABLE documents USING fts4 ("
244             + "id INTEGER PRIMARY KEY, "
245             + "title TEXT, "
246             + "content TEXT"
247             + ")");
248     ContentValues values = new ContentValues();
249     values.put("title", "Title1");
250     values.put("content", "Hello World");
251     database.insert("documents", null, values);
252 
253     String[] columns = {"id"};
254     Cursor results =
255         database.query(
256             "documents",
257             columns,
258             "documents MATCH 'content:*Wor* OR title:*Wor*'",
259             null,
260             null,
261             null,
262             null);
263     assertThat(results.getCount()).isEqualTo(1);
264     results.close();
265 
266     // Android does not support parenthesis in MATCH clauses
267     // See https://github.com/robolectric/robolectric/issues/8495
268     results =
269         database.query(
270             "documents",
271             columns,
272             "documents MATCH '(content:*Wor* OR title:*Wor*)'",
273             null,
274             null,
275             null,
276             null);
277     assertThat(results.getCount()).isEqualTo(0);
278     results.close();
279   }
280 
281   @Test
uniqueConstraintViolation_errorMessage()282   public void uniqueConstraintViolation_errorMessage() {
283     database.execSQL(
284         "CREATE TABLE my_table(\n"
285             + "  _id INTEGER PRIMARY KEY AUTOINCREMENT, \n"
286             + "  unique_column TEXT UNIQUE\n"
287             + ");\n");
288     ContentValues values = new ContentValues();
289     values.put("unique_column", "test");
290     database.insertOrThrow("my_table", null, values);
291     SQLiteConstraintException exception =
292         assertThrows(
293             SQLiteConstraintException.class,
294             () -> database.insertOrThrow("my_table", null, values));
295     assertThat(exception)
296         .hasMessageThat()
297         .startsWith("UNIQUE constraint failed: my_table.unique_column");
298     assertThat(exception).hasMessageThat().contains("code 2067");
299   }
300 }
301