Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.database.sqlite.cts;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNotNull;
     21 import static org.junit.Assert.assertTrue;
     22 import static org.junit.Assert.fail;
     23 
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.database.Cursor;
     27 import android.database.sqlite.SQLiteCursor;
     28 import android.database.sqlite.SQLiteCursorDriver;
     29 import android.database.sqlite.SQLiteDatabase;
     30 import android.database.sqlite.SQLiteQuery;
     31 import android.database.sqlite.SQLiteQueryBuilder;
     32 import android.os.CancellationSignal;
     33 import android.os.OperationCanceledException;
     34 
     35 import androidx.test.InstrumentationRegistry;
     36 import androidx.test.runner.AndroidJUnit4;
     37 
     38 import org.junit.After;
     39 import org.junit.Before;
     40 import org.junit.Test;
     41 import org.junit.runner.RunWith;
     42 
     43 import java.util.HashMap;
     44 import java.util.Map;
     45 import java.util.Objects;
     46 import java.util.concurrent.Semaphore;
     47 
     48 @RunWith(AndroidJUnit4.class)
     49 public class SQLiteQueryBuilderTest {
     50     private SQLiteDatabase mDatabase;
     51     private final String TEST_TABLE_NAME = "test";
     52     private final String EMPLOYEE_TABLE_NAME = "employee";
     53     private static final String DATABASE_FILE = "database_test.db";
     54 
     55     @Before
     56     public void setUp() throws Exception {
     57         final Context context = InstrumentationRegistry.getTargetContext();
     58 
     59         context.deleteDatabase(DATABASE_FILE);
     60         mDatabase = Objects.requireNonNull(
     61                 context.openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null));
     62     }
     63 
     64     @After
     65     public void tearDown() throws Exception {
     66         final Context context = InstrumentationRegistry.getTargetContext();
     67 
     68         mDatabase.close();
     69         context.deleteDatabase(DATABASE_FILE);
     70     }
     71 
     72     @Test
     73     public void testConstructor() {
     74         new SQLiteQueryBuilder();
     75     }
     76 
     77     @Test
     78     public void testSetDistinct() {
     79         String expected;
     80         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
     81         sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
     82         sqliteQueryBuilder.setDistinct(false);
     83         sqliteQueryBuilder.appendWhere("age=20");
     84         String sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
     85                 null, null, null, null, null);
     86         assertEquals(TEST_TABLE_NAME, sqliteQueryBuilder.getTables());
     87         expected = "SELECT age, address FROM " + TEST_TABLE_NAME + " WHERE (age=20)";
     88         assertEquals(expected, sql);
     89 
     90         sqliteQueryBuilder = new SQLiteQueryBuilder();
     91         sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME);
     92         sqliteQueryBuilder.setDistinct(true);
     93         sqliteQueryBuilder.appendWhere("age>32");
     94         sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
     95                 null, null, null, null, null);
     96         assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables());
     97         expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME + " WHERE (age>32)";
     98         assertEquals(expected, sql);
     99 
    100         sqliteQueryBuilder = new SQLiteQueryBuilder();
    101         sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME);
    102         sqliteQueryBuilder.setDistinct(true);
    103         sqliteQueryBuilder.appendWhereEscapeString("age>32");
    104         sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
    105                 null, null, null, null, null);
    106         assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables());
    107         expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME
    108                 + " WHERE ('age>32')";
    109         assertEquals(expected, sql);
    110     }
    111 
    112     @Test
    113     public void testSetProjectionMap() {
    114         String expected;
    115         Map<String, String> projectMap = new HashMap<String, String>();
    116         projectMap.put("EmployeeName", "name");
    117         projectMap.put("EmployeeAge", "age");
    118         projectMap.put("EmployeeAddress", "address");
    119         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    120         sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
    121         sqliteQueryBuilder.setDistinct(false);
    122         sqliteQueryBuilder.setProjectionMap(projectMap);
    123         String sql = sqliteQueryBuilder.buildQuery(new String[] { "EmployeeName", "EmployeeAge" },
    124                 null, null, null, null, null);
    125         expected = "SELECT name, age FROM " + TEST_TABLE_NAME;
    126         assertEquals(expected, sql);
    127 
    128         sql = sqliteQueryBuilder.buildQuery(null, // projectionIn is null
    129                 null, null, null, null, null);
    130         assertTrue(sql.matches("SELECT (age|name|address), (age|name|address), (age|name|address) "
    131                 + "FROM " + TEST_TABLE_NAME));
    132         assertTrue(sql.contains("age"));
    133         assertTrue(sql.contains("name"));
    134         assertTrue(sql.contains("address"));
    135 
    136         sqliteQueryBuilder.setProjectionMap(null);
    137         sql = sqliteQueryBuilder.buildQuery(new String[] { "name", "address" },
    138                 null, null, null, null, null);
    139         assertTrue(sql.matches("SELECT (name|address), (name|address) "
    140                 + "FROM " + TEST_TABLE_NAME));
    141         assertTrue(sql.contains("name"));
    142         assertTrue(sql.contains("address"));
    143     }
    144 
    145     @Test
    146     public void testSetCursorFactory() {
    147         mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, " +
    148                 "name TEXT, age INTEGER, address TEXT);");
    149         mDatabase.execSQL("INSERT INTO test (name, age, address) VALUES ('Mike', '20', 'LA');");
    150         mDatabase.execSQL("INSERT INTO test (name, age, address) VALUES ('jack', '40', 'LA');");
    151 
    152         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    153         sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
    154         Cursor cursor = sqliteQueryBuilder.query(mDatabase, new String[] { "name", "age" },
    155                 null, null, null, null, null);
    156         assertNotNull(cursor);
    157         assertTrue(cursor instanceof SQLiteCursor);
    158 
    159         SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
    160             @Override
    161             public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
    162                     String editTable, SQLiteQuery query) {
    163                 return new MockCursor(masterQuery, editTable, query);
    164             }
    165         };
    166 
    167         sqliteQueryBuilder.setCursorFactory(factory);
    168         cursor = sqliteQueryBuilder.query(mDatabase, new String[] { "name", "age" },
    169                 null, null, null, null, null);
    170         assertNotNull(cursor);
    171         assertTrue(cursor instanceof MockCursor);
    172     }
    173 
    174     private static class MockCursor extends SQLiteCursor {
    175         public MockCursor(SQLiteCursorDriver driver,
    176                 String editTable, SQLiteQuery query) {
    177             super(driver, editTable, query);
    178         }
    179     }
    180 
    181     @Test
    182     public void testBuildQueryString() {
    183         String expected;
    184         final String[] DEFAULT_TEST_PROJECTION = new String [] { "name", "age", "sum(salary)" };
    185         final String DEFAULT_TEST_WHERE = "age > 25";
    186         final String DEFAULT_HAVING = "sum(salary) > 3000";
    187 
    188         String sql = SQLiteQueryBuilder.buildQueryString(false, "Employee",
    189                 DEFAULT_TEST_PROJECTION,
    190                 DEFAULT_TEST_WHERE, "name", DEFAULT_HAVING, "name", "100");
    191 
    192         expected = "SELECT name, age, sum(salary) FROM Employee WHERE " + DEFAULT_TEST_WHERE +
    193                 " GROUP BY name " +
    194                 "HAVING " + DEFAULT_HAVING + " " +
    195                 "ORDER BY name " +
    196                 "LIMIT 100";
    197         assertEquals(expected, sql);
    198     }
    199 
    200     @Test
    201     public void testBuildQuery() {
    202         final String[] DEFAULT_TEST_PROJECTION = new String[] { "name", "sum(salary)" };
    203         final String DEFAULT_TEST_WHERE = "age > 25";
    204         final String DEFAULT_HAVING = "sum(salary) > 2000";
    205 
    206         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    207         sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
    208         sqliteQueryBuilder.setDistinct(false);
    209         String sql = sqliteQueryBuilder.buildQuery(DEFAULT_TEST_PROJECTION,
    210                 DEFAULT_TEST_WHERE, "name", DEFAULT_HAVING, "name", "2");
    211         String expected = "SELECT name, sum(salary) FROM " + TEST_TABLE_NAME
    212                 + " WHERE (" + DEFAULT_TEST_WHERE + ") " +
    213                 "GROUP BY name HAVING " + DEFAULT_HAVING + " ORDER BY name LIMIT 2";
    214         assertEquals(expected, sql);
    215     }
    216 
    217     @Test
    218     public void testAppendColumns() {
    219         StringBuilder sb = new StringBuilder();
    220         String[] columns = new String[] { "name", "age" };
    221 
    222         assertEquals("", sb.toString());
    223         SQLiteQueryBuilder.appendColumns(sb, columns);
    224         assertEquals("name, age ", sb.toString());
    225     }
    226 
    227     @Test
    228     public void testAppendWhereStandalone() {
    229         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    230         qb.setTables("Employee");
    231         qb.appendWhereStandalone("A");
    232         qb.appendWhereStandalone("B");
    233         qb.appendWhereStandalone("C");
    234 
    235         final String query = qb.buildQuery(null, null, null, null, null, null);
    236         assertTrue(query.contains("(A) AND (B) AND (C)"));
    237     }
    238 
    239     @Test
    240     public void testQuery() {
    241         createEmployeeTable();
    242 
    243         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    244         sqliteQueryBuilder.setTables("Employee");
    245         Cursor cursor = sqliteQueryBuilder.query(mDatabase,
    246                 new String[] { "name", "sum(salary)" }, null, null,
    247                 "name", "sum(salary)>1000", "name");
    248         assertNotNull(cursor);
    249         assertEquals(3, cursor.getCount());
    250 
    251         final int COLUMN_NAME_INDEX = 0;
    252         final int COLUMN_SALARY_INDEX = 1;
    253         cursor.moveToFirst();
    254         assertEquals("Jim", cursor.getString(COLUMN_NAME_INDEX));
    255         assertEquals(4500, cursor.getInt(COLUMN_SALARY_INDEX));
    256         cursor.moveToNext();
    257         assertEquals("Mike", cursor.getString(COLUMN_NAME_INDEX));
    258         assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX));
    259         cursor.moveToNext();
    260         assertEquals("jack", cursor.getString(COLUMN_NAME_INDEX));
    261         assertEquals(3500, cursor.getInt(COLUMN_SALARY_INDEX));
    262 
    263         sqliteQueryBuilder = new SQLiteQueryBuilder();
    264         sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME);
    265         cursor = sqliteQueryBuilder.query(mDatabase,
    266                 new String[] { "name", "sum(salary)" }, null, null,
    267                 "name", "sum(salary)>1000", "name", "2" // limit is 2
    268                 );
    269         assertNotNull(cursor);
    270         assertEquals(2, cursor.getCount());
    271         cursor.moveToFirst();
    272         assertEquals("Jim", cursor.getString(COLUMN_NAME_INDEX));
    273         assertEquals(4500, cursor.getInt(COLUMN_SALARY_INDEX));
    274         cursor.moveToNext();
    275         assertEquals("Mike", cursor.getString(COLUMN_NAME_INDEX));
    276         assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX));
    277     }
    278 
    279     @Test
    280     public void testUnionQuery() {
    281         String expected;
    282         String[] innerProjection = new String[] {"name", "age", "location"};
    283         SQLiteQueryBuilder employeeQueryBuilder = new SQLiteQueryBuilder();
    284         SQLiteQueryBuilder peopleQueryBuilder = new SQLiteQueryBuilder();
    285 
    286         employeeQueryBuilder.setTables("employee");
    287         peopleQueryBuilder.setTables("people");
    288 
    289         String employeeSubQuery = employeeQueryBuilder.buildUnionSubQuery(
    290                 "_id", innerProjection,
    291                 null, 2, "employee",
    292                 "age=25",
    293                 null, null);
    294         String peopleSubQuery = peopleQueryBuilder.buildUnionSubQuery(
    295                 "_id", innerProjection,
    296                 null, 2, "people",
    297                 "location=LA",
    298                 null, null);
    299         expected = "SELECT name, age, location FROM employee WHERE (age=25)";
    300         assertEquals(expected, employeeSubQuery);
    301         expected = "SELECT name, age, location FROM people WHERE (location=LA)";
    302         assertEquals(expected, peopleSubQuery);
    303 
    304         SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
    305 
    306         unionQueryBuilder.setDistinct(true);
    307 
    308         String unionQuery = unionQueryBuilder.buildUnionQuery(
    309                 new String[] { employeeSubQuery, peopleSubQuery }, null, null);
    310         expected = "SELECT name, age, location FROM employee WHERE (age=25) " +
    311                 "UNION SELECT name, age, location FROM people WHERE (location=LA)";
    312         assertEquals(expected, unionQuery);
    313     }
    314 
    315     @Test
    316     public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
    317         createEmployeeTable();
    318 
    319         CancellationSignal cancellationSignal = new CancellationSignal();
    320         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    321         sqliteQueryBuilder.setTables("Employee");
    322         Cursor cursor = sqliteQueryBuilder.query(mDatabase,
    323                 new String[] { "name", "sum(salary)" }, null, null,
    324                 "name", "sum(salary)>1000", "name", null, cancellationSignal);
    325 
    326         assertEquals(3, cursor.getCount());
    327     }
    328 
    329     @Test
    330     public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
    331         createEmployeeTable();
    332 
    333         CancellationSignal cancellationSignal = new CancellationSignal();
    334         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    335         sqliteQueryBuilder.setTables("Employee");
    336 
    337         cancellationSignal.cancel();
    338         try {
    339             sqliteQueryBuilder.query(mDatabase,
    340                     new String[] { "name", "sum(salary)" }, null, null,
    341                     "name", "sum(salary)>1000", "name", null, cancellationSignal);
    342             fail("Expected OperationCanceledException");
    343         } catch (OperationCanceledException ex) {
    344             // expected
    345         }
    346     }
    347 
    348     @Test
    349     public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
    350         createEmployeeTable();
    351 
    352         CancellationSignal cancellationSignal = new CancellationSignal();
    353         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    354         sqliteQueryBuilder.setTables("Employee");
    355 
    356         Cursor cursor = sqliteQueryBuilder.query(mDatabase,
    357                 new String[] { "name", "sum(salary)" }, null, null,
    358                 "name", "sum(salary)>1000", "name", null, cancellationSignal);
    359 
    360         cancellationSignal.cancel();
    361         try {
    362             cursor.getCount(); // force execution
    363             fail("Expected OperationCanceledException");
    364         } catch (OperationCanceledException ex) {
    365             // expected
    366         }
    367     }
    368 
    369     @Test
    370     public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() {
    371         createEmployeeTable();
    372 
    373         for (int i = 0; i < 5; i++) {
    374             final CancellationSignal cancellationSignal = new CancellationSignal();
    375             final Semaphore barrier1 = new Semaphore(0);
    376             final Semaphore barrier2 = new Semaphore(0);
    377             Thread contentionThread = new Thread() {
    378                 @Override
    379                 public void run() {
    380                     mDatabase.beginTransaction(); // acquire the only available connection
    381                     barrier1.release(); // release query to start running
    382                     try {
    383                         barrier2.acquire(); // wait for test to end
    384                     } catch (InterruptedException e) {
    385                     }
    386                     mDatabase.endTransaction(); // release the connection
    387                 }
    388             };
    389             Thread cancellationThread = new Thread() {
    390                 @Override
    391                 public void run() {
    392                     try {
    393                         Thread.sleep(300);
    394                     } catch (InterruptedException ex) {
    395                     }
    396                     cancellationSignal.cancel();
    397                 }
    398             };
    399             try {
    400                 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    401                 sqliteQueryBuilder.setTables("Employee");
    402 
    403                 contentionThread.start();
    404                 cancellationThread.start();
    405 
    406                 try {
    407                     barrier1.acquire(); // wait for contention thread to start transaction
    408                 } catch (InterruptedException e) {
    409                 }
    410 
    411                 final long startTime = System.nanoTime();
    412                 try {
    413                     Cursor cursor = sqliteQueryBuilder.query(mDatabase,
    414                             new String[] { "name", "sum(salary)" }, null, null,
    415                             "name", "sum(salary)>1000", "name", null, cancellationSignal);
    416                     cursor.getCount(); // force execution
    417                     fail("Expected OperationCanceledException");
    418                 } catch (OperationCanceledException ex) {
    419                     // expected
    420                 }
    421 
    422                 // We want to confirm that the query really was blocked trying to acquire a
    423                 // connection for a certain amount of time before it was freed by cancel.
    424                 final long waitTime = System.nanoTime() - startTime;
    425                 if (waitTime > 150 * 1000000L) {
    426                     return; // success!
    427                 }
    428             } finally {
    429                 barrier1.release();
    430                 barrier2.release();
    431                 try {
    432                     contentionThread.join();
    433                     cancellationThread.join();
    434                 } catch (InterruptedException e) {
    435                 }
    436             }
    437         }
    438 
    439         // Occasionally we might miss the timing deadline due to factors in the
    440         // environment, but if after several trials we still couldn't demonstrate
    441         // that the query was blocked, then the test must be broken.
    442         fail("Could not prove that the query actually blocked before cancel() was called.");
    443     }
    444 
    445     @Test
    446     public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() {
    447         // Populate a table with a bunch of integers.
    448         mDatabase.execSQL("CREATE TABLE x (v INTEGER);");
    449         for (int i = 0; i < 100; i++) {
    450             mDatabase.execSQL("INSERT INTO x VALUES (?)", new Object[] { i });
    451         }
    452 
    453         for (int i = 0; i < 5; i++) {
    454             final CancellationSignal cancellationSignal = new CancellationSignal();
    455             Thread cancellationThread = new Thread() {
    456                 @Override
    457                 public void run() {
    458                     try {
    459                         Thread.sleep(300);
    460                     } catch (InterruptedException ex) {
    461                     }
    462                     cancellationSignal.cancel();
    463                 }
    464             };
    465             try {
    466                 // Build an unsatisfiable 5-way cross-product query over 100 values but
    467                 // produces no output.  This should force SQLite to loop for a long time
    468                 // as it tests 10^10 combinations.
    469                 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
    470                 sqliteQueryBuilder.setTables("x AS a, x AS b, x AS c, x AS d, x AS e");
    471 
    472                 cancellationThread.start();
    473 
    474                 final long startTime = System.nanoTime();
    475                 try {
    476                     Cursor cursor = sqliteQueryBuilder.query(mDatabase, null,
    477                             "a.v + b.v + c.v + d.v + e.v > 1000000",
    478                             null, null, null, null, null, cancellationSignal);
    479                     cursor.getCount(); // force execution
    480                     fail("Expected OperationCanceledException");
    481                 } catch (OperationCanceledException ex) {
    482                     // expected
    483                 }
    484 
    485                 // We want to confirm that the query really was running and then got
    486                 // canceled midway.
    487                 final long waitTime = System.nanoTime() - startTime;
    488                 if (waitTime > 150 * 1000000L && waitTime < 600 * 1000000L) {
    489                     return; // success!
    490                 }
    491             } finally {
    492                 try {
    493                     cancellationThread.join();
    494                 } catch (InterruptedException e) {
    495                 }
    496             }
    497         }
    498 
    499         // Occasionally we might miss the timing deadline due to factors in the
    500         // environment, but if after several trials we still couldn't demonstrate
    501         // that the query was canceled, then the test must be broken.
    502         fail("Could not prove that the query actually canceled midway during execution.");
    503     }
    504 
    505     @Test
    506     public void testUpdate() throws Exception {
    507         createEmployeeTable();
    508 
    509         final ContentValues values = new ContentValues();
    510         values.put("name", "Anonymous");
    511         values.put("salary", 0);
    512 
    513         {
    514             final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    515             qb.setTables("employee");
    516             qb.appendWhere("month=3");
    517             assertEquals(2, qb.update(mDatabase, values, null, null));
    518         }
    519         {
    520             final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    521             qb.setTables("employee");
    522             assertEquals(1, qb.update(mDatabase, values, "month=?", new String[] { "2" }));
    523         }
    524     }
    525 
    526     @Test
    527     public void testDelete() throws Exception {
    528         createEmployeeTable();
    529 
    530         {
    531             final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    532             qb.setTables("employee");
    533             qb.appendWhere("month=3");
    534             assertEquals(2, qb.delete(mDatabase, null, null));
    535             assertEquals(0, qb.delete(mDatabase, null, null));
    536         }
    537         {
    538             final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    539             qb.setTables("employee");
    540             assertEquals(1, qb.delete(mDatabase, "month=?", new String[] { "2" }));
    541             assertEquals(0, qb.delete(mDatabase, "month=?", new String[] { "2" }));
    542         }
    543     }
    544 
    545     @Test
    546     public void testStrictQuery() throws Exception {
    547         createEmployeeTable();
    548 
    549         final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    550         qb.setTables("employee");
    551         qb.setStrict(true);
    552         qb.appendWhere("month=2");
    553 
    554         // Should normally only be able to see one row
    555         try (Cursor c = qb.query(mDatabase, null, null, null, null, null, null)) {
    556             assertEquals(1, c.getCount());
    557         }
    558 
    559         // Trying sneaky queries should fail; even if they somehow succeed, we
    560         // shouldn't get to see any other data.
    561         try (Cursor c = qb.query(mDatabase, null, "1=1", null, null, null, null)) {
    562             assertEquals(1, c.getCount());
    563         } catch (Exception tolerated) {
    564         }
    565         try (Cursor c = qb.query(mDatabase, null, "1=1 --", null, null, null, null)) {
    566             assertEquals(1, c.getCount());
    567         } catch (Exception tolerated) {
    568         }
    569         try (Cursor c = qb.query(mDatabase, null, "1=1) OR (1=1", null, null, null, null)) {
    570             assertEquals(1, c.getCount());
    571         } catch (Exception tolerated) {
    572         }
    573         try (Cursor c = qb.query(mDatabase, null, "1=1)) OR ((1=1", null, null, null, null)) {
    574             assertEquals(1, c.getCount());
    575         } catch (Exception tolerated) {
    576         }
    577     }
    578 
    579     @Test
    580     public void testStrictUpdate() throws Exception {
    581         createEmployeeTable();
    582 
    583         final ContentValues values = new ContentValues();
    584         values.put("name", "Anonymous");
    585         values.put("salary", 0);
    586 
    587         final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    588         qb.setTables("employee");
    589         qb.setStrict(true);
    590         qb.appendWhere("month=2");
    591 
    592         // Should normally only be able to update one row
    593         assertEquals(1, qb.update(mDatabase, values, null, null));
    594 
    595         // Trying sneaky queries should fail; even if they somehow succeed, we
    596         // shouldn't get to see any other data.
    597         try {
    598             assertEquals(1, qb.update(mDatabase, values, "1=1", null));
    599         } catch (Exception tolerated) {
    600         }
    601         try {
    602             assertEquals(1, qb.update(mDatabase, values, "1=1 --", null));
    603         } catch (Exception tolerated) {
    604         }
    605         try {
    606             assertEquals(1, qb.update(mDatabase, values, "1=1) OR (1=1", null));
    607         } catch (Exception tolerated) {
    608         }
    609         try {
    610             assertEquals(1, qb.update(mDatabase, values, "1=1)) OR ((1=1", null));
    611         } catch (Exception tolerated) {
    612         }
    613     }
    614 
    615     @Test
    616     public void testStrictDelete() throws Exception {
    617         final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    618         qb.setTables("employee");
    619         qb.setStrict(true);
    620         qb.appendWhere("month=2");
    621 
    622         // Should normally only be able to update one row
    623         createEmployeeTable();
    624         assertEquals(1, qb.delete(mDatabase, null, null));
    625 
    626         // Trying sneaky queries should fail; even if they somehow succeed, we
    627         // shouldn't get to see any other data.
    628         try {
    629             createEmployeeTable();
    630             assertEquals(1, qb.delete(mDatabase, "1=1", null));
    631         } catch (Exception tolerated) {
    632         }
    633         try {
    634             createEmployeeTable();
    635             assertEquals(1, qb.delete(mDatabase, "1=1 --", null));
    636         } catch (Exception tolerated) {
    637         }
    638         try {
    639             createEmployeeTable();
    640             assertEquals(1, qb.delete(mDatabase, "1=1) OR (1=1", null));
    641         } catch (Exception tolerated) {
    642         }
    643         try {
    644             createEmployeeTable();
    645             assertEquals(1, qb.delete(mDatabase, "1=1)) OR ((1=1", null));
    646         } catch (Exception tolerated) {
    647         }
    648     }
    649 
    650     private void createEmployeeTable() {
    651         mDatabase.execSQL("DROP TABLE IF EXISTS employee;");
    652         mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " +
    653                 "name TEXT, month INTEGER, salary INTEGER);");
    654         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
    655                 "VALUES ('Mike', '1', '1000');");
    656         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
    657                 "VALUES ('Mike', '2', '3000');");
    658         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
    659                 "VALUES ('jack', '1', '2000');");
    660         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
    661                 "VALUES ('jack', '3', '1500');");
    662         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
    663                 "VALUES ('Jim', '1', '1000');");
    664         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
    665                 "VALUES ('Jim', '3', '3500');");
    666     }
    667 }
    668