Redefine QueryResultAdapter to contain multiple RowAdapters

With the work on multimap return types a single row can represent multiple POJOs and thus requires multiple row adapters. This changes make it so that result adapters are compose of multiple row adapters which align better with the multimap return types.

This does mean cursor mismatch validation can no longer be done in the row adapter as a single adapter is unaware if other unused columns will actually be used or not, therefore cursor mismatch validation is moved to the query processor once the results adapter along with its row adapter have been created.

Bug: 191693863
Test: room-compiler:test
Change-Id: I320ee991fb7f567ca72eb5438cac9a92bf515583
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
index 604ec0e..488c1f8 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
@@ -21,7 +21,6 @@
 import androidx.room.Insert;
 import androidx.room.Query;
 import androidx.room.RawQuery;
-import androidx.room.RoomWarnings;
 import androidx.room.Transaction;
 import androidx.room.integration.testapp.vo.Album;
 import androidx.room.integration.testapp.vo.AlbumNameAndBandName;
@@ -42,8 +41,6 @@
 import io.reactivex.Flowable;
 
 @Dao
-@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
-// TODO: (b/191693863) Cannot use @RewriteQueriesToDropUnusedColumns due to this bug.
 public interface MusicDao {
 
     @Insert
@@ -80,6 +77,7 @@
     @Query("SELECT * FROM Artist JOIN Song ON Artist.mArtistName = Song.mArtist")
     Map<Artist, List<Song>> getAllArtistAndTheirSongs();
 
+    @Transaction
     @Query("SELECT * FROM Artist JOIN Album ON Artist.mArtistName = Album.mAlbumArtist")
     Map<Artist, List<AlbumWithSongs>> getAllArtistAndTheirAlbumsWithSongs();
 
@@ -93,9 +91,7 @@
     Flowable<Map<Artist, List<Song>>> getAllArtistAndTheirSongsAsFlowable();
 
     @Query("SELECT Album.mAlbumReleaseYear as mReleaseYear, Album.mAlbumName, Album.mAlbumArtist "
-            + "as mBandName"
-            + " from Album "
-            + "JOIN Song "
-            + "ON Album.mAlbumArtist = Song.mArtist AND Album.mAlbumName = Song.mAlbum")
+            + "as mBandName from Album JOIN Song ON Album.mAlbumArtist = Song.mArtist AND "
+            + "Album.mAlbumName = Song.mAlbum")
     Map<ReleasedAlbum, List<AlbumNameAndBandName>> getReleaseYearToAlbumsAndBands();
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/parser/expansion/ProjectionExpander.kt b/room/room-compiler/src/main/kotlin/androidx/room/parser/expansion/ProjectionExpander.kt
index 94f8ff3..32a7d76 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/parser/expansion/ProjectionExpander.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/parser/expansion/ProjectionExpander.kt
@@ -21,7 +21,7 @@
 import androidx.room.parser.SqlParser
 import androidx.room.processor.QueryRewriter
 import androidx.room.solver.query.result.PojoRowAdapter
-import androidx.room.solver.query.result.RowAdapter
+import androidx.room.solver.query.result.QueryResultAdapter
 import androidx.room.verifier.QueryResultInfo
 import androidx.room.vo.EmbeddedField
 import androidx.room.vo.Entity
@@ -68,21 +68,31 @@
 
     override fun rewrite(
         query: ParsedQuery,
-        rowAdapter: RowAdapter
-    ) = if (rowAdapter is PojoRowAdapter) {
-        interpret(
-            query = ExpandableSqlParser.parse(query.original),
-            pojo = rowAdapter.pojo
-        ).let {
-            val reParsed = SqlParser.parse(it)
-            if (reParsed.errors.isEmpty()) {
-                reParsed
-            } else {
-                query // return original, expansion somewhat failed
-            }
+        resultAdapter: QueryResultAdapter
+    ): ParsedQuery {
+        if (resultAdapter.rowAdapters.isEmpty()) {
+            return query
         }
-    } else {
-        query
+        // Don't know how to expand when multiple POJO types are created from the same row.
+        if (resultAdapter.rowAdapters.size > 1) {
+            return query
+        }
+        val rowAdapter = resultAdapter.rowAdapters.single()
+        return if (rowAdapter is PojoRowAdapter) {
+            interpret(
+                query = ExpandableSqlParser.parse(query.original),
+                pojo = rowAdapter.pojo
+            ).let {
+                val reParsed = SqlParser.parse(it)
+                if (reParsed.errors.isEmpty()) {
+                    reParsed
+                } else {
+                    query // return original, expansion somewhat failed
+                }
+            }
+        } else {
+            query
+        }
     }
 
     private fun interpret(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/parser/optimization/RemoveUnusedColumnQueryRewriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/parser/optimization/RemoveUnusedColumnQueryRewriter.kt
index 5c5fc2b..56264f6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/parser/optimization/RemoveUnusedColumnQueryRewriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/parser/optimization/RemoveUnusedColumnQueryRewriter.kt
@@ -19,8 +19,7 @@
 import androidx.room.parser.ParsedQuery
 import androidx.room.parser.SqlParser
 import androidx.room.processor.QueryRewriter
-import androidx.room.solver.query.result.PojoRowAdapter
-import androidx.room.solver.query.result.RowAdapter
+import androidx.room.solver.query.result.QueryResultAdapter
 
 /**
  * If the query response has unused columns, this rewrites the query to only fetch those columns.
@@ -31,18 +30,20 @@
  * flattens the query to avoid fetching unused columns in intermediate steps.
  */
 object RemoveUnusedColumnQueryRewriter : QueryRewriter {
-    override fun rewrite(query: ParsedQuery, rowAdapter: RowAdapter): ParsedQuery {
-        if (rowAdapter !is PojoRowAdapter) {
-            return query
-        }
+    override fun rewrite(query: ParsedQuery, resultAdapter: QueryResultAdapter): ParsedQuery {
         // cannot do anything w/o a result info
         val resultInfo = query.resultInfo ?: return query
-
-        val unusedColumns = rowAdapter.mapping.unusedColumns
+        if (resultAdapter.mappings.isEmpty()) {
+            return query
+        }
+        val usedColumns = resultAdapter.mappings.flatMap { mapping ->
+            mapping.matchedFields.map { it.columnName }
+        }
+        val columnNames = resultInfo.columns.map { it.name }
+        val unusedColumns = columnNames - usedColumns
         if (unusedColumns.isEmpty()) {
             return query // nothing to optimize here
         }
-        val columnNames = resultInfo.columns.map { it.name }
         if (columnNames.size != columnNames.distinct().size) {
             // if result has duplicate columns, ignore for now
             return query
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 83381bc..c520c32 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -250,16 +250,20 @@
     }
 
     fun cursorPojoMismatch(
-        pojoTypeName: TypeName,
+        pojoTypeNames: List<TypeName>,
         unusedColumns: List<String>,
         allColumns: List<String>,
-        unusedFields: List<Field>,
-        allFields: List<Field>
+        pojoUnusedFields: Map<TypeName, List<Field>>,
     ): String {
         val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) {
+            val pojoNames = if (pojoTypeNames.size > 1) {
+                "any of [${pojoTypeNames.joinToString(", ")}]"
+            } else {
+                pojoTypeNames.single().toString()
+            }
             """
                 The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
-                used by $pojoTypeName. You can use @ColumnInfo annotation on the fields to specify
+                used by $pojoNames. You can use @ColumnInfo annotation on the fields to specify
                 the mapping.
                 You can annotate the method with @RewriteQueriesToDropUnusedColumns to direct Room
                 to rewrite your query to avoid fetching unused columns.
@@ -267,24 +271,20 @@
         } else {
             ""
         }
-        val unusedFieldsWarning = if (unusedFields.isNotEmpty()) {
+        val unusedFieldsWarning = pojoUnusedFields.map { (pojoName, unusedFields) ->
             """
-                $pojoTypeName has some fields
-                [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by the
-                query. If they are not supposed to be read from the result, you can mark them with
-                @Ignore annotation.
+                $pojoName has some fields
+                [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by
+                the query. If they are not supposed to be read from the result, you can mark them
+                with @Ignore annotation.
             """.trim()
-        } else {
-            ""
         }
-
         return """
             $unusedColumnsWarning
-            $unusedFieldsWarning
+            ${unusedFieldsWarning.joinToString(separator = " ")}
             You can suppress this warning by annotating the method with
             @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH).
             Columns returned by the query: ${allColumns.joinToString(", ")}.
-            Fields in $pojoTypeName: ${allFields.joinToString(", ") { it.columnName }}.
             """.trim()
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
index aec2e0f..ebd0e81 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -19,12 +19,12 @@
 import androidx.room.Query
 import androidx.room.SkipQueryVerification
 import androidx.room.Transaction
-import androidx.room.parser.ParsedQuery
-import androidx.room.parser.QueryType
-import androidx.room.parser.SqlParser
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.isNotError
+import androidx.room.parser.ParsedQuery
+import androidx.room.parser.QueryType
+import androidx.room.parser.SqlParser
 import androidx.room.solver.query.result.PojoRowAdapter
 import androidx.room.verifier.DatabaseVerificationErrors
 import androidx.room.verifier.DatabaseVerifier
@@ -68,10 +68,10 @@
         }
         // check if want to swap the query for a better one
         val finalResult = if (initialResult is ReadQueryMethod) {
-            val rowAdapter = initialResult.queryResultBinder.adapter?.rowAdapter
+            val resultAdapter = initialResult.queryResultBinder.adapter
             val originalQuery = initialResult.query
-            val finalQuery = rowAdapter?.let {
-                context.queryRewriter.rewrite(originalQuery, rowAdapter)
+            val finalQuery = resultAdapter?.let {
+                context.queryRewriter.rewrite(originalQuery, resultAdapter)
             } ?: originalQuery
             if (finalQuery != originalQuery) {
                 // ok parse again
@@ -202,7 +202,6 @@
         query: ParsedQuery
     ): QueryMethod {
         val resultBinder = delegate.findResultBinder(returnType, query)
-        val rowAdapter = resultBinder.adapter?.rowAdapter
         context.checker.check(
             resultBinder.adapter != null,
             executableElement,
@@ -212,7 +211,11 @@
         val inTransaction = executableElement.hasAnnotation(Transaction::class)
         if (query.type == QueryType.SELECT && !inTransaction) {
             // put a warning if it is has relations and not annotated w/ transaction
-            if (rowAdapter is PojoRowAdapter && rowAdapter.relationCollectors.isNotEmpty()) {
+            val hasRelations =
+                resultBinder.adapter?.rowAdapters?.any { adapter ->
+                    adapter is PojoRowAdapter && adapter.relationCollectors.isNotEmpty()
+                } == true
+            if (hasRelations) {
                 context.logger.w(
                     Warning.RELATION_QUERY_WITHOUT_TRANSACTION,
                     executableElement, ProcessorErrors.TRANSACTION_MISSING_ON_RELATION
@@ -220,6 +223,32 @@
             }
         }
 
+        query.resultInfo?.let { queryResultInfo ->
+            val mappings = resultBinder.adapter?.mappings ?: return@let
+            // If there are no mapping (e.g. might be a primitive return type result), then we
+            // can't reasonable determine cursor mismatch.
+            if (mappings.isEmpty()) {
+                return@let
+            }
+            val usedColumns = mappings.flatMap { mapping ->
+                mapping.matchedFields.map { it.columnName }
+            }
+            val columnNames = queryResultInfo.columns.map { it.name }
+            val unusedColumns = columnNames - usedColumns
+            val pojoUnusedFields = mappings
+                .filter { it.unusedFields.isNotEmpty() }
+                .associate { it.pojo.typeName to it.unusedFields }
+            if (unusedColumns.isNotEmpty() || pojoUnusedFields.isNotEmpty()) {
+                val warningMsg = ProcessorErrors.cursorPojoMismatch(
+                    pojoTypeNames = mappings.map { it.pojo.typeName },
+                    unusedColumns = unusedColumns,
+                    allColumns = columnNames,
+                    pojoUnusedFields = pojoUnusedFields,
+                )
+                context.logger.w(Warning.CURSOR_MISMATCH, executableElement, warningMsg)
+            }
+        }
+
         val parameters = delegate.extractQueryParams(query)
 
         return ReadQueryMethod(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryRewriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryRewriter.kt
index af2ece4..7ca2a47 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryRewriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryRewriter.kt
@@ -17,7 +17,7 @@
 package androidx.room.processor
 
 import androidx.room.parser.ParsedQuery
-import androidx.room.solver.query.result.RowAdapter
+import androidx.room.solver.query.result.QueryResultAdapter
 
 /**
  * Interface to rewrite user queries
@@ -27,11 +27,11 @@
      * Rewrites the user given query. This is a place where we can run optimizations etc on the
      * user query.
      */
-    fun rewrite(query: ParsedQuery, rowAdapter: RowAdapter): ParsedQuery
+    fun rewrite(query: ParsedQuery, resultAdapter: QueryResultAdapter): ParsedQuery
 
     companion object {
         val NoOpRewriter = object : QueryRewriter {
-            override fun rewrite(query: ParsedQuery, rowAdapter: RowAdapter) = query
+            override fun rewrite(query: ParsedQuery, resultAdapter: QueryResultAdapter) = query
         }
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 5147586..ecad9834 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -423,68 +423,66 @@
             val rowAdapter =
                 findRowAdapter(typeMirror.componentType, query) ?: return null
             return ArrayQueryResultAdapter(rowAdapter)
-        } else {
-            if (typeMirror.typeArguments.isEmpty()) {
-                val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
-                return SingleEntityQueryResultAdapter(rowAdapter)
-            } else if (typeMirror.rawType.typeName == GuavaBaseTypeNames.OPTIONAL) {
-                // Handle Guava Optional by unpacking its generic type argument and adapting that.
-                // The Optional adapter will reappend the Optional type.
-                val typeArg = typeMirror.typeArguments.first()
-                // use nullable when finding row adapter as non-null adapters might return
-                // default values
-                val rowAdapter = findRowAdapter(typeArg.makeNullable(), query) ?: return null
-                return GuavaOptionalQueryResultAdapter(
-                    typeArg = typeArg,
-                    resultAdapter = SingleEntityQueryResultAdapter(rowAdapter)
-                )
-            } else if (typeMirror.rawType.typeName == CommonTypeNames.OPTIONAL) {
-                // Handle java.util.Optional similarly.
-                val typeArg = typeMirror.typeArguments.first()
-                // use nullable when finding row adapter as non-null adapters might return
-                // default values
-                val rowAdapter = findRowAdapter(typeArg.makeNullable(), query) ?: return null
-                return OptionalQueryResultAdapter(
-                    typeArg = typeArg,
-                    resultAdapter = SingleEntityQueryResultAdapter(rowAdapter)
-                )
-            } else if (typeMirror.isTypeOf(ImmutableList::class)) {
-                val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
-                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
-                return ImmutableListQueryResultAdapter(
-                    typeArg = typeArg,
-                    rowAdapter = rowAdapter
-                )
-            } else if (typeMirror.isTypeOf(java.util.List::class)) {
-                val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
-                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
-                return ListQueryResultAdapter(
-                    typeArg = typeArg,
-                    rowAdapter = rowAdapter
-                )
-            } else if (typeMirror.isTypeOf(java.util.Map::class)) {
-                val keyArg = typeMirror.typeArguments[0].extendsBoundOrSelf()
-                val secondTypeArg = typeMirror.typeArguments[1].extendsBoundOrSelf()
+        } else if (typeMirror.typeArguments.isEmpty()) {
+            val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
+            return SingleEntityQueryResultAdapter(rowAdapter)
+        } else if (typeMirror.rawType.typeName == GuavaBaseTypeNames.OPTIONAL) {
+            // Handle Guava Optional by unpacking its generic type argument and adapting that.
+            // The Optional adapter will reappend the Optional type.
+            val typeArg = typeMirror.typeArguments.first()
+            // use nullable when finding row adapter as non-null adapters might return
+            // default values
+            val rowAdapter = findRowAdapter(typeArg.makeNullable(), query) ?: return null
+            return GuavaOptionalQueryResultAdapter(
+                typeArg = typeArg,
+                resultAdapter = SingleEntityQueryResultAdapter(rowAdapter)
+            )
+        } else if (typeMirror.rawType.typeName == CommonTypeNames.OPTIONAL) {
+            // Handle java.util.Optional similarly.
+            val typeArg = typeMirror.typeArguments.first()
+            // use nullable when finding row adapter as non-null adapters might return
+            // default values
+            val rowAdapter = findRowAdapter(typeArg.makeNullable(), query) ?: return null
+            return OptionalQueryResultAdapter(
+                typeArg = typeArg,
+                resultAdapter = SingleEntityQueryResultAdapter(rowAdapter)
+            )
+        } else if (typeMirror.isTypeOf(ImmutableList::class)) {
+            val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
+            val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+            return ImmutableListQueryResultAdapter(
+                typeArg = typeArg,
+                rowAdapter = rowAdapter
+            )
+        } else if (typeMirror.isTypeOf(java.util.List::class)) {
+            val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
+            val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+            return ListQueryResultAdapter(
+                typeArg = typeArg,
+                rowAdapter = rowAdapter
+            )
+        } else if (typeMirror.isTypeOf(java.util.Map::class)) {
+            val keyArg = typeMirror.typeArguments[0].extendsBoundOrSelf()
+            val secondTypeArg = typeMirror.typeArguments[1].extendsBoundOrSelf()
 
-                // TODO: Support Set::class here as well.
-                if (!secondTypeArg.isTypeOf(java.util.List::class)) {
-                    context.logger.e("Only supporting Map<Key, List<Value>> for now.")
-                    return null
-                }
-                val valueArg = secondTypeArg.typeArguments.first().extendsBoundOrSelf()
-
-                val keyRowAdapter = findRowAdapter(keyArg, query) ?: return null
-                val valueRowAdapter = findRowAdapter(valueArg, query) ?: return null
-
-                return MapQueryResultAdapter(
-                    keyTypeArg = keyArg,
-                    valueTypeArg = valueArg,
-                    keyRowAdapter = keyRowAdapter,
-                    valueRowAdapter = valueRowAdapter
-                )
+            // TODO: Support Set::class here as well.
+            if (!secondTypeArg.isTypeOf(java.util.List::class)) {
+                context.logger.e("Only supporting Map<Key, List<Value>> for now.")
+                return null
             }
-            return null
+            val valueArg = secondTypeArg.typeArguments.first().extendsBoundOrSelf()
+
+            val keyRowAdapter = findRowAdapter(keyArg, query) ?: return null
+            val valueRowAdapter = findRowAdapter(valueArg, query) ?: return null
+
+            return MapQueryResultAdapter(
+                keyTypeArg = keyArg,
+                valueTypeArg = valueArg,
+                keyRowAdapter = keyRowAdapter,
+                valueRowAdapter = valueRowAdapter
+            )
         }
+        return null
     }
 
     /**
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
index 9d7d0c1..88cdd1a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
@@ -22,11 +22,13 @@
 import com.squareup.javapoet.ArrayTypeName
 import com.squareup.javapoet.TypeName
 
-class ArrayQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+class ArrayQueryResultAdapter(
+    private val rowAdapter: RowAdapter
+) : QueryResultAdapter(listOf(rowAdapter)) {
     val type = rowAdapter.out
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
+            rowAdapter.onCursorReady(cursorVarName, scope)
 
             val arrayType = ArrayTypeName.of(type.typeName)
             addStatement(
@@ -38,12 +40,12 @@
             addStatement("$T $L = 0", TypeName.INT, indexVar)
             beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
                 addStatement("final $T $L", type.typeName, tmpVarName)
-                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                rowAdapter.convert(tmpVarName, cursorVarName, scope)
                 addStatement("$L[$L] = $L", outVarName, indexVar, tmpVarName)
                 addStatement("$L ++", indexVar)
             }
             endControlFlow()
-            rowAdapter?.onCursorFinished()?.invoke(scope)
+            rowAdapter.onCursorFinished()?.invoke(scope)
         }
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
index beccfd7..38d5132 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
@@ -49,7 +49,7 @@
     }
 
     companion object {
-        private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(null) {
+        private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(emptyList()) {
             override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
             }
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
index a76b36e7..78ac7559 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
@@ -30,7 +30,7 @@
 class GuavaOptionalQueryResultAdapter(
     private val typeArg: XType,
     private val resultAdapter: SingleEntityQueryResultAdapter
-) : QueryResultAdapter(resultAdapter.rowAdapter) {
+) : QueryResultAdapter(resultAdapter.rowAdapters) {
     override fun convert(
         outVarName: String,
         cursorVarName: String,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt
index 7264e80..1492be9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt
@@ -26,11 +26,11 @@
 
 class ImmutableListQueryResultAdapter(
     private val typeArg: XType,
-    rowAdapter: RowAdapter
-) : QueryResultAdapter(rowAdapter) {
+    private val rowAdapter: RowAdapter
+) : QueryResultAdapter(listOf(rowAdapter)) {
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
+            rowAdapter.onCursorReady(cursorVarName, scope)
             val collectionType = ParameterizedTypeName
                 .get(ClassName.get(ImmutableList::class.java), typeArg.typeName)
             val immutableListBuilderType = ParameterizedTypeName
@@ -44,7 +44,7 @@
             val tmpVarName = scope.getTmpVar("_item")
             beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
                 addStatement("final $T $L", typeArg.typeName, tmpVarName)
-                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                rowAdapter.convert(tmpVarName, cursorVarName, scope)
                 addStatement("$L.add($L)", immutableListBuilderName, tmpVarName)
             }
             endControlFlow()
@@ -52,7 +52,7 @@
                 "final $T $L = $L.build()",
                 collectionType, outVarName, immutableListBuilderName
             )
-            rowAdapter?.onCursorFinished()?.invoke(scope)
+            rowAdapter.onCursorFinished()?.invoke(scope)
         }
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
index 99e4fa6..6ba990b 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
@@ -26,11 +26,11 @@
 
 class ListQueryResultAdapter(
     private val typeArg: XType,
-    rowAdapter: RowAdapter
-) : QueryResultAdapter(rowAdapter) {
+    private val rowAdapter: RowAdapter
+) : QueryResultAdapter(listOf(rowAdapter)) {
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
+            rowAdapter.onCursorReady(cursorVarName, scope)
             val collectionType = ParameterizedTypeName
                 .get(ClassName.get(List::class.java), typeArg.typeName)
             val arrayListType = ParameterizedTypeName
@@ -42,11 +42,11 @@
             val tmpVarName = scope.getTmpVar("_item")
             beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
                 addStatement("final $T $L", typeArg.typeName, tmpVarName)
-                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                rowAdapter.convert(tmpVarName, cursorVarName, scope)
                 addStatement("$L.add($L)", outVarName, tmpVarName)
             }
             endControlFlow()
-            rowAdapter?.onCursorFinished()?.invoke(scope)
+            rowAdapter.onCursorFinished()?.invoke(scope)
         }
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt
index 520b1de..124d9da 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt
@@ -28,7 +28,7 @@
     private val valueTypeArg: XType,
     private val keyRowAdapter: RowAdapter,
     private val valueRowAdapter: RowAdapter,
-) : QueryResultAdapter(null) {
+) : QueryResultAdapter(listOf(keyRowAdapter, valueRowAdapter)) {
     private val listType = ParameterizedTypeName.get(
         ClassName.get(List::class.java),
         valueTypeArg.typeName
@@ -83,13 +83,4 @@
             valueRowAdapter.onCursorFinished()?.invoke(scope)
         }
     }
-
-    override fun shouldCopyCursor() =
-        (keyRowAdapter is PojoRowAdapter && keyRowAdapter.relationCollectors.isNotEmpty()) ||
-            (valueRowAdapter is PojoRowAdapter && valueRowAdapter.relationCollectors.isNotEmpty())
-
-    override fun accessedTableNames() = mutableListOf<String>().apply {
-        (keyRowAdapter as? PojoRowAdapter)?.relationTableNames()?.let { addAll(it) }
-        (valueRowAdapter as? PojoRowAdapter)?.relationTableNames()?.let { addAll(it) }
-    }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt
index 25d6ba2..72b83de0 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt
@@ -32,7 +32,7 @@
 class OptionalQueryResultAdapter(
     private val typeArg: XType,
     private val resultAdapter: SingleEntityQueryResultAdapter
-) : QueryResultAdapter(resultAdapter.rowAdapter) {
+) : QueryResultAdapter(resultAdapter.rowAdapters) {
     override fun convert(
         outVarName: String,
         cursorVarName: String,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PagingSourceQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PagingSourceQueryResultBinder.kt
index 6db2f39..85bbb87 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PagingSourceQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PagingSourceQueryResultBinder.kt
@@ -25,9 +25,9 @@
 import com.squareup.javapoet.FieldSpec
 import com.squareup.javapoet.MethodSpec
 import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeSpec
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
 import javax.lang.model.element.Modifier
 
 /**
@@ -38,7 +38,8 @@
     private val listAdapter: ListQueryResultAdapter?,
     private val tableNames: Set<String>,
 ) : QueryResultBinder(listAdapter) {
-    private val itemTypeName: TypeName = listAdapter?.rowAdapter?.out?.typeName ?: TypeName.OBJECT
+    private val itemTypeName: TypeName =
+        listAdapter?.rowAdapters?.firstOrNull()?.out?.typeName ?: TypeName.OBJECT
     private val limitOffsetPagingSourceTypeNam: ParameterizedTypeName = ParameterizedTypeName.get(
         RoomTypeNames.LIMIT_OFFSET_PAGING_SOURCE, itemTypeName
     )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
index fdce370..c14942b 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -16,11 +16,11 @@
 
 package androidx.room.solver.query.result
 
+import androidx.room.compiler.processing.XType
 import androidx.room.ext.L
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.S
 import androidx.room.ext.T
-import androidx.room.compiler.processing.XType
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
 import androidx.room.solver.CodeGenScope
@@ -29,7 +29,6 @@
 import androidx.room.vo.FieldWithIndex
 import androidx.room.vo.Pojo
 import androidx.room.vo.RelationCollector
-import androidx.room.vo.Warning
 import androidx.room.vo.findFieldByColumnName
 import androidx.room.writer.FieldReadWriteWriter
 import capitalize
@@ -48,9 +47,12 @@
     val pojo: Pojo,
     out: XType
 ) : RowAdapter(out) {
-    val mapping: Mapping
+    val mapping: QueryMappedResultAdapter.Mapping
     val relationCollectors: List<RelationCollector>
 
+    // Set when cursor is ready.
+    lateinit var fieldsWithIndices: List<FieldWithIndex>
+
     init {
 
         // toMutableList documentation is not clear if it copies so lets be safe.
@@ -71,16 +73,6 @@
                     field
                 }
             }
-            if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) {
-                val warningMsg = ProcessorErrors.cursorPojoMismatch(
-                    pojoTypeName = pojo.typeName,
-                    unusedColumns = unusedColumns,
-                    allColumns = info.columns.map { it.name },
-                    unusedFields = remainingFields,
-                    allFields = pojo.fields
-                )
-                context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg)
-            }
             val nonNulls = remainingFields.filter { it.nonNull }
             if (nonNulls.isNotEmpty()) {
                 context.logger.e(
@@ -100,7 +92,8 @@
         }
         relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
 
-        mapping = Mapping(
+        mapping = QueryMappedResultAdapter.Mapping(
+            pojo = pojo,
             matchedFields = matchedFields,
             unusedColumns = unusedColumns,
             unusedFields = remainingFields
@@ -119,7 +112,7 @@
     }
 
     override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
-        mapping.fieldsWithIndices = mapping.matchedFields.map {
+        fieldsWithIndices = mapping.matchedFields.map {
             val indexVar = scope.getTmpVar(
                 "_cursorIndexOf${it.name.stripNonJava().capitalize(Locale.US)}"
             )
@@ -140,7 +133,7 @@
             scope.builder().apply {
                 beginControlFlow("while ($L.moveToNext())", cursorVarName).apply {
                     relationCollectors.forEach {
-                        it.writeReadParentKeyCode(cursorVarName, mapping.fieldsWithIndices, scope)
+                        it.writeReadParentKeyCode(cursorVarName, fieldsWithIndices, scope)
                     }
                 }
                 endControlFlow()
@@ -156,19 +149,10 @@
                 outVar = outVarName,
                 outPojo = pojo,
                 cursorVar = cursorVarName,
-                fieldsWithIndices = mapping.fieldsWithIndices,
+                fieldsWithIndices = fieldsWithIndices,
                 relationCollectors = relationCollectors,
                 scope = scope
             )
         }
     }
-
-    data class Mapping(
-        val matchedFields: List<Field>,
-        val unusedColumns: List<String>,
-        val unusedFields: List<Field>
-    ) {
-        // set when cursor is ready.
-        lateinit var fieldsWithIndices: List<FieldWithIndex>
-    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
index 3a9ba29..f66a788 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
@@ -49,10 +49,12 @@
     val tableNames: Set<String>,
     val forPaging3: Boolean,
 ) : QueryResultBinder(listAdapter) {
-    val itemTypeName: TypeName = listAdapter?.rowAdapter?.out?.typeName ?: TypeName.OBJECT
+    val itemTypeName: TypeName =
+        listAdapter?.rowAdapters?.firstOrNull()?.out?.typeName ?: TypeName.OBJECT
     val typeName: ParameterizedTypeName = ParameterizedTypeName.get(
         RoomTypeNames.LIMIT_OFFSET_DATA_SOURCE, itemTypeName
     )
+
     override fun convertAndReturn(
         roomSQLiteQueryVar: String,
         canReleaseQuery: Boolean,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryMappedResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryMappedResultAdapter.kt
new file mode 100644
index 0000000..0c54ef7
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryMappedResultAdapter.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.vo.Field
+import androidx.room.vo.Pojo
+
+/**
+ * Interface that defines a result adapter containing mapping information of the query and its
+ * result POJOs if any.
+ */
+interface QueryMappedResultAdapter {
+    val mappings: List<Mapping>
+
+    data class Mapping(
+        val pojo: Pojo,
+        val matchedFields: List<Field>,
+        val unusedColumns: List<String>,
+        val unusedFields: List<Field>
+    )
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt
index 68b5242..fce3604 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt
@@ -21,15 +21,18 @@
 /**
  * Gets a Cursor and converts it into the return type of a method annotated with @Query.
  */
-abstract class QueryResultAdapter(val rowAdapter: RowAdapter?) {
+abstract class QueryResultAdapter(val rowAdapters: List<RowAdapter>) : QueryMappedResultAdapter {
+
+    override val mappings: List<QueryMappedResultAdapter.Mapping>
+        get() = rowAdapters.filterIsInstance<PojoRowAdapter>().map { it.mapping }
 
     abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
 
-    // indicates whether the cursor should be copied before converting
-    open fun shouldCopyCursor() = rowAdapter is PojoRowAdapter &&
-        rowAdapter.relationCollectors.isNotEmpty()
+    // Indicates whether the cursor should be copied before converting.
+    // This is important for performance reasons if the Cursor will be traverse more than once.
+    fun shouldCopyCursor(): Boolean =
+        rowAdapters.filterIsInstance<PojoRowAdapter>().any { it.relationCollectors.isNotEmpty() }
 
-    open fun accessedTableNames(): List<String> {
-        return (rowAdapter as? PojoRowAdapter)?.relationTableNames() ?: emptyList()
-    }
+    fun accessedTableNames(): List<String> =
+        rowAdapters.filterIsInstance<PojoRowAdapter>().flatMap { it.relationTableNames() }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt
index 2411b81..e5c96be 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt
@@ -23,19 +23,21 @@
 /**
  * Wraps a row adapter when there is only 1 item in the result
  */
-class SingleEntityQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+class SingleEntityQueryResultAdapter(
+    private val rowAdapter: RowAdapter
+) : QueryResultAdapter(listOf(rowAdapter)) {
     val type = rowAdapter.out
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
+            rowAdapter.onCursorReady(cursorVarName, scope)
             addStatement("final $T $L", type.typeName, outVarName)
             beginControlFlow("if($L.moveToFirst())", cursorVarName)
-            rowAdapter?.convert(outVarName, cursorVarName, scope)
+            rowAdapter.convert(outVarName, cursorVarName, scope)
             nextControlFlow("else").apply {
-                addStatement("$L = $L", outVarName, rowAdapter?.out?.defaultValue())
+                addStatement("$L = $L", outVarName, rowAdapter.out.defaultValue())
             }
             endControlFlow()
-            rowAdapter?.onCursorFinished()?.invoke(scope)
+            rowAdapter.onCursorFinished()?.invoke(scope)
         }
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 4d2b5b1..96c7a93 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -828,7 +828,7 @@
                 .filterIsInstance<ReadQueryMethod>()
                 .find { it.name == "loadOne" }
             assertThat(loadOne, notNullValue())
-            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
+            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapters?.single()
             assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
             val adapterEntity = (adapter as EntityRowAdapter).entity
             assertThat(
@@ -840,7 +840,7 @@
                 .filterIsInstance<ReadQueryMethod>()
                 .find { it.name == "loadWithConverter" }
             assertThat(withConverter, notNullValue())
-            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
+            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapters?.single()
             assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
             val convAdapterEntity = (convAdapter as EntityRowAdapter).entity
             assertThat(
@@ -869,7 +869,7 @@
                 .filterIsInstance<ReadQueryMethod>()
                 .find { it.name == "loadOnePojo" }
             assertThat(loadOne, notNullValue())
-            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
+            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapters?.single()
             assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
             val adapterPojo = (adapter as PojoRowAdapter).pojo
 
@@ -877,7 +877,7 @@
                 .filterIsInstance<ReadQueryMethod>()
                 .find { it.name == "loadAllPojos" }
             assertThat(loadAll, notNullValue())
-            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapter
+            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapters?.single()
             assertThat("test sanity", loadAllAdapter, instanceOf(PojoRowAdapter::class.java))
             val loadAllPojo = (loadAllAdapter as PojoRowAdapter).pojo
             assertThat(adapter, not(sameInstance(loadAllAdapter)))
@@ -887,7 +887,7 @@
                 .filterIsInstance<ReadQueryMethod>()
                 .find { it.name == "loadPojoWithConverter" }
             assertThat(withConverter, notNullValue())
-            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
+            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapters?.single()
             assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
             val convAdapterPojo = (convAdapter as PojoRowAdapter).pojo
             assertThat(convAdapterPojo, notNullValue())
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 8f573d5..a460d84 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -36,6 +36,7 @@
 import androidx.room.solver.query.result.ListQueryResultAdapter
 import androidx.room.solver.query.result.LiveDataQueryResultBinder
 import androidx.room.solver.query.result.PojoRowAdapter
+import androidx.room.solver.query.result.SingleColumnRowAdapter
 import androidx.room.solver.query.result.SingleEntityQueryResultAdapter
 import androidx.room.testing.context
 import androidx.room.vo.Field
@@ -72,6 +73,8 @@
                 package foo.bar;
                 import androidx.annotation.NonNull;
                 import androidx.room.*;
+                import java.util.Map;
+                import java.util.List;
                 @Dao
                 abstract class MyClass {
                 """
@@ -794,7 +797,7 @@
             val adapter = parsedQuery.queryResultBinder.adapter
             assertThat(checkNotNull(adapter))
             assertThat(adapter::class, `is`(SingleEntityQueryResultAdapter::class))
-            val rowAdapter = adapter.rowAdapter
+            val rowAdapter = adapter.rowAdapters.single()
             assertThat(checkNotNull(rowAdapter))
             assertThat(rowAdapter::class, `is`(PojoRowAdapter::class))
         }
@@ -843,8 +846,8 @@
                 instanceOf(ListQueryResultAdapter::class.java)
             )
             val listAdapter = method.queryResultBinder.adapter as ListQueryResultAdapter
-            assertThat(listAdapter.rowAdapter, instanceOf(PojoRowAdapter::class.java))
-            val pojoRowAdapter = listAdapter.rowAdapter as PojoRowAdapter
+            assertThat(listAdapter.rowAdapters.single(), instanceOf(PojoRowAdapter::class.java))
+            val pojoRowAdapter = listAdapter.rowAdapters.single() as PojoRowAdapter
             assertThat(pojoRowAdapter.relationCollectors.size, `is`(1))
             assertThat(
                 pojoRowAdapter.relationCollectors[0].relationTypeName,
@@ -916,6 +919,28 @@
     }
 
     @Test
+    fun primitive_removeUnusedColumns() {
+        if (!enableVerification) {
+            throw AssumptionViolatedException("nothing to test w/o db verification")
+        }
+        singleQueryMethod<ReadQueryMethod>(
+            """
+                @RewriteQueriesToDropUnusedColumns
+                @Query("select 1 from user")
+                abstract int getOne();
+                """
+        ) { method, invocation ->
+            val adapter = method.queryResultBinder.adapter?.rowAdapters?.single()
+            check(adapter is SingleColumnRowAdapter)
+            assertThat(method.query.original)
+                .isEqualTo("select 1 from user")
+            invocation.assertCompilationResult {
+                hasNoWarnings()
+            }
+        }
+    }
+
+    @Test
     fun pojo_removeUnusedColumns() {
         if (!enableVerification) {
             throw AssumptionViolatedException("nothing to test w/o db verification")
@@ -931,7 +956,7 @@
                 abstract Pojo loadUsers();
                 """
         ) { method, invocation ->
-            val adapter = method.queryResultBinder.adapter?.rowAdapter
+            val adapter = method.queryResultBinder.adapter?.rowAdapters?.single()
             check(adapter is PojoRowAdapter)
             assertThat(method.query.original)
                 .isEqualTo("SELECT `name`, `lastName` FROM (select * from user LIMIT 1)")
@@ -942,6 +967,46 @@
     }
 
     @Test
+    fun pojo_multimapQuery_removeUnusedColumns() {
+        if (!enableVerification) {
+            throw AssumptionViolatedException("nothing to test w/o db verification")
+        }
+        val relatingEntity = Source.java(
+            "foo.bar.Relation",
+            """
+            package foo.bar;
+            import androidx.room.*;
+            @Entity
+            public class Relation {
+              @PrimaryKey
+              long relationId;
+              long userId;
+            }
+            """.trimIndent()
+        )
+        singleQueryMethod<ReadQueryMethod>(
+            """
+                public static class Username {
+                    public String name;
+                }
+                @RewriteQueriesToDropUnusedColumns
+                @Query("SELECT * FROM User JOIN Relation ON (User.uid = Relation.userId)")
+                abstract Map<Username, List<Relation>> loadUserRelations();
+                """,
+            additionalSources = listOf(relatingEntity)
+        ) { method, invocation ->
+            assertThat(method.query.original)
+                .isEqualTo(
+                    "SELECT `name`, `relationId`, `userId` FROM " +
+                        "(SELECT * FROM User JOIN Relation ON (User.uid = Relation.userId))"
+                )
+            invocation.assertCompilationResult {
+                hasNoWarnings()
+            }
+        }
+    }
+
+    @Test
     fun pojo_dontRemoveUnusedColumnsWhenColumnNamesConflict() {
         if (!enableVerification) {
             throw AssumptionViolatedException("nothing to test w/o db verification")
@@ -957,7 +1022,7 @@
                 abstract Pojo loadUsers();
                 """
         ) { method, invocation ->
-            val adapter = method.queryResultBinder.adapter?.rowAdapter
+            val adapter = method.queryResultBinder.adapter?.rowAdapters?.single()
             check(adapter is PojoRowAdapter)
             assertThat(method.query.original).isEqualTo("select * from user u, user u2 LIMIT 1")
             invocation.assertCompilationResult {
@@ -1003,17 +1068,15 @@
                 )
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
+                        pojoTypeNames = listOf(POJO),
                         unusedColumns = listOf("name", "lastName"),
-                        unusedFields = listOf(
-                            createField("nameX"),
-                            createField("lastNameX")
+                        pojoUnusedFields = mapOf(
+                            POJO to listOf(
+                                createField("nameX"),
+                                createField("lastNameX")
+                            )
                         ),
                         allColumns = listOf("name", "lastName"),
-                        allFields = listOf(
-                            createField("nameX"),
-                            createField("lastNameX")
-                        )
                     )
                 )
             }
@@ -1058,11 +1121,10 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
+                        pojoTypeNames = listOf(POJO),
                         unusedColumns = listOf("uid"),
-                        unusedFields = emptyList(),
+                        pojoUnusedFields = emptyMap(),
                         allColumns = listOf("uid", "name", "lastName"),
-                        allFields = listOf(createField("name"), createField("lastName"))
                     )
                 )
             }
@@ -1088,11 +1150,10 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
+                        pojoTypeNames = listOf(POJO),
                         unusedColumns = emptyList(),
-                        unusedFields = listOf(createField("name")),
                         allColumns = listOf("lastName"),
-                        allFields = listOf(createField("name"), createField("lastName"))
+                        pojoUnusedFields = mapOf(POJO to listOf(createField("name"))),
                     )
                 )
             }
@@ -1119,11 +1180,10 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
+                        pojoTypeNames = listOf(POJO),
                         unusedColumns = emptyList(),
-                        unusedFields = listOf(createField("name")),
+                        pojoUnusedFields = mapOf(POJO to listOf(createField("name"))),
                         allColumns = listOf("lastName"),
-                        allFields = listOf(createField("name"), createField("lastName"))
                     )
                 )
                 hasErrorContaining(
@@ -1156,11 +1216,10 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
+                        pojoTypeNames = listOf(POJO),
                         unusedColumns = listOf("uid"),
-                        unusedFields = listOf(createField("lastName")),
                         allColumns = listOf("uid", "name"),
-                        allFields = listOf(createField("name"), createField("lastName"))
+                        pojoUnusedFields = mapOf(POJO to listOf(createField("lastName")))
                     )
                 )
             }
@@ -1206,7 +1265,11 @@
             val adapter = parsedQuery.queryResultBinder.adapter
             if (enableVerification) {
                 if (adapter is SingleEntityQueryResultAdapter) {
-                    handler(adapter.rowAdapter as? PojoRowAdapter, parsedQuery, invocation)
+                    handler(
+                        adapter.rowAdapters.single() as? PojoRowAdapter,
+                        parsedQuery,
+                        invocation
+                    )
                 } else {
                     handler(null, parsedQuery, invocation)
                 }