• 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 com.google.common.base.Ascii;
16 import com.google.common.base.Throwables;
17 import com.google.common.io.ByteStreams;
18 import java.io.File;
19 import java.io.PrintStream;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.Executors;
24 import org.junit.After;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.robolectric.annotation.Config;
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 */
170     @Override
finalize()171     public void finalize() throws Throwable {
172       super.finalize();
173     }
174   }
175 
176   // TODO(hoisie): This test crashes in emulators, enable when it is fixed in Android.
177   @SdkSuppress(minSdkVersion = 34)
178   @Test
cursorWindow_finalize_concurrentStressTest()179   public void cursorWindow_finalize_concurrentStressTest() throws Throwable {
180     final PrintStream originalErr = System.err;
181     // discard stderr output for this test to prevent CloseGuard logspam.
182     System.setErr(new PrintStream(ByteStreams.nullOutputStream()));
183     try {
184       ExecutorService executor = Executors.newFixedThreadPool(4);
185       for (int i = 0; i < 1000; i++) {
186         final MyCursorWindow cursorWindow = new MyCursorWindow(String.valueOf(i));
187         for (int j = 0; j < 4; j++) {
188           executor.execute(
189               () -> {
190                 try {
191                   cursorWindow.finalize();
192                 } catch (Throwable e) {
193                   throw new AssertionError(e);
194                 }
195               });
196         }
197       }
198       executor.shutdown();
199       executor.awaitTermination(100, SECONDS);
200     } finally {
201       System.setErr(originalErr);
202     }
203   }
204 
205   @Test
206   @Config(minSdk = LOLLIPOP)
207   @SdkSuppress(minSdkVersion = LOLLIPOP)
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