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 com.android.providers.media.util; 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.os.Build; 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 com.google.common.util.concurrent.Uninterruptibles; 39 40 import org.junit.After; 41 import org.junit.Before; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 45 import java.util.Arrays; 46 import java.util.HashMap; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.concurrent.Semaphore; 50 51 @RunWith(AndroidJUnit4.class) 52 public class SQLiteQueryBuilderTest { 53 private SQLiteDatabase mDatabase; 54 private SQLiteQueryBuilder mStrictBuilder; 55 56 private final String TEST_TABLE_NAME = "test"; 57 private final String EMPLOYEE_TABLE_NAME = "employee"; 58 private static final String DATABASE_FILE = "database_test.db"; 59 60 @Before setUp()61 public void setUp() throws Exception { 62 final Context context = InstrumentationRegistry.getTargetContext(); 63 64 context.deleteDatabase(DATABASE_FILE); 65 mDatabase = Objects.requireNonNull( 66 context.openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null)); 67 68 createEmployeeTable(); 69 createStrictQueryBuilder(); 70 } 71 72 @After tearDown()73 public void tearDown() throws Exception { 74 final Context context = InstrumentationRegistry.getTargetContext(); 75 76 mDatabase.close(); 77 context.deleteDatabase(DATABASE_FILE); 78 } 79 80 @Test testConstructor()81 public void testConstructor() { 82 new SQLiteQueryBuilder(); 83 } 84 85 @Test testSetDistinct()86 public void testSetDistinct() { 87 String expected; 88 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 89 sqliteQueryBuilder.setTables(TEST_TABLE_NAME); 90 sqliteQueryBuilder.setDistinct(false); 91 sqliteQueryBuilder.appendWhere("age=20"); 92 String sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" }, 93 null, null, null, null, null); 94 assertEquals(TEST_TABLE_NAME, sqliteQueryBuilder.getTables()); 95 expected = "SELECT age, address FROM " + TEST_TABLE_NAME + " WHERE (age=20)"; 96 assertEquals(expected, sql); 97 98 sqliteQueryBuilder = new SQLiteQueryBuilder(); 99 sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME); 100 sqliteQueryBuilder.setDistinct(true); 101 sqliteQueryBuilder.appendWhere("age>32"); 102 sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" }, 103 null, null, null, null, null); 104 assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables()); 105 expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME + " WHERE (age>32)"; 106 assertEquals(expected, sql); 107 108 sqliteQueryBuilder = new SQLiteQueryBuilder(); 109 sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME); 110 sqliteQueryBuilder.setDistinct(true); 111 sqliteQueryBuilder.appendWhereEscapeString("age>32"); 112 sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" }, 113 null, null, null, null, null); 114 assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables()); 115 expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME 116 + " WHERE ('age>32')"; 117 assertEquals(expected, sql); 118 } 119 120 @Test testSetProjectionMap()121 public void testSetProjectionMap() { 122 String expected; 123 Map<String, String> projectMap = new HashMap<String, String>(); 124 projectMap.put("EmployeeName", "name"); 125 projectMap.put("EmployeeAge", "age"); 126 projectMap.put("EmployeeAddress", "address"); 127 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 128 sqliteQueryBuilder.setTables(TEST_TABLE_NAME); 129 sqliteQueryBuilder.setDistinct(false); 130 sqliteQueryBuilder.setProjectionMap(projectMap); 131 String sql = sqliteQueryBuilder.buildQuery(new String[] { "EmployeeName", "EmployeeAge" }, 132 null, null, null, null, null); 133 expected = "SELECT name, age FROM " + TEST_TABLE_NAME; 134 assertEquals(expected, sql); 135 136 sql = sqliteQueryBuilder.buildQuery(null, // projectionIn is null 137 null, null, null, null, null); 138 assertTrue(sql.matches("SELECT (age|name|address), (age|name|address), (age|name|address) " 139 + "FROM " + TEST_TABLE_NAME)); 140 assertTrue(sql.contains("age")); 141 assertTrue(sql.contains("name")); 142 assertTrue(sql.contains("address")); 143 144 sqliteQueryBuilder.setProjectionMap(null); 145 sql = sqliteQueryBuilder.buildQuery(new String[] { "name", "address" }, 146 null, null, null, null, null); 147 assertTrue(sql.matches("SELECT (name|address), (name|address) " 148 + "FROM " + TEST_TABLE_NAME)); 149 assertTrue(sql.contains("name")); 150 assertTrue(sql.contains("address")); 151 } 152 153 private static class MockCursor extends SQLiteCursor { MockCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query)154 public MockCursor(SQLiteCursorDriver driver, 155 String editTable, SQLiteQuery query) { 156 super(driver, editTable, query); 157 } 158 } 159 160 @Test testBuildQueryString()161 public void testBuildQueryString() { 162 String expected; 163 final String[] DEFAULT_TEST_PROJECTION = new String [] { "name", "age", "sum(salary)" }; 164 final String DEFAULT_TEST_WHERE = "age > 25"; 165 final String DEFAULT_HAVING = "sum(salary) > 3000"; 166 167 String sql = SQLiteQueryBuilder.buildQueryString(false, "Employee", 168 DEFAULT_TEST_PROJECTION, 169 DEFAULT_TEST_WHERE, "name", DEFAULT_HAVING, "name", "100"); 170 171 expected = "SELECT name, age, sum(salary) FROM Employee WHERE " + DEFAULT_TEST_WHERE + 172 " GROUP BY name " + 173 "HAVING " + DEFAULT_HAVING + " " + 174 "ORDER BY name " + 175 "LIMIT 100"; 176 assertEquals(expected, sql); 177 } 178 179 @Test testBuildQuery()180 public void testBuildQuery() { 181 final String[] DEFAULT_TEST_PROJECTION = new String[] { "name", "sum(salary)" }; 182 final String DEFAULT_TEST_WHERE = "age > 25"; 183 final String DEFAULT_HAVING = "sum(salary) > 2000"; 184 185 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 186 sqliteQueryBuilder.setTables(TEST_TABLE_NAME); 187 sqliteQueryBuilder.setDistinct(false); 188 String sql = sqliteQueryBuilder.buildQuery(DEFAULT_TEST_PROJECTION, 189 DEFAULT_TEST_WHERE, "name", DEFAULT_HAVING, "name", "2"); 190 String expected = "SELECT name, sum(salary) FROM " + TEST_TABLE_NAME 191 + " WHERE (" + DEFAULT_TEST_WHERE + ") " + 192 "GROUP BY name HAVING " + DEFAULT_HAVING + " ORDER BY name LIMIT 2"; 193 assertEquals(expected, sql); 194 } 195 196 @Test testAppendColumns()197 public void testAppendColumns() { 198 StringBuilder sb = new StringBuilder(); 199 String[] columns = new String[] { "name", "age" }; 200 201 assertEquals("", sb.toString()); 202 SQLiteQueryBuilder.appendColumns(sb, columns); 203 assertEquals("name, age ", sb.toString()); 204 } 205 206 @Test testAppendWhereStandalone()207 public void testAppendWhereStandalone() { 208 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 209 qb.setTables("Employee"); 210 qb.appendWhereStandalone("A"); 211 qb.appendWhereStandalone("B"); 212 qb.appendWhereStandalone("C"); 213 214 final String query = qb.buildQuery(null, null, null, null, null, null); 215 assertTrue(query.contains("(A) AND (B) AND (C)")); 216 } 217 218 @Test testQuery()219 public void testQuery() { 220 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 221 sqliteQueryBuilder.setTables("Employee"); 222 Cursor cursor = sqliteQueryBuilder.query(mDatabase, 223 new String[] { "name", "sum(salary)" }, null, null, 224 "name", "sum(salary)>1000", "name", null, null); 225 assertNotNull(cursor); 226 assertEquals(3, cursor.getCount()); 227 228 final int COLUMN_NAME_INDEX = 0; 229 final int COLUMN_SALARY_INDEX = 1; 230 cursor.moveToFirst(); 231 assertEquals("Jim", cursor.getString(COLUMN_NAME_INDEX)); 232 assertEquals(4500, cursor.getInt(COLUMN_SALARY_INDEX)); 233 cursor.moveToNext(); 234 assertEquals("Mike", cursor.getString(COLUMN_NAME_INDEX)); 235 assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX)); 236 cursor.moveToNext(); 237 assertEquals("jack", cursor.getString(COLUMN_NAME_INDEX)); 238 assertEquals(3500, cursor.getInt(COLUMN_SALARY_INDEX)); 239 240 sqliteQueryBuilder = new SQLiteQueryBuilder(); 241 sqliteQueryBuilder.setTables(EMPLOYEE_TABLE_NAME); 242 cursor = sqliteQueryBuilder.query(mDatabase, 243 new String[] { "name", "sum(salary)" }, null, null, 244 "name", "sum(salary)>1000", "name", "2" // limit is 2 245 , null); 246 assertNotNull(cursor); 247 assertEquals(2, cursor.getCount()); 248 cursor.moveToFirst(); 249 assertEquals("Jim", cursor.getString(COLUMN_NAME_INDEX)); 250 assertEquals(4500, cursor.getInt(COLUMN_SALARY_INDEX)); 251 cursor.moveToNext(); 252 assertEquals("Mike", cursor.getString(COLUMN_NAME_INDEX)); 253 assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX)); 254 } 255 256 @Test testCancelableQuery_WhenNotCanceled_ReturnsResultSet()257 public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() { 258 CancellationSignal cancellationSignal = new CancellationSignal(); 259 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 260 sqliteQueryBuilder.setTables("Employee"); 261 Cursor cursor = sqliteQueryBuilder.query(mDatabase, 262 new String[] { "name", "sum(salary)" }, null, null, 263 "name", "sum(salary)>1000", "name", null, cancellationSignal); 264 265 assertEquals(3, cursor.getCount()); 266 } 267 268 @Test testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately()269 public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() { 270 CancellationSignal cancellationSignal = new CancellationSignal(); 271 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 272 sqliteQueryBuilder.setTables("Employee"); 273 274 cancellationSignal.cancel(); 275 try { 276 sqliteQueryBuilder.query(mDatabase, 277 new String[] { "name", "sum(salary)" }, null, null, 278 "name", "sum(salary)>1000", "name", null, cancellationSignal); 279 fail("Expected OperationCanceledException"); 280 } catch (OperationCanceledException ex) { 281 // expected 282 } 283 } 284 285 @Test testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted()286 public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() { 287 CancellationSignal cancellationSignal = new CancellationSignal(); 288 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 289 sqliteQueryBuilder.setTables("Employee"); 290 291 Cursor cursor = sqliteQueryBuilder.query(mDatabase, 292 new String[] { "name", "sum(salary)" }, null, null, 293 "name", "sum(salary)>1000", "name", null, cancellationSignal); 294 295 cancellationSignal.cancel(); 296 try { 297 cursor.getCount(); // force execution 298 fail("Expected OperationCanceledException"); 299 } catch (OperationCanceledException ex) { 300 // expected 301 } 302 } 303 304 @Test testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows()305 public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() { 306 for (int i = 0; i < 5; i++) { 307 final CancellationSignal cancellationSignal = new CancellationSignal(); 308 final Semaphore barrier1 = new Semaphore(0); 309 final Semaphore barrier2 = new Semaphore(0); 310 Thread contentionThread = new Thread() { 311 @Override 312 public void run() { 313 mDatabase.beginTransaction(); // acquire the only available connection 314 barrier1.release(); // release query to start running 315 try { 316 barrier2.acquire(); // wait for test to end 317 } catch (InterruptedException e) { 318 } 319 mDatabase.endTransaction(); // release the connection 320 } 321 }; 322 Thread cancellationThread = new Thread() { 323 @Override 324 public void run() { 325 try { 326 Thread.sleep(300); 327 } catch (InterruptedException ex) { 328 } 329 cancellationSignal.cancel(); 330 } 331 }; 332 try { 333 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 334 sqliteQueryBuilder.setTables("Employee"); 335 336 contentionThread.start(); 337 cancellationThread.start(); 338 339 try { 340 barrier1.acquire(); // wait for contention thread to start transaction 341 } catch (InterruptedException e) { 342 } 343 344 final long startTime = System.nanoTime(); 345 try { 346 Cursor cursor = sqliteQueryBuilder.query(mDatabase, 347 new String[] { "name", "sum(salary)" }, null, null, 348 "name", "sum(salary)>1000", "name", null, cancellationSignal); 349 cursor.getCount(); // force execution 350 fail("Expected OperationCanceledException"); 351 } catch (OperationCanceledException ex) { 352 // expected 353 } 354 355 // We want to confirm that the query really was blocked trying to acquire a 356 // connection for a certain amount of time before it was freed by cancel. 357 final long waitTime = System.nanoTime() - startTime; 358 if (waitTime > 150 * 1000000L) { 359 return; // success! 360 } 361 } finally { 362 barrier1.release(); 363 barrier2.release(); 364 Uninterruptibles.joinUninterruptibly(contentionThread); 365 Uninterruptibles.joinUninterruptibly(cancellationThread); 366 } 367 } 368 369 // Occasionally we might miss the timing deadline due to factors in the 370 // environment, but if after several trials we still couldn't demonstrate 371 // that the query was blocked, then the test must be broken. 372 fail("Could not prove that the query actually blocked before cancel() was called."); 373 } 374 375 @Test testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows()376 public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() { 377 // Populate a table with a bunch of integers. 378 mDatabase.execSQL("CREATE TABLE x (v INTEGER);"); 379 for (int i = 0; i < 100; i++) { 380 mDatabase.execSQL("INSERT INTO x VALUES (?)", new Object[] { i }); 381 } 382 383 for (int i = 0; i < 5; i++) { 384 final CancellationSignal cancellationSignal = new CancellationSignal(); 385 Thread cancellationThread = new Thread() { 386 @Override 387 public void run() { 388 try { 389 Thread.sleep(300); 390 } catch (InterruptedException ex) { 391 } 392 cancellationSignal.cancel(); 393 } 394 }; 395 try { 396 // Build an unsatisfiable 5-way cross-product query over 100 values but 397 // produces no output. This should force SQLite to loop for a long time 398 // as it tests 10^10 combinations. 399 SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); 400 sqliteQueryBuilder.setTables("x AS a, x AS b, x AS c, x AS d, x AS e"); 401 402 cancellationThread.start(); 403 404 final long startTime = System.nanoTime(); 405 try { 406 Cursor cursor = sqliteQueryBuilder.query(mDatabase, null, 407 "a.v + b.v + c.v + d.v + e.v > 1000000", 408 null, null, null, null, null, cancellationSignal); 409 cursor.getCount(); // force execution 410 fail("Expected OperationCanceledException"); 411 } catch (OperationCanceledException ex) { 412 // expected 413 } 414 415 // We want to confirm that the query really was running and then got 416 // canceled midway. 417 final long waitTime = System.nanoTime() - startTime; 418 if (waitTime > 150 * 1000000L && waitTime < 600 * 1000000L) { 419 return; // success! 420 } 421 } finally { 422 Uninterruptibles.joinUninterruptibly(cancellationThread); 423 } 424 } 425 426 // Occasionally we might miss the timing deadline due to factors in the 427 // environment, but if after several trials we still couldn't demonstrate 428 // that the query was canceled, then the test must be broken. 429 fail("Could not prove that the query actually canceled midway during execution."); 430 } 431 432 @Test testUpdate()433 public void testUpdate() throws Exception { 434 final ContentValues values = new ContentValues(); 435 values.put("name", "Anonymous"); 436 values.put("salary", 0); 437 438 { 439 final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 440 qb.setTables("employee"); 441 qb.appendWhere("month=3"); 442 assertEquals(2, qb.update(mDatabase, values, null, null)); 443 } 444 { 445 final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 446 qb.setTables("employee"); 447 assertEquals(1, qb.update(mDatabase, values, "month=?", new String[] { "2" })); 448 } 449 } 450 451 @Test testDelete()452 public void testDelete() throws Exception { 453 { 454 final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 455 qb.setTables("employee"); 456 qb.appendWhere("month=3"); 457 assertEquals(2, qb.delete(mDatabase, null, null)); 458 assertEquals(0, qb.delete(mDatabase, null, null)); 459 } 460 { 461 final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 462 qb.setTables("employee"); 463 assertEquals(1, qb.delete(mDatabase, "month=?", new String[] { "2" })); 464 assertEquals(0, qb.delete(mDatabase, "month=?", new String[] { "2" })); 465 } 466 } 467 468 @Test testStrictQuery()469 public void testStrictQuery() throws Exception { 470 final SQLiteQueryBuilder qb = mStrictBuilder; 471 472 // Should normally only be able to see one row 473 try (Cursor c = qb.query(mDatabase, null, null, null, null, null, null, null, null)) { 474 assertEquals(1, c.getCount()); 475 } 476 477 // Trying sneaky queries should fail; even if they somehow succeed, we 478 // shouldn't get to see any other data. 479 try (Cursor c = qb.query(mDatabase, null, "1=1", null, null, null, null, null, null)) { 480 assertEquals(1, c.getCount()); 481 } catch (Exception tolerated) { 482 } 483 try (Cursor c = qb.query(mDatabase, null, "1=1 --", null, null, null, null, null, null)) { 484 assertEquals(1, c.getCount()); 485 } catch (Exception tolerated) { 486 } 487 try (Cursor c = qb.query(mDatabase, null, "1=1) OR (1=1", null, null, null, null, null, null)) { 488 assertEquals(1, c.getCount()); 489 } catch (Exception tolerated) { 490 } 491 try (Cursor c = qb.query(mDatabase, null, "1=1)) OR ((1=1", null, null, null, null, null, null)) { 492 assertEquals(1, c.getCount()); 493 } catch (Exception tolerated) { 494 } 495 } 496 497 @Test testStrictUpdate()498 public void testStrictUpdate() throws Exception { 499 final SQLiteQueryBuilder qb = mStrictBuilder; 500 501 final ContentValues values = new ContentValues(); 502 values.put("name", "Anonymous"); 503 504 // Should normally only be able to update one row 505 assertEquals(1, qb.update(mDatabase, values, null, null)); 506 507 // Trying sneaky queries should fail; even if they somehow succeed, we 508 // shouldn't get to see any other data. 509 try { 510 assertEquals(1, qb.update(mDatabase, values, "1=1", null)); 511 } catch (Exception tolerated) { 512 } 513 try { 514 assertEquals(1, qb.update(mDatabase, values, "1=1 --", null)); 515 } catch (Exception tolerated) { 516 } 517 try { 518 assertEquals(1, qb.update(mDatabase, values, "1=1) OR (1=1", null)); 519 } catch (Exception tolerated) { 520 } 521 try { 522 assertEquals(1, qb.update(mDatabase, values, "1=1)) OR ((1=1", null)); 523 } catch (Exception tolerated) { 524 } 525 } 526 527 @Test testStrictDelete()528 public void testStrictDelete() throws Exception { 529 final SQLiteQueryBuilder qb = mStrictBuilder; 530 531 // Should normally only be able to update one row 532 createEmployeeTable(); 533 assertEquals(1, qb.delete(mDatabase, null, null)); 534 535 // Trying sneaky queries should fail; even if they somehow succeed, we 536 // shouldn't get to see any other data. 537 try { 538 createEmployeeTable(); 539 assertEquals(1, qb.delete(mDatabase, "1=1", null)); 540 } catch (Exception tolerated) { 541 } 542 try { 543 createEmployeeTable(); 544 assertEquals(1, qb.delete(mDatabase, "1=1 --", null)); 545 } catch (Exception tolerated) { 546 } 547 try { 548 createEmployeeTable(); 549 assertEquals(1, qb.delete(mDatabase, "1=1) OR (1=1", null)); 550 } catch (Exception tolerated) { 551 } 552 try { 553 createEmployeeTable(); 554 assertEquals(1, qb.delete(mDatabase, "1=1)) OR ((1=1", null)); 555 } catch (Exception tolerated) { 556 } 557 } 558 559 private static final String[] COLUMNS_VALID = new String[] { 560 "_id", 561 }; 562 563 private static final String[] COLUMNS_INVALID = new String[] { 564 "salary", 565 "MAX(salary)", 566 "undefined", 567 "(secret_column IN secret_table)", 568 "(SELECT secret_column FROM secret_table)", 569 }; 570 571 @Test testStrictQueryProjection()572 public void testStrictQueryProjection() throws Exception { 573 for (String column : COLUMNS_VALID) { 574 assertStrictQueryValid( 575 new String[] { column }, null, null, null, null, null, null); 576 } 577 for (String column : COLUMNS_INVALID) { 578 assertStrictQueryInvalid( 579 new String[] { column }, null, null, null, null, null, null); 580 } 581 } 582 583 @Test testStrictQueryWhere()584 public void testStrictQueryWhere() throws Exception { 585 for (String column : COLUMNS_VALID) { 586 assertStrictQueryValid( 587 null, column + ">0", null, null, null, null, null); 588 assertStrictQueryValid( 589 null, "_id>" + column, null, null, null, null, null); 590 } 591 for (String column : COLUMNS_INVALID) { 592 assertStrictQueryInvalid( 593 null, column + ">0", null, null, null, null, null); 594 assertStrictQueryInvalid( 595 null, "_id>" + column, null, null, null, null, null); 596 } 597 } 598 599 @Test testStrictQueryGroupBy()600 public void testStrictQueryGroupBy() { 601 for (String column : COLUMNS_VALID) { 602 assertStrictQueryValid( 603 null, null, null, column, null, null, null); 604 assertStrictQueryValid( 605 null, null, null, "_id," + column, null, null, null); 606 } 607 for (String column : COLUMNS_INVALID) { 608 assertStrictQueryInvalid( 609 null, null, null, column, null, null, null); 610 assertStrictQueryInvalid( 611 null, null, null, "_id," + column, null, null, null); 612 } 613 } 614 615 @Test testStrictQueryHaving()616 public void testStrictQueryHaving() { 617 for (String column : COLUMNS_VALID) { 618 assertStrictQueryValid( 619 null, null, null, "_id", column, null, null); 620 } 621 for (String column : COLUMNS_INVALID) { 622 assertStrictQueryInvalid( 623 null, null, null, "_id", column, null, null); 624 } 625 } 626 627 @Test testStrictQueryOrderBy()628 public void testStrictQueryOrderBy() { 629 for (String column : COLUMNS_VALID) { 630 assertStrictQueryValid( 631 null, null, null, null, null, column, null); 632 assertStrictQueryValid( 633 null, null, null, null, null, column + " ASC", null); 634 assertStrictQueryValid( 635 null, null, null, null, null, "_id COLLATE NOCASE ASC," + column, null); 636 } 637 for (String column : COLUMNS_INVALID) { 638 assertStrictQueryInvalid( 639 null, null, null, null, null, column, null); 640 assertStrictQueryInvalid( 641 null, null, null, null, null, column + " ASC", null); 642 assertStrictQueryInvalid( 643 null, null, null, null, null, "_id COLLATE NOCASE ASC," + column, null); 644 } 645 } 646 647 @Test testStrictQueryLimit()648 public void testStrictQueryLimit() { 649 assertStrictQueryValid( 650 null, null, null, null, null, null, "32"); 651 assertStrictQueryValid( 652 null, null, null, null, null, null, "0,32"); 653 assertStrictQueryValid( 654 null, null, null, null, null, null, "32 OFFSET 0"); 655 656 for (String column : COLUMNS_VALID) { 657 assertStrictQueryInvalid( 658 null, null, null, null, null, null, column); 659 } 660 for (String column : COLUMNS_INVALID) { 661 assertStrictQueryInvalid( 662 null, null, null, null, null, null, column); 663 } 664 } 665 666 @Test testStrictInsertValues()667 public void testStrictInsertValues() throws Exception { 668 final ContentValues values = new ContentValues(); 669 for (String column : COLUMNS_VALID) { 670 values.clear(); 671 values.put(column, 42); 672 assertStrictInsertValid(values); 673 } 674 for (String column : COLUMNS_INVALID) { 675 values.clear(); 676 values.put(column, 42); 677 assertStrictInsertInvalid(values); 678 } 679 } 680 681 @Test testStrictUpdateValues()682 public void testStrictUpdateValues() throws Exception { 683 final ContentValues values = new ContentValues(); 684 for (String column : COLUMNS_VALID) { 685 values.clear(); 686 values.put(column, 42); 687 assertStrictUpdateValid(values, null, null); 688 } 689 for (String column : COLUMNS_INVALID) { 690 values.clear(); 691 values.put(column, 42); 692 assertStrictUpdateInvalid(values, null, null); 693 } 694 } 695 assertEnforceStrictGrammarRelaxedByTargetSdk(String selection)696 private static void assertEnforceStrictGrammarRelaxedByTargetSdk(String selection) { 697 final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 698 final HashMap<String, String> map = new HashMap<>(); 699 map.put("_data", "_data"); 700 map.put("date_added", "date_added"); 701 map.put("date_modified", "date_modified"); 702 map.put("media_type", "media_type"); 703 builder.setProjectionMap(map); 704 { 705 builder.setTargetSdkVersion(Build.VERSION_CODES.Q); 706 builder.enforceStrictGrammar(selection, null, null, null, null); 707 } 708 try { 709 builder.setTargetSdkVersion(Build.VERSION_CODES.R); 710 builder.enforceStrictGrammar(selection, null, null, null, null); 711 fail("Expected to throw"); 712 } catch (IllegalArgumentException expected) { 713 } 714 } 715 716 @Test testStrict_154193772()717 public void testStrict_154193772() { 718 final String selection = "(LOWER(_data) LIKE \"%.wmv\" OR LOWER(_data) LIKE \"%.wm\" OR LOWER(_data) LIKE \"%.wtv\" OR LOWER(_data) LIKE \"%.asf\" OR LOWER(_data) LIKE \"%.hls\" OR LOWER(_data) LIKE \"%.mp4\" OR LOWER(_data) LIKE \"%.m4v\" OR LOWER(_data) LIKE \"%.mov\" OR LOWER(_data) LIKE \"%.mp4v\" OR LOWER(_data) LIKE \"%.3g2\" OR LOWER(_data) LIKE \"%.3gp\" OR LOWER(_data) LIKE \"%.3gp2\" OR LOWER(_data) LIKE \"%.3gpp\" OR LOWER(_data) LIKE \"%.mj2\" OR LOWER(_data) LIKE \"%.qt\" OR LOWER(_data) LIKE \"%.external\" OR LOWER(_data) LIKE \"%.mov\" OR LOWER(_data) LIKE \"%.asf\" OR LOWER(_data) LIKE \"%.avi\" OR LOWER(_data) LIKE \"%.divx\" OR LOWER(_data) LIKE \"%.mpg\" OR LOWER(_data) LIKE \"%.mpeg\" OR LOWER(_data) LIKE \"%.mkv\" OR LOWER(_data) LIKE \"%.webm\" OR LOWER(_data) LIKE \"%.mk3d\" OR LOWER(_data) LIKE \"%.mks\" OR LOWER(_data) LIKE \"%.3gp\" OR LOWER(_data) LIKE \"%.mpegts\" OR LOWER(_data) LIKE \"%.ts\" OR LOWER(_data) LIKE \"%.m2ts\" OR LOWER(_data) LIKE \"%.m2t\") AND (date_added >= ? OR date_modified >=?)"; 719 assertEnforceStrictGrammarRelaxedByTargetSdk(selection); 720 } 721 722 @Test testStrict_156554363()723 public void testStrict_156554363() { 724 final String selection = "date_added>? AND media_type=0 AND (_data LIKE \"%.mov\" OR _data LIKE \"%.MOV\""; 725 assertEnforceStrictGrammarRelaxedByTargetSdk(selection); 726 } 727 728 @Test testStrict_156832140()729 public void testStrict_156832140() { 730 final String selection = "_data LIKE \"%com.gopro.smarty%\""; 731 assertEnforceStrictGrammarRelaxedByTargetSdk(selection); 732 } 733 734 @Test testStrict_156136746()735 public void testStrict_156136746() { 736 // Verify that both keywords column names are allowed to be 737 // case-insensitive, per the SQLite specification 738 assertStrictQueryValid(new String[] { "Name", "Max(Month)" }, 739 "IfNull(Month,-1) Between 1100 And 1900", null, 740 "Month", "Month In (1,2)", null, null); 741 } 742 743 @Test testStrict_158537159()744 public void testStrict_158537159() { 745 final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 746 final HashMap<String, String> map = new HashMap<>(); 747 map.put("bucket_id", "bucket_id"); 748 builder.setProjectionMap(map); 749 final String sortOrder = "bucket_id COLLATE LOCALIZED ASC"; 750 { 751 builder.setTargetSdkVersion(Build.VERSION_CODES.Q); 752 builder.enforceStrictGrammar(null, null, null, sortOrder, null); 753 } 754 try { 755 builder.setTargetSdkVersion(Build.VERSION_CODES.R); 756 builder.enforceStrictGrammar(null, null, null, sortOrder, null); 757 fail("Expected to throw"); 758 } catch (IllegalArgumentException expected) { 759 } 760 } 761 762 @Test testStrictCustomCollator()763 public void testStrictCustomCollator() { 764 final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 765 final HashMap<String, String> map = new HashMap<>(); 766 map.put("bucket_id", "bucket_id"); 767 builder.setProjectionMap(map); 768 769 final String sortOrder = "bucket_id COLLATE custom_zh ASC"; 770 builder.enforceStrictGrammar(null, null, null, sortOrder, null); 771 } 772 assertStrictInsertValid(ContentValues values)773 private void assertStrictInsertValid(ContentValues values) { 774 mStrictBuilder.insert(mDatabase, values); 775 } 776 assertStrictInsertInvalid(ContentValues values)777 private void assertStrictInsertInvalid(ContentValues values) { 778 try { 779 mStrictBuilder.insert(mDatabase, values); 780 fail(Arrays.asList(values).toString()); 781 } catch (Exception expected) { 782 } 783 } 784 assertStrictUpdateValid(ContentValues values, String selection, String[] selectionArgs)785 private void assertStrictUpdateValid(ContentValues values, String selection, 786 String[] selectionArgs) { 787 mStrictBuilder.update(mDatabase, values, selection, selectionArgs); 788 } 789 assertStrictUpdateInvalid(ContentValues values, String selection, String[] selectionArgs)790 private void assertStrictUpdateInvalid(ContentValues values, String selection, 791 String[] selectionArgs) { 792 try { 793 mStrictBuilder.update(mDatabase, values, selection, selectionArgs); 794 fail(Arrays.asList(values, selection, selectionArgs).toString()); 795 } catch (Exception expected) { 796 } 797 } 798 assertStrictQueryValid(String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)799 private void assertStrictQueryValid(String[] projectionIn, String selection, 800 String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) { 801 try (Cursor c = mStrictBuilder.query(mDatabase, projectionIn, selection, selectionArgs, 802 groupBy, having, sortOrder, limit, null)) { 803 } 804 } 805 assertStrictQueryInvalid(String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)806 private void assertStrictQueryInvalid(String[] projectionIn, String selection, 807 String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) { 808 try (Cursor c = mStrictBuilder.query(mDatabase, projectionIn, selection, selectionArgs, 809 groupBy, having, sortOrder, limit, null)) { 810 fail(Arrays.asList(projectionIn, selection, selectionArgs, 811 groupBy, having, sortOrder, limit).toString()); 812 } catch (Exception expected) { 813 } 814 } 815 createEmployeeTable()816 private void createEmployeeTable() { 817 mDatabase.execSQL("DROP TABLE IF EXISTS employee;"); 818 mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " + 819 "name TEXT, month INTEGER, salary INTEGER);"); 820 mDatabase.execSQL("INSERT INTO employee (name, month, salary) " + 821 "VALUES ('Mike', '1', '1000');"); 822 mDatabase.execSQL("INSERT INTO employee (name, month, salary) " + 823 "VALUES ('Mike', '2', '3000');"); 824 mDatabase.execSQL("INSERT INTO employee (name, month, salary) " + 825 "VALUES ('jack', '1', '2000');"); 826 mDatabase.execSQL("INSERT INTO employee (name, month, salary) " + 827 "VALUES ('jack', '3', '1500');"); 828 mDatabase.execSQL("INSERT INTO employee (name, month, salary) " + 829 "VALUES ('Jim', '1', '1000');"); 830 mDatabase.execSQL("INSERT INTO employee (name, month, salary) " + 831 "VALUES ('Jim', '3', '3500');"); 832 } 833 createStrictQueryBuilder()834 private void createStrictQueryBuilder() { 835 mStrictBuilder = new SQLiteQueryBuilder(); 836 mStrictBuilder.setTables("employee"); 837 mStrictBuilder.setStrict(true); 838 mStrictBuilder.setStrictColumns(true); 839 mStrictBuilder.setStrictGrammar(true); 840 mStrictBuilder.appendWhere("month=2"); 841 842 final Map<String, String> projectionMap = new HashMap<>(); 843 projectionMap.put("_id", "_id"); 844 projectionMap.put("name", "name"); 845 projectionMap.put("month", "month"); 846 mStrictBuilder.setProjectionMap(projectionMap); 847 } 848 } 849