Merge "Add convenience APIs for Float, Int and Boolean in SQLiteStatement" into androidx-main
diff --git a/room/room-common/api/restricted_current.txt b/room/room-common/api/restricted_current.txt
index a618b5c..d6c9b38 100644
--- a/room/room-common/api/restricted_current.txt
+++ b/room/room-common/api/restricted_current.txt
@@ -1,6 +1,12 @@
 // Signature format: 4.0
 package androidx.room {
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class AmbiguousColumnResolver {
+    method public static int[][] resolve(String[] resultColumns, String[][] mappings);
+    method public static int[][] resolve(java.util.List<java.lang.String> resultColumns, String[][] mappings);
+    field public static final androidx.room.AmbiguousColumnResolver INSTANCE;
+  }
+
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.CLASS) public @interface AutoMigration {
     method public abstract int from();
     method public abstract Class<?> spec() default java.lang.Object;
diff --git a/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt b/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
index c810bde..ba3536d 100644
--- a/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
+++ b/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
@@ -47,7 +47,7 @@
  * are continuous.
  *
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 public object AmbiguousColumnResolver {
 
     /**
@@ -61,6 +61,23 @@
      */
     @JvmStatic
     public fun resolve(
+        resultColumns: List<String>,
+        mappings: Array<Array<String>>
+    ): Array<IntArray> {
+        return resolve(resultColumns.toTypedArray(), mappings)
+    }
+
+    /**
+     * Maps query result column indices to result object columns.
+     *
+     * @param resultColumns The ordered result column names.
+     * @param mappings      An array containing the list of result object column names that must be
+     *                      mapped to indices of `resultColumns`.
+     * @return An array with the same dimensions as `mappings` whose values correspond to the
+     * index in `resultColumns` that match the object column at `mappings[i][j]`.
+     */
+    @JvmStatic
+    public fun resolve(
         resultColumns: Array<String>,
         mappings: Array<Array<String>>
     ): Array<IntArray> {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt
index 3090f83..31c6b80 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt
@@ -18,7 +18,6 @@
 
 import androidx.room.AmbiguousColumnResolver
 import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.DoubleArrayLiteral
@@ -84,24 +83,13 @@
                     typeName = XTypeName.getArrayName(
                         XTypeName.getArrayName(XTypeName.PRIMITIVE_INT)
                     ),
-                    assignExpr = if (scope.useDriverApi) {
-                        XCodeBlock.of(
-                            language,
-                            "%T.resolve(%M(%L), %L)",
-                            RoomTypeNames.AMBIGUOUS_COLUMN_RESOLVER,
-                            RoomTypeNames.STATEMENT_UTIL.packageMember("getColumnNames"),
-                            cursorVarName,
-                            rowMappings
-                        )
-                    } else {
-                        XCodeBlock.of(
-                            language,
-                            "%T.resolve(%L.getColumnNames(), %L)",
-                            RoomTypeNames.AMBIGUOUS_COLUMN_RESOLVER,
-                            cursorVarName,
-                            rowMappings
-                        )
-                    }
+                    assignExpr = XCodeBlock.of(
+                        language,
+                        "%T.resolve(%L.getColumnNames(), %L)",
+                        RoomTypeNames.AMBIGUOUS_COLUMN_RESOLVER,
+                        cursorVarName,
+                        rowMappings
+                    )
                 )
             }
         }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
index 449a95f..379cfe0 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
@@ -4,7 +4,6 @@
 import androidx.room.RoomSQLiteQuery
 import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.getColumnIndex
-import androidx.room.util.getColumnNames
 import androidx.room.util.performBlocking
 import androidx.room.util.query
 import androidx.room.util.wrapMappedColumns
@@ -38,8 +37,9 @@
     return performBlocking(__db, true, false) { _connection ->
       val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _cursorIndices: Array<IntArray> = AmbiguousColumnResolver.resolve(getColumnNames(_stmt),
-            arrayOf(arrayOf("id", "name"), arrayOf("id", "userId", "text")))
+        val _cursorIndices: Array<IntArray> =
+            AmbiguousColumnResolver.resolve(_stmt.getColumnNames(), arrayOf(arrayOf("id", "name"),
+            arrayOf("id", "userId", "text")))
         val _result: MutableMap<User, MutableList<Comment>> =
             LinkedHashMap<User, MutableList<Comment>>()
         while (_stmt.step()) {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt
index 375ce65..571867b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt
@@ -1,6 +1,5 @@
 import androidx.room.AmbiguousColumnResolver
 import androidx.room.RoomDatabase
-import androidx.room.util.getColumnNames
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
 import java.nio.ByteBuffer
@@ -34,9 +33,9 @@
     return performBlocking(__db, true, false) { _connection ->
       val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _cursorIndices: Array<IntArray> = AmbiguousColumnResolver.resolve(getColumnNames(_stmt),
-            arrayOf(arrayOf("id", "name"), arrayOf("userId", "url", "data"), arrayOf("id", "userId",
-            "text")))
+        val _cursorIndices: Array<IntArray> =
+            AmbiguousColumnResolver.resolve(_stmt.getColumnNames(), arrayOf(arrayOf("id", "name"),
+            arrayOf("userId", "url", "data"), arrayOf("id", "userId", "text")))
         val _result: MutableMap<User, MutableMap<Avatar, MutableList<Comment>>> =
             LinkedHashMap<User, MutableMap<Avatar, MutableList<Comment>>>()
         while (_stmt.step()) {
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 6fb1bbd..4ca2f68 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -500,7 +500,6 @@
 
   public final class SQLiteStatementUtil {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int getColumnIndexOrThrow(androidx.sqlite.SQLiteStatement stmt, String name);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static String[] getColumnNames(androidx.sqlite.SQLiteStatement statement);
   }
 
   @RestrictTo({androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class StringUtil {
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
index 8145bcf..cebd289 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
@@ -53,13 +53,3 @@
     }
     return -1
 }
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-fun getColumnNames(statement: SQLiteStatement): Array<String> {
-    val columnCount = statement.getColumnCount()
-    val columnNames = mutableListOf<String>()
-    for (index in 0 until columnCount) {
-        columnNames.add(index, statement.getColumnName(index))
-    }
-    return columnNames.toTypedArray()
-}
diff --git a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
index a4621c1..92b183a 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
@@ -55,28 +55,65 @@
     @Test
     fun bindAndReadColumns() = testWithConnection { connection ->
         connection.execSQL(
-            "CREATE TABLE Test(integerCol INTEGER, realCol REAL, textCol TEXT, blobCol BLOB)"
+            """
+            CREATE TABLE Test(
+                integerCol_long INTEGER,
+                integerCol_int INTEGER,
+                integerCol_boolean INTEGER,
+                realCol_double REAL,
+                realCol_float REAL,
+                textCol TEXT,
+                blobCol BLOB
+            )
+            """.trimIndent()
         )
-        connection.prepare(
-            "INSERT INTO Test (integerCol, realCol, textCol, blobCol) VALUES (?, ?, ?, ?)"
+        connection.prepare("""
+            INSERT INTO Test (
+                integerCol_long,
+                integerCol_int,
+                integerCol_boolean,
+                realCol_double,
+                realCol_float,
+                textCol,
+                blobCol
+            ) VALUES (?, ?, ?, ?, ?, ?, ?)
+        """.trimIndent()
         ).use {
             it.bindLong(1, 3)
-            it.bindDouble(2, 7.87)
-            it.bindText(3, "PR")
-            it.bindBlob(4, byteArrayOf(0x0F, 0x12, 0x1B))
+            it.bindInt(2, 22)
+            it.bindBoolean(3, true)
+            it.bindDouble(4, 7.87)
+            it.bindFloat(5, 9.39f)
+            it.bindText(6, "PR")
+            it.bindBlob(7, byteArrayOf(0x0F, 0x12, 0x1B))
             assertThat(it.step()).isFalse() // SQLITE_DONE
         }
         connection.prepare("SELECT * FROM Test").use {
             assertThat(it.step()).isTrue() // SQLITE_ROW
-            assertThat(it.getColumnCount()).isEqualTo(4)
-            assertThat(it.getColumnName(0)).isEqualTo("integerCol")
-            assertThat(it.getColumnName(1)).isEqualTo("realCol")
-            assertThat(it.getColumnName(2)).isEqualTo("textCol")
-            assertThat(it.getColumnName(3)).isEqualTo("blobCol")
+            assertThat(it.getColumnCount()).isEqualTo(7)
+            assertThat(it.getColumnName(0)).isEqualTo("integerCol_long")
+            assertThat(it.getColumnName(1)).isEqualTo("integerCol_int")
+            assertThat(it.getColumnName(2)).isEqualTo("integerCol_boolean")
+            assertThat(it.getColumnName(3)).isEqualTo("realCol_double")
+            assertThat(it.getColumnName(4)).isEqualTo("realCol_float")
+            assertThat(it.getColumnName(5)).isEqualTo("textCol")
+            assertThat(it.getColumnName(6)).isEqualTo("blobCol")
+            assertThat(it.getColumnNames()).containsExactly(
+                "integerCol_long",
+                "integerCol_int",
+                "integerCol_boolean",
+                "realCol_double",
+                "realCol_float",
+                "textCol",
+                "blobCol"
+            ).inOrder()
             assertThat(it.getLong(0)).isEqualTo(3)
-            assertThat(it.getDouble(1)).isEqualTo(7.87)
-            assertThat(it.getText(2)).isEqualTo("PR")
-            assertThat(it.getBlob(3)).isEqualTo(byteArrayOf(0x0F, 0x12, 0x1B))
+            assertThat(it.getInt(1)).isEqualTo(22)
+            assertThat(it.getBoolean(2)).isTrue()
+            assertThat(it.getDouble(3)).isEqualTo(7.87)
+            assertThat(it.getFloat(4)).isEqualTo(9.39f)
+            assertThat(it.getText(5)).isEqualTo("PR")
+            assertThat(it.getBlob(6)).isEqualTo(byteArrayOf(0x0F, 0x12, 0x1B))
             assertThat(it.step()).isFalse() // SQLITE_DONE
         }
     }
diff --git a/sqlite/sqlite/api/current.txt b/sqlite/sqlite/api/current.txt
index 7c8bfc7..9e31171 100644
--- a/sqlite/sqlite/api/current.txt
+++ b/sqlite/sqlite/api/current.txt
@@ -18,16 +18,23 @@
 
   public interface SQLiteStatement {
     method public void bindBlob(int index, byte[] value);
+    method public default void bindBoolean(int index, boolean value);
     method public void bindDouble(int index, double value);
+    method public default void bindFloat(int index, float value);
+    method public default void bindInt(int index, int value);
     method public void bindLong(int index, long value);
     method public void bindNull(int index);
     method public void bindText(int index, String value);
     method public void clearBindings();
     method public void close();
     method public byte[] getBlob(int index);
+    method public default boolean getBoolean(int index);
     method public int getColumnCount();
     method public String getColumnName(int index);
+    method public default java.util.List<java.lang.String> getColumnNames();
     method public double getDouble(int index);
+    method public default float getFloat(int index);
+    method public default int getInt(int index);
     method public long getLong(int index);
     method public String getText(int index);
     method public boolean isNull(int index);
diff --git a/sqlite/sqlite/api/restricted_current.txt b/sqlite/sqlite/api/restricted_current.txt
index 7c8bfc7..9e31171 100644
--- a/sqlite/sqlite/api/restricted_current.txt
+++ b/sqlite/sqlite/api/restricted_current.txt
@@ -18,16 +18,23 @@
 
   public interface SQLiteStatement {
     method public void bindBlob(int index, byte[] value);
+    method public default void bindBoolean(int index, boolean value);
     method public void bindDouble(int index, double value);
+    method public default void bindFloat(int index, float value);
+    method public default void bindInt(int index, int value);
     method public void bindLong(int index, long value);
     method public void bindNull(int index);
     method public void bindText(int index, String value);
     method public void clearBindings();
     method public void close();
     method public byte[] getBlob(int index);
+    method public default boolean getBoolean(int index);
     method public int getColumnCount();
     method public String getColumnName(int index);
+    method public default java.util.List<java.lang.String> getColumnNames();
     method public double getDouble(int index);
+    method public default float getFloat(int index);
+    method public default int getInt(int index);
     method public long getLong(int index);
     method public String getText(int index);
     method public boolean isNull(int index);
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
index e0f62d6..e0b16bd 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
@@ -43,6 +43,16 @@
     fun bindDouble(index: Int, value: Double)
 
     /**
+     * Binds a Float value to this statement at an index.
+     *
+     * @param index the 1-based index of the parameter to bind
+     * @param value the value to bind
+     */
+    fun bindFloat(index: Int, value: Float) {
+        bindDouble(index, value.toDouble())
+    }
+
+    /**
      * Binds a Long value to this statement at an index.
      *
      * @param index the 1-based index of the parameter to bind
@@ -51,6 +61,26 @@
     fun bindLong(index: Int, value: Long)
 
     /**
+     * Binds a Int value to this statement at an index.
+     *
+     * @param index the 1-based index of the parameter to bind
+     * @param value the value to bind
+     */
+    fun bindInt(index: Int, value: Int) {
+        bindLong(index, value.toLong())
+    }
+
+    /**
+     * Binds a Boolean value to this statement at an index.
+     *
+     * @param index the 1-based index of the parameter to bind
+     * @param value the value to bind
+     */
+    fun bindBoolean(index: Int, value: Boolean) {
+        bindLong(index, if (value) 1L else 0L)
+    }
+
+    /**
      * Binds a String value to this statement at an index.
      *
      * @param index the 1-based index of the parameter to bind
@@ -82,6 +112,16 @@
     fun getDouble(index: Int): Double
 
     /**
+     * Returns the value of the column at [index] as a Float.
+     *
+     * @param index the 0-based index of the column
+     * @return the value of the column
+     */
+    fun getFloat(index: Int): Float {
+        return getDouble(index).toFloat()
+    }
+
+    /**
      * Returns the value of the column at [index] as a Long.
      *
      * @param index the 0-based index of the column
@@ -90,6 +130,26 @@
     fun getLong(index: Int): Long
 
     /**
+     * Returns the value of the column at [index] as a Int.
+     *
+     * @param index the 0-based index of the column
+     * @return the value of the column
+     */
+    fun getInt(index: Int): Int {
+        return getLong(index).toInt()
+    }
+
+    /**
+     * Returns the value of the column at [index] as a Boolean.
+     *
+     * @param index the 0-based index of the column
+     * @return the value of the column
+     */
+    fun getBoolean(index: Int): Boolean {
+        return getLong(index) != 0L
+    }
+
+    /**
      * Returns the value of the column at [index] as a String.
      *
      * @param index the 0-based index of the column
@@ -106,7 +166,7 @@
     fun isNull(index: Int): Boolean
 
     /**
-     * Returns the numbers of columns in the result of the statement.
+     * Returns the number of columns in the result of the statement.
      *
      * @return the number of columns
      */
@@ -121,6 +181,15 @@
     fun getColumnName(index: Int): String
 
     /**
+     * Returns the name of the columns in the result of the statement ordered by their index.
+     *
+     * @return the names of the columns
+     */
+    fun getColumnNames(): List<String> {
+       return List(getColumnCount()) { i -> getColumnName(i) }
+    }
+
+    /**
      * Executes the statement and evaluates the next result row if available.
      *
      * A statement is initially prepared and compiled but is not executed until one or more calls