Add ability to execute per-connection SQL.

Developers have been able to register custom collators using syntax
like "SELECT icu_load_collation()", but collators are registered per
database connection.

Since we don't expose any details APIs for interacting with connection
pools directly, developers can end up with flaky behavior as their
queries rotate through the pool of connections, as only a subset of
connections will have their collation registered.

This solve this, we add a new execPerConnectionSQL() method to
ensure that a given statement is executed on all current and future
database connections.

Bug: 152005629
Test: atest CtsDatabaseTestCases:android.database.sqlite.cts.SQLiteDatabaseTest
Change-Id: I52523016fa5a03692a889786774bbea6c5f91d8f
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
index 2ee5de0..32609e9 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -34,6 +34,8 @@
 import android.database.sqlite.SQLiteQuery;
 import android.database.sqlite.SQLiteStatement;
 import android.database.sqlite.SQLiteTransactionListener;
+import android.icu.text.Collator;
+import android.icu.util.ULocale;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -42,11 +44,13 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.Semaphore;
 import java.util.function.BinaryOperator;
 import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
 
 public class SQLiteDatabaseTest extends AndroidTestCase {
 
@@ -298,6 +302,14 @@
         }
     }
 
+    private static List<String> collect(Cursor c) {
+        List<String> res = new ArrayList<>();
+        while (c.moveToNext()) {
+            res.add(c.getString(0));
+        }
+        return res;
+    }
+
     public void testAccessMaximumSize() {
         long curMaximumSize = mDatabase.getMaximumSize();
 
@@ -519,6 +531,35 @@
         cursor.close();;
     }
 
+    public void testExecPerConnectionSQL() {
+        final List<String> data = Arrays.asList(
+                "ABC", "abc", "pinyin", "가나다", "바사", "테스트", "马",
+                "嘛", "妈", "骂", "吗", "码", "玛", "麻", "中", "梵", "苹果", "久了", "伺候");
+        final String values = data.stream().map((d) -> "('" + d + "')")
+                .collect(Collectors.joining(","));
+
+        mDatabase.execSQL("CREATE TABLE employee (name TEXT);");
+        mDatabase.execSQL("INSERT INTO employee (name) VALUES " + values + ";");
+
+        for (ULocale locale : new ULocale[] {
+                new ULocale("zh"),
+                new ULocale("zh@collation=pinyin"),
+                new ULocale("zh@collation=stroke"),
+                new ULocale("zh@collation=zhuyin"),
+        }) {
+            final String collationName = "cts_" + System.nanoTime();
+            mDatabase.execPerConnectionSQL("SELECT icu_load_collation(?, ?);",
+                    new Object[] { locale.getName(), collationName });
+
+            // Assert that sorting is identical between SQLite and ICU4J
+            try (Cursor c = mDatabase.query(true, "employee", new String[] { "name" },
+                    null, null, null, null, "name COLLATE " + collationName + " ASC", null)) {
+                data.sort(Collator.getInstance(locale));
+                assertEquals(data, collect(c));
+            }
+        }
+    }
+
     public void testFindEditTable() {
         String tables = "table1 table2 table3";
         assertEquals("table1", SQLiteDatabase.findEditTable(tables));