• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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