Don't rely on indices in query results

Room used to depend on index of columns in the query result which
may not be reliable especially with migrations. Instead, we
read the column indices using the cursorReady callback. This will
make migration codes easier since they don't need to recreate the
table for added columns.

Bug: 32342709
Test: existing tests pass
Change-Id: Ic93733d847a8de736cd341f312c2f51d090a8359
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
index a2649d7..254fc49 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
@@ -17,6 +17,7 @@
 package com.android.support.room.solver.query.result
 
 import com.android.support.room.ext.L
+import com.android.support.room.ext.S
 import com.android.support.room.ext.T
 import com.android.support.room.ext.typeName
 import com.android.support.room.processor.Context
@@ -29,6 +30,8 @@
 import com.android.support.room.vo.RelationCollector
 import com.android.support.room.vo.Warning
 import com.android.support.room.writer.FieldReadWriteWriter
+import com.squareup.javapoet.TypeName
+import stripNonJava
 import javax.lang.model.type.TypeMirror
 
 /**
@@ -45,7 +48,7 @@
         // toMutableList documentation is not clear if it copies so lets be safe.
         val remainingFields = pojo.fields.mapTo(mutableListOf<Field>(), { it })
         val unusedColumns = arrayListOf<String>()
-        val associations = info.columns.mapIndexed { index, column ->
+        val matchedFields = info.columns.map { column ->
             // first check remaining, otherwise check any. maybe developer wants to map the same
             // column into 2 fields. (if they want to post process etc)
             val field = remainingFields.firstOrNull { it.columnName == column.name } ?:
@@ -55,7 +58,7 @@
                 null
             } else {
                 remainingFields.remove(field)
-                FieldWithIndex(field, index.toString())
+                field
             }
         }.filterNotNull()
         if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) {
@@ -68,14 +71,14 @@
             )
             context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg)
         }
-        if (associations.isEmpty()) {
+        if (matchedFields.isEmpty()) {
             context.logger.e(ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
         }
 
         relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
 
         mapping = Mapping(
-                associations = associations,
+                matchedFields = matchedFields,
                 unusedColumns = unusedColumns,
                 unusedFields = remainingFields
         )
@@ -94,18 +97,24 @@
 
     override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
         relationCollectors.forEach { it.writeInitCode(scope) }
+        mapping.fieldsWithIndices = mapping.matchedFields.map {
+            val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+            scope.builder().addStatement("final $T $L = $L.getColumnIndexOrThrow($S)",
+                    TypeName.INT, indexVar, cursorVarName, it.columnName)
+            FieldWithIndex(it, indexVar)
+        }
     }
 
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
             addStatement("$L = new $T()", outVarName, out.typeName())
             FieldReadWriteWriter.readFromCursor(outVarName, cursorVarName,
-                    mapping.associations, scope)
+                    mapping.fieldsWithIndices, scope)
             relationCollectors.forEach {
                 it.writeReadParentKeyCode(
                         cursorVarName = cursorVarName,
                         itemVar = outVarName,
-                        fieldsWithIndices = mapping.associations,
+                        fieldsWithIndices = mapping.fieldsWithIndices,
                         scope = scope)
             }
         }
@@ -124,7 +133,10 @@
                 }
             }
 
-    data class Mapping(val associations: List<FieldWithIndex>,
+    data class Mapping(val matchedFields: List<Field>,
                        val unusedColumns: List<String>,
-                       val unusedFields: List<Field>)
+                       val unusedFields: List<Field>) {
+        // set when cursor is ready.
+        lateinit var fieldsWithIndices: List<FieldWithIndex>
+    }
 }
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index ee080c8..1b955cd 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -29,12 +29,14 @@
         _statement.bindLong(_argIndex, id);
         final Cursor _cursor = __db.query(_statement);
         try {
+            final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName");
+            final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
             final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
             while(_cursor.moveToNext()) {
                 final ComplexDao.FullName _item;
                 _item = new ComplexDao.FullName();
-                _item.fullName = _cursor.getString(0);
-                _item.id = _cursor.getInt(1);
+                _item.fullName = _cursor.getString(_cursorIndexOfFullName);
+                _item.id = _cursor.getInt(_cursorIndexOfId);
                 _result.add(_item);
             }
             return _result;
@@ -340,4 +342,4 @@
         }
         return _entity;
     }
-}
+}
\ No newline at end of file