Merge "Merged insert and upsert adapters; piped adapter to processor" into androidx-main
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt
index db917ab..28b2d34 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2022 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.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
index 7d87c5a..e45bf90 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
@@ -77,12 +77,11 @@
         )
 
         val methodBinder = delegate.findUpsertMethodBinder(returnType, params)
-        // TODO: (b/240491114) Uncomment code below for UpsertMethodAdapter is implemented
-        /*context.checker.check(
+        context.checker.check(
             methodBinder.adapter != null,
             executableElement,
             ProcessorErrors.CANNOT_FIND_UPSERT_RESULT_ADAPTER
-        )*/
+        )
 
         return UpsertionMethod(
             element = executableElement,
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 b7d3188..8662073 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
@@ -93,8 +93,7 @@
 import androidx.room.solver.shortcut.binderprovider.RxCallableInsertMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.RxCallableUpsertMethodBinderProvider
 import androidx.room.solver.shortcut.result.DeleteOrUpdateMethodAdapter
-import androidx.room.solver.shortcut.result.InsertMethodAdapter
-import androidx.room.solver.shortcut.result.UpsertMethodAdapter
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.solver.types.BoxedBooleanToBoxedIntConverter
 import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter
 import androidx.room.solver.types.ByteArrayColumnTypeAdapter
@@ -448,17 +447,15 @@
     fun findInsertAdapter(
         typeMirror: XType,
         params: List<ShortcutQueryParameter>
-    ): InsertMethodAdapter? {
-        return InsertMethodAdapter.create(typeMirror, params)
+    ): InsertOrUpsertMethodAdapter? {
+        return InsertOrUpsertMethodAdapter.createInsert(typeMirror, params)
     }
 
-    @Suppress("UNUSED_PARAMETER") // param will be used in a future change
     fun findUpsertAdapter(
         typeMirror: XType,
         params: List<ShortcutQueryParameter>
-    ): UpsertMethodAdapter? {
-        // TODO: change for UpsertMethodAdapter when bind has been created
-        return null
+    ): InsertOrUpsertMethodAdapter? {
+        return InsertOrUpsertMethodAdapter.createUpsert(typeMirror, params)
     }
 
     fun findQueryResultAdapter(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt
index e73004d..a4601ad 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertMethodBinder.kt
@@ -19,7 +19,7 @@
 import androidx.room.ext.CallableTypeSpecBuilder
 import androidx.room.compiler.processing.XType
 import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.InsertMethodAdapter
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
 import com.squareup.javapoet.CodeBlock
 import com.squareup.javapoet.FieldSpec
@@ -29,19 +29,19 @@
  * Binder for deferred insert methods.
  *
  * This binder will create a Callable implementation that delegates to the
- * [InsertMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
+ * [InsertOrUpsertMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
  * function.
  */
 class CallableInsertMethodBinder(
     val typeArg: XType,
     val addStmntBlock: CodeBlock.Builder.(callableImpl: TypeSpec, dbField: FieldSpec) -> Unit,
-    adapter: InsertMethodAdapter?
+    adapter: InsertOrUpsertMethodAdapter?
 ) : InsertOrUpsertMethodBinder(adapter) {
 
     companion object {
         fun createInsertBinder(
             typeArg: XType,
-            adapter: InsertMethodAdapter?,
+            adapter: InsertOrUpsertMethodAdapter?,
             addCodeBlock: CodeBlock.Builder.(callableImpl: TypeSpec, dbField: FieldSpec) -> Unit
         ) = CallableInsertMethodBinder(typeArg, addCodeBlock, adapter)
     }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt
index 7040cc4..b579df0 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableUpsertMethodBinder.kt
@@ -19,7 +19,7 @@
 import androidx.room.ext.CallableTypeSpecBuilder
 import androidx.room.compiler.processing.XType
 import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.UpsertMethodAdapter
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
 import com.squareup.javapoet.CodeBlock
 import com.squareup.javapoet.FieldSpec
@@ -29,19 +29,19 @@
  * Binder for deferred upsert methods.
  *
  * This binder will create a Callable implementation that delegates to the
- * [UpsertMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
+ * [InsertOrUpsertMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
  * function.
  */
 class CallableUpsertMethodBinder(
     val typeArg: XType,
     val addStmntBlock: CodeBlock.Builder.(callableImpl: TypeSpec, dbField: FieldSpec) -> Unit,
-    adapter: UpsertMethodAdapter?
+    adapter: InsertOrUpsertMethodAdapter?
 ) : InsertOrUpsertMethodBinder(adapter) {
 
     companion object {
         fun createUpsertBinder(
             typeArg: XType,
-            adapter: UpsertMethodAdapter?,
+            adapter: InsertOrUpsertMethodAdapter?,
             addCodeBlock: CodeBlock.Builder.(callableImpl: TypeSpec, dbField: FieldSpec) -> Unit
         ) = CallableUpsertMethodBinder(typeArg, addCodeBlock, adapter)
     }
@@ -54,7 +54,12 @@
     ) {
         val adapterScope = scope.fork()
         val callableImpl = CallableTypeSpecBuilder(typeArg.typeName) {
-            // TODO add the createMethodBody in UpsertMethodAdapter
+            adapter?.createMethodBody(
+                parameters = parameters,
+                adapters = adapters,
+                dbField = dbField,
+                scope = adapterScope
+            )
             addCode(adapterScope.generate())
         }.build()
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
index f909b2a..0fa0ae0 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
@@ -18,13 +18,11 @@
 
 import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.InsertMethodAdapter
-import androidx.room.solver.shortcut.result.UpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
 import com.squareup.javapoet.FieldSpec
 
 /**
- * Connects the insert and upsert method, the database and the [InsertMethodAdapter] or [UpsertMethodAdapter].
+ * Connects the insert and upsert method, the database and the [InsertOrUpsertMethodAdapter].
  */
 abstract class InsertOrUpsertMethodBinder(val adapter: InsertOrUpsertMethodAdapter?) {
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
index ac4c6cd..8e3aa51 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantInsertMethodBinder.kt
@@ -18,7 +18,7 @@
 
 import androidx.room.ext.N
 import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.InsertMethodAdapter
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
 import androidx.room.writer.DaoWriter
 import com.squareup.javapoet.FieldSpec
@@ -26,7 +26,7 @@
 /**
  * Binder that knows how to write instant (blocking) insert methods.
  */
-class InstantInsertMethodBinder(adapter: InsertMethodAdapter?) :
+class InstantInsertMethodBinder(adapter: InsertOrUpsertMethodAdapter?) :
     InsertOrUpsertMethodBinder(adapter) {
 
     override fun convertAndReturn(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
index 2043db9..bc6f811 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InstantUpsertMethodBinder.kt
@@ -18,7 +18,7 @@
 
 import androidx.room.ext.N
 import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.UpsertMethodAdapter
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.vo.ShortcutQueryParameter
 import androidx.room.writer.DaoWriter
 import com.squareup.javapoet.FieldSpec
@@ -26,7 +26,7 @@
 /**
  * Binder that knows how to write instant (blocking) upsert methods.
  */
-class InstantUpsertMethodBinder(adapter: UpsertMethodAdapter?) :
+class InstantUpsertMethodBinder(adapter: InsertOrUpsertMethodAdapter?) :
     InsertOrUpsertMethodBinder(adapter) {
 
     override fun convertAndReturn(
@@ -38,6 +38,11 @@
         scope.builder().apply {
             addStatement("$N.assertNotSuspendingTransaction()", DaoWriter.dbField)
         }
-        // TODO: createUpsertionMethodBody
+        adapter?.createMethodBody(
+            parameters = parameters,
+            adapters = adapters,
+            dbField = dbField,
+            scope = scope
+        )
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt
deleted file mode 100644
index c910e02..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2018 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.shortcut.result
-
-import androidx.room.compiler.processing.XType
-import androidx.room.compiler.processing.isArray
-import androidx.room.compiler.processing.isKotlinUnit
-import androidx.room.compiler.processing.isLong
-import androidx.room.compiler.processing.isVoid
-import androidx.room.compiler.processing.isVoidObject
-import androidx.room.ext.KotlinTypeNames
-import androidx.room.ext.L
-import androidx.room.ext.N
-import androidx.room.ext.T
-import androidx.room.ext.typeName
-import androidx.room.solver.CodeGenScope
-import androidx.room.vo.ShortcutQueryParameter
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-
-/**
- * Class that knows how to generate an insert method body.
- */
-class InsertMethodAdapter private constructor(private val insertionType: InsertionType) :
-    InsertOrUpsertMethodAdapter() {
-    companion object {
-        fun create(
-            returnType: XType,
-            params: List<ShortcutQueryParameter>
-        ): InsertMethodAdapter? {
-            val insertionType = getInsertionType(returnType)
-            if (insertionType != null && isInsertValid(insertionType, params)) {
-                return InsertMethodAdapter(insertionType)
-            }
-            return null
-        }
-
-        private fun isInsertValid(
-            insertionType: InsertionType?,
-            params: List<ShortcutQueryParameter>
-        ): Boolean {
-            if (insertionType == null) {
-                return false
-            }
-            if (params.isEmpty() || params.size > 1) {
-                return insertionType == InsertionType.INSERT_VOID ||
-                    insertionType == InsertionType.INSERT_UNIT
-            }
-            return if (params.first().isMultiple) {
-                insertionType in MULTIPLE_ITEM_SET
-            } else {
-                insertionType == InsertionType.INSERT_VOID ||
-                    insertionType == InsertionType.INSERT_VOID_OBJECT ||
-                    insertionType == InsertionType.INSERT_UNIT ||
-                    insertionType == InsertionType.INSERT_SINGLE_ID
-            }
-        }
-
-        private val MULTIPLE_ITEM_SET by lazy {
-            setOf(
-                InsertionType.INSERT_VOID,
-                InsertionType.INSERT_VOID_OBJECT,
-                InsertionType.INSERT_UNIT,
-                InsertionType.INSERT_ID_ARRAY,
-                InsertionType.INSERT_ID_ARRAY_BOX,
-                InsertionType.INSERT_ID_LIST
-            )
-        }
-
-        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-        private fun getInsertionType(returnType: XType): InsertionType? {
-            return if (returnType.isVoid()) {
-                InsertionType.INSERT_VOID
-            } else if (returnType.isVoidObject()) {
-                InsertionType.INSERT_VOID_OBJECT
-            } else if (returnType.isKotlinUnit()) {
-                InsertionType.INSERT_UNIT
-            } else if (returnType.isArray()) {
-                val param = returnType.componentType
-                if (param.isLong()) {
-                    if (param.typeName == TypeName.LONG) {
-                        InsertionType.INSERT_ID_ARRAY
-                    } else {
-                        InsertionType.INSERT_ID_ARRAY_BOX
-                    }
-                } else {
-                    null
-                }
-            } else if (returnType.isList()) {
-                val param = returnType.typeArguments.first()
-                if (param.isLong()) {
-                    InsertionType.INSERT_ID_LIST
-                } else {
-                    null
-                }
-            } else if (returnType.isLong()) {
-                InsertionType.INSERT_SINGLE_ID
-            } else {
-                null
-            }
-        }
-    }
-
-    override fun createMethodBody(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<FieldSpec, Any>>,
-        dbField: FieldSpec,
-        scope: CodeGenScope
-    ) {
-        scope.builder().apply {
-            // TODO assert thread
-            // TODO collect results
-            addStatement("$N.beginTransaction()", dbField)
-            val needsResultVar = insertionType != InsertionType.INSERT_VOID &&
-                insertionType != InsertionType.INSERT_VOID_OBJECT &&
-                insertionType != InsertionType.INSERT_UNIT
-            val resultVar = if (needsResultVar) {
-                scope.getTmpVar("_result")
-            } else {
-                null
-            }
-
-            beginControlFlow("try").apply {
-                parameters.forEach { param ->
-                    val insertionAdapter = adapters[param.name]?.first
-                    if (needsResultVar) {
-                        // if it has more than 1 parameter, we would've already printed the error
-                        // so we don't care about re-declaring the variable here
-                        addStatement(
-                            "$T $L = $N.$L($L)",
-                            insertionType.returnTypeName, resultVar,
-                            insertionAdapter, insertionType.methodName,
-                            param.name
-                        )
-                    } else {
-                        addStatement(
-                            "$N.$L($L)", insertionAdapter, insertionType.methodName,
-                            param.name
-                        )
-                    }
-                }
-                addStatement("$N.setTransactionSuccessful()", dbField)
-                if (needsResultVar) {
-                    addStatement("return $L", resultVar)
-                } else if (insertionType == InsertionType.INSERT_VOID_OBJECT) {
-                    addStatement("return null")
-                } else if (insertionType == InsertionType.INSERT_UNIT) {
-                    addStatement("return $T.INSTANCE", KotlinTypeNames.UNIT)
-                }
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$N.endTransaction()", dbField)
-            }
-            endControlFlow()
-        }
-    }
-
-    enum class InsertionType(
-        // methodName matches EntityInsertionAdapter methods
-        val methodName: String,
-        val returnTypeName: TypeName
-    ) {
-        INSERT_VOID("insert", TypeName.VOID), // return void
-        INSERT_VOID_OBJECT("insert", TypeName.VOID), // return void
-        INSERT_UNIT("insert", KotlinTypeNames.UNIT), // return kotlin.Unit.INSTANCE
-        INSERT_SINGLE_ID("insertAndReturnId", TypeName.LONG), // return long
-        INSERT_ID_ARRAY(
-            "insertAndReturnIdsArray",
-            ArrayTypeName.of(TypeName.LONG)
-        ), // return long[]
-        INSERT_ID_ARRAY_BOX(
-            "insertAndReturnIdsArrayBox",
-            ArrayTypeName.of(TypeName.LONG.box())
-        ), // return Long[]
-        INSERT_ID_LIST(
-            "insertAndReturnIdsList", // return List<Long>
-            ParameterizedTypeName.get(List::class.typeName, TypeName.LONG.box())
-        ),
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
index 2eeba8b..9d06985 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
@@ -16,18 +16,204 @@
 
 package androidx.room.solver.shortcut.result
 
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.isArray
+import androidx.room.compiler.processing.isKotlinUnit
+import androidx.room.compiler.processing.isLong
+import androidx.room.compiler.processing.isVoid
+import androidx.room.compiler.processing.isVoidObject
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.T
+import androidx.room.ext.typeName
 import androidx.room.solver.CodeGenScope
 import androidx.room.vo.ShortcutQueryParameter
+import com.squareup.javapoet.ArrayTypeName
 import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
 
-/**
- * Abstract class for insert and update method adapters.
- */
-abstract class InsertOrUpsertMethodAdapter {
-    abstract fun createMethodBody(
+class InsertOrUpsertMethodAdapter private constructor(private val methodType: MethodType) {
+    companion object {
+        fun createInsert(
+            returnType: XType,
+            params: List<ShortcutQueryParameter>
+        ): InsertOrUpsertMethodAdapter? {
+            val methodReturnType = getReturnType(returnType)
+            if (methodReturnType != null && isReturnValid(methodReturnType, params)) {
+                val methodType = InsertMethodType(methodReturnType)
+                return InsertOrUpsertMethodAdapter(methodType)
+            }
+            return null
+        }
+
+        fun createUpsert(
+            returnType: XType,
+            params: List<ShortcutQueryParameter>
+        ): InsertOrUpsertMethodAdapter? {
+            val methodReturnType = getReturnType(returnType)
+            if (methodReturnType != null && isReturnValid(methodReturnType, params)) {
+                val methodType = UpsertMethodType(methodReturnType)
+                return InsertOrUpsertMethodAdapter(methodType)
+            }
+            return null
+        }
+
+        private fun isReturnValid(
+            returnType: ReturnType,
+            params: List<ShortcutQueryParameter>
+        ): Boolean {
+            if (params.isEmpty() || params.size > 1) {
+                return returnType == ReturnType.VOID ||
+                    returnType == ReturnType.UNIT
+            }
+            return if (params.first().isMultiple) {
+                returnType in MULTIPLE_ITEM_SET
+            } else {
+                returnType == ReturnType.VOID ||
+                    returnType == ReturnType.VOID_OBJECT ||
+                    returnType == ReturnType.UNIT ||
+                    returnType == ReturnType.SINGLE_ID
+            }
+        }
+
+        private val MULTIPLE_ITEM_SET by lazy {
+            setOf(
+                ReturnType.VOID,
+                ReturnType.VOID_OBJECT,
+                ReturnType.UNIT,
+                ReturnType.ID_ARRAY,
+                ReturnType.ID_ARRAY_BOX,
+                ReturnType.ID_LIST
+            )
+        }
+
+        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+        private fun getReturnType(returnType: XType): ReturnType? {
+            return if (returnType.isVoid()) {
+                ReturnType.VOID
+            } else if (returnType.isVoidObject()) {
+                ReturnType.VOID_OBJECT
+            } else if (returnType.isKotlinUnit()) {
+                ReturnType.UNIT
+            } else if (returnType.isArray()) {
+                val param = returnType.componentType
+                if (param.isLong()) {
+                    if (param.typeName == TypeName.LONG) {
+                        ReturnType.ID_ARRAY
+                    } else {
+                        ReturnType.ID_ARRAY_BOX
+                    }
+                } else {
+                    null
+                }
+            } else if (returnType.isList()) {
+                val param = returnType.typeArguments.first()
+                if (param.isLong()) {
+                    ReturnType.ID_LIST
+                } else {
+                    null
+                }
+            } else if (returnType.isLong()) {
+                ReturnType.SINGLE_ID
+            } else {
+                null
+            }
+        }
+    }
+
+    fun createMethodBody(
         parameters: List<ShortcutQueryParameter>,
         adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
-    )
+    ) {
+
+        scope.builder().apply {
+            val methodName = methodType.methodName
+            val methodReturnType = methodType.returnType
+
+            // TODO assert thread
+            // TODO collect results
+            addStatement("$N.beginTransaction()", dbField)
+            val needsResultVar = methodReturnType != ReturnType.VOID &&
+                methodReturnType != ReturnType.VOID_OBJECT &&
+                methodReturnType != ReturnType.UNIT
+            val resultVar = if (needsResultVar) {
+                scope.getTmpVar("_result")
+            } else {
+                null
+            }
+
+            beginControlFlow("try").apply {
+                parameters.forEach { param ->
+                    val upsertionAdapter = adapters[param.name]?.first
+                    if (needsResultVar) {
+                        // if it has more than 1 parameter, we would've already printed the error
+                        // so we don't care about re-declaring the variable here
+                        addStatement(
+                            "$T $L = $N.$L($L)",
+                            methodReturnType.returnTypeName, resultVar,
+                            upsertionAdapter, methodName,
+                            param.name
+                        )
+                    } else {
+                        addStatement(
+                            "$N.$L($L)", upsertionAdapter, methodName,
+                            param.name
+                        )
+                    }
+                }
+                addStatement("$N.setTransactionSuccessful()", dbField)
+                if (needsResultVar) {
+                    addStatement("return $L", resultVar)
+                } else if (methodReturnType == ReturnType.VOID_OBJECT) {
+                    addStatement("return null")
+                } else if (methodReturnType == ReturnType.UNIT) {
+                    addStatement("return $T.INSTANCE", KotlinTypeNames.UNIT)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }
+    }
+
+    sealed class MethodType(
+        val returnType: ReturnType
+    ) {
+        abstract val methodName: String
+    }
+
+    class InsertMethodType(returnType: ReturnType) : MethodType(returnType) {
+        override val methodName = "insert" + returnType.methodSuffix
+    }
+
+    class UpsertMethodType(returnType: ReturnType) : MethodType(returnType) {
+        override val methodName = "upsert" + returnType.methodSuffix
+    }
+
+    enum class ReturnType(
+        val methodSuffix: String,
+        val returnTypeName: TypeName
+    ) {
+        VOID("", TypeName.VOID), // return void
+        VOID_OBJECT("", TypeName.VOID), // return void
+        UNIT("", KotlinTypeNames.UNIT), // return kotlin.Unit.INSTANCE
+        SINGLE_ID("AndReturnId", TypeName.LONG), // return long
+        ID_ARRAY(
+            "AndReturnIdsArray",
+            ArrayTypeName.of(TypeName.LONG)
+        ), // return long[]
+        ID_ARRAY_BOX(
+            "AndReturnIdsArrayBox",
+            ArrayTypeName.of(TypeName.LONG.box())
+        ), // return Long[]
+        ID_LIST(
+            "AndReturnIdsList",
+            ParameterizedTypeName.get(List::class.typeName, TypeName.LONG.box())
+        ), // return List<Long>
+    }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/UpsertMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/UpsertMethodAdapter.kt
deleted file mode 100644
index e45ccea..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/UpsertMethodAdapter.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2022 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.shortcut.result
-
-import androidx.room.solver.CodeGenScope
-import androidx.room.vo.ShortcutQueryParameter
-import com.squareup.javapoet.FieldSpec
-
-class UpsertMethodAdapter : InsertOrUpsertMethodAdapter() {
-    override fun createMethodBody(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<FieldSpec, Any>>,
-        dbField: FieldSpec,
-        scope: CodeGenScope
-    ) {
-    }
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
index 1df9af3..ae942bd 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.processing.XVariableElement
 
 /**
- * Parameters used in DAO methods that are annotated with Insert, Delete, Update. and Upsert
+ * Parameters used in DAO methods that are annotated with Insert, Delete, Update, and Upsert.
  */
 data class ShortcutQueryParameter(
     val element: XVariableElement,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt
index 76cc0fd..7fcdd72 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt
@@ -26,4 +26,4 @@
     returnType: XType,
     parameters: List<ShortcutQueryParameter>,
     methodBinder: InsertOrUpsertMethodBinder
-    ) : InsertOrUpsertShortcutMethod(element, entities, returnType, parameters, methodBinder)
\ No newline at end of file
+        ) : InsertOrUpsertShortcutMethod(element, entities, returnType, parameters, methodBinder)
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
index 93b83a8..a1aee1e 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -25,6 +25,9 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.ext.RxJava3TypeNames
+import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.testing.context
 import androidx.room.vo.InsertOrUpsertShortcutMethod
 import com.squareup.javapoet.ClassName
@@ -373,7 +376,178 @@
         }
     }
 
-    // TODO: Add in the return type tests when upsertionMethodAdapter is implemented
+    @Test
+    fun invalidReturnType() {
+        listOf(
+            "int",
+            "${RxJava2TypeNames.SINGLE}<Int>",
+            "${RxJava2TypeNames.MAYBE}<Int>",
+            "${RxJava2TypeNames.SINGLE}<String>",
+            "${RxJava2TypeNames.MAYBE}<String>",
+            "${RxJava2TypeNames.SINGLE}<User>",
+            "${RxJava2TypeNames.MAYBE}<User>"
+        ).forEach { type ->
+            singleInsertUpsertShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public $type foo(User user);
+                """
+            ) { insertionUpsertion, invocation ->
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertionUpsertion.methodBinder?.adapter).isNull()
+
+                invocation.assertCompilationResult {
+                    hasErrorContaining(noAdapter())
+                }
+            }
+        }
+    }
+
+    @Test
+    fun mismatchedReturnType() {
+        listOf(
+            "long[]",
+            "Long[]",
+            "List<Long>",
+            "${RxJava2TypeNames.SINGLE}<List<Long>>",
+            "${RxJava2TypeNames.MAYBE}<List<Long>>"
+        ).forEach { type ->
+            singleInsertUpsertShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public $type foo(User user);
+                """
+            ) { insertionUpsertion, invocation ->
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertionUpsertion.methodBinder?.adapter).isNull()
+
+                invocation.assertCompilationResult {
+                    hasErrorContaining(noAdapter())
+                }
+            }
+        }
+    }
+
+    @Test
+    fun mismatchedReturnType2() {
+        listOf(
+            "long",
+            "Long",
+            "${RxJava2TypeNames.SINGLE}<Long>",
+            "${RxJava2TypeNames.MAYBE}<Long>"
+        ).forEach { type ->
+            singleInsertUpsertShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public $type foo(User... user);
+                """
+            ) { insertionUpsertion, invocation ->
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertionUpsertion.methodBinder?.adapter).isNull()
+
+                invocation.assertCompilationResult {
+                    hasErrorContaining(noAdapter())
+                }
+            }
+        }
+    }
+
+    @Test
+    fun mismatchedReturnType3() {
+        listOf(
+            "long",
+            "Long",
+            "${RxJava2TypeNames.SINGLE}<Long>",
+            "${RxJava2TypeNames.MAYBE}<Long>"
+        ).forEach { type ->
+            singleInsertUpsertShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public $type foo(User user1, User user2);
+                """
+            ) { insertionUpsertion, invocation ->
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertionUpsertion.methodBinder?.adapter).isNull()
+
+                invocation.assertCompilationResult {
+                    hasErrorContaining(noAdapter())
+                }
+            }
+        }
+    }
+
+    @Test
+    fun validReturnTypes() {
+        listOf(
+            Pair("void", InsertOrUpsertMethodAdapter.ReturnType.VOID),
+            Pair("long", InsertOrUpsertMethodAdapter.ReturnType.SINGLE_ID),
+            Pair("long[]", InsertOrUpsertMethodAdapter.ReturnType.ID_ARRAY),
+            Pair("Long[]", InsertOrUpsertMethodAdapter.ReturnType.ID_ARRAY_BOX),
+            Pair("List<Long>", InsertOrUpsertMethodAdapter.ReturnType.ID_LIST),
+            Pair(
+                RxJava2TypeNames.COMPLETABLE,
+                InsertOrUpsertMethodAdapter.ReturnType.VOID_OBJECT
+            ),
+            Pair(
+                "${RxJava2TypeNames.SINGLE}<Long>",
+                InsertOrUpsertMethodAdapter.ReturnType.SINGLE_ID
+            ),
+            Pair(
+                "${RxJava2TypeNames.SINGLE}<List<Long>>",
+                InsertOrUpsertMethodAdapter.ReturnType.ID_LIST
+            ),
+            Pair(
+                "${RxJava2TypeNames.MAYBE}<Long>",
+                InsertOrUpsertMethodAdapter.ReturnType.SINGLE_ID
+            ),
+            Pair(
+                "${RxJava2TypeNames.MAYBE}<List<Long>>",
+                InsertOrUpsertMethodAdapter.ReturnType.ID_LIST
+            ),
+            Pair(
+                RxJava3TypeNames.COMPLETABLE,
+                InsertOrUpsertMethodAdapter.ReturnType.VOID_OBJECT
+            ),
+            Pair(
+                "${RxJava3TypeNames.SINGLE}<Long>",
+                InsertOrUpsertMethodAdapter.ReturnType.SINGLE_ID
+            ),
+            Pair(
+                "${RxJava3TypeNames.SINGLE}<List<Long>>",
+                InsertOrUpsertMethodAdapter.ReturnType.ID_LIST
+            ),
+            Pair(
+                "${RxJava3TypeNames.MAYBE}<Long>",
+                InsertOrUpsertMethodAdapter.ReturnType.SINGLE_ID
+            ),
+            Pair(
+                "${RxJava3TypeNames.MAYBE}<List<Long>>",
+                InsertOrUpsertMethodAdapter.ReturnType.ID_LIST
+            )
+        ).forEach { pair ->
+            val dots = if (pair.second in setOf(
+                    InsertOrUpsertMethodAdapter.ReturnType.ID_LIST,
+                    InsertOrUpsertMethodAdapter.ReturnType.ID_ARRAY,
+                    InsertOrUpsertMethodAdapter.ReturnType.ID_ARRAY_BOX
+                )
+            ) {
+                "..."
+            } else {
+                ""
+            }
+            singleInsertUpsertShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public ${pair.first} foo(User$dots user);
+                """
+            ) { insertionUpsertion, _ ->
+                assertThat(insertionUpsertion.methodBinder?.adapter).isNotNull()
+            }
+        }
+    }
+
+    abstract fun noAdapter(): String
+
     @Test
     fun targetEntitySingle() {
         val usernameSource = Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
index 7891b39..a968514 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -16,354 +16,49 @@
 
 package androidx.room.processor
 
-import COMMON
-import androidx.room.Dao
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy
-import androidx.room.compiler.processing.XTypeElement
-import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
-import androidx.room.ext.CommonTypeNames
-import androidx.room.ext.GuavaUtilConcurrentTypeNames
-import androidx.room.ext.KotlinTypeNames
-import androidx.room.ext.LifecyclesTypeNames
-import androidx.room.ext.ReactiveStreamsTypeNames
-import androidx.room.ext.RxJava2TypeNames
-import androidx.room.ext.RxJava3TypeNames
-import androidx.room.solver.shortcut.result.InsertMethodAdapter
-import androidx.room.testing.context
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XType
+import androidx.room.processor.ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
 import androidx.room.vo.InsertionMethod
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 @RunWith(JUnit4::class)
-class InsertionMethodProcessorTest {
-    companion object {
-        const val DAO_PREFIX = """
-                package foo.bar;
-                import androidx.room.*;
-                import java.util.*;
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_PREFIX_KT = """
-                package foo.bar
-                import androidx.room.*
-                import java.util.*
-                import io.reactivex.*         
-                io.reactivex.rxjava3.core.*
-                androidx.lifecycle.*
-                com.google.common.util.concurrent.*
-                org.reactivestreams.*
-                kotlinx.coroutines.flow.*
-            
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_SUFFIX = "}"
-        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
-        val USERNAME_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Username")
-        val BOOK_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Book")
+class InsertionMethodProcessorTest :
+    InsertOrUpsertShortcutMethodProcessorTest<InsertionMethod>(Insert::class) {
+    override fun noParamsError(): String = INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
+
+    override fun missingPrimaryKey(partialEntityName: String, primaryKeyName: List<String>):
+    String {
+        return ProcessorErrors.missingPrimaryKeysInPartialEntityForInsert(
+            partialEntityName,
+            primaryKeyName
+        )
     }
 
-    @Test
-    fun readNoParams() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void foo();
-                """
-        ) { insertion, invocation ->
-            assertThat(insertion.element.jvmName, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(0))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-            assertThat(insertion.entities.size, `is`(0))
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
-                )
-            }
-        }
-    }
-
-    @Test
-    fun insertSingle() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public long foo(User user);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName, `is`(USER_TYPE_NAME))
-            assertThat(param.pojoType?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["user"]?.isPartialEntity, `is`(false))
-            assertThat(
-                insertion.entities["user"]?.pojo?.typeName,
-                `is`(ClassName.get("foo.bar", "User") as TypeName)
-            )
-            assertThat(insertion.returnType.typeName, `is`(TypeName.LONG))
-        }
-    }
-
-    @Test
-    fun insertNotAnEntity() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void foo(NotAnEntity notValid);
-                """
-        ) { insertion, invocation ->
-            assertThat(insertion.element.jvmName, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(1))
-            assertThat(insertion.entities.size, `is`(0))
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
-                )
-            }
-        }
-    }
-
-    @Test
-    fun insertTwo() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void foo(User u1, User u2);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("foo"))
-
-            assertThat(insertion.parameters.size, `is`(2))
-            insertion.parameters.forEach {
-                assertThat(it.type.typeName, `is`(USER_TYPE_NAME))
-                assertThat(it.pojoType?.typeName, `is`(USER_TYPE_NAME))
-            }
-            assertThat(insertion.entities.size, `is`(2))
-            assertThat(insertion.entities["u1"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["u2"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "u2")))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-        }
-    }
-
-    @Test
-    fun insertList() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public List<Long> insertUsers(List<User> users);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(
-                param.type.typeName,
-                `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "List"),
-                        USER_TYPE_NAME
-                    ) as TypeName
-                )
-            )
-            assertThat(param.pojoType?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(
-                insertion.returnType.typeName,
-                `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "List"),
-                        ClassName.get("java.lang", "Long")
-                    ) as TypeName
-                )
-            )
-        }
-    }
-
-    @Test
-    fun insertArray() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void insertUsers(User[] users);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(
-                param.type.typeName,
-                `is`(
-                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName
-                )
-            )
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-        }
-    }
-
-    @Test
-    fun insertSet() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void insertUsers(Set<User> users);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(
-                param.type.typeName,
-                `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "Set"),
-                        COMMON.USER_TYPE_NAME
-                    ) as TypeName
-                )
-            )
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-        }
-    }
-
-    @Test
-    fun insertQueue() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void insertUsers(Queue<User> users);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(
-                param.type.typeName,
-                `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "Queue"),
-                        USER_TYPE_NAME
-                    ) as TypeName
-                )
-            )
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-        }
-    }
-
-    @Test
-    fun insertIterable() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void insert(Iterable<User> users);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("insert"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(
-                param.type.typeName,
-                `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.lang", "Iterable"),
-                        USER_TYPE_NAME
-                    ) as TypeName
-                )
-            )
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-        }
-    }
-
-    @Test
-    fun insertCustomCollection() {
-        singleInsertMethod(
-            """
-                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
-                @Insert
-                abstract public void insert(MyList<String, User> users);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("insert"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(
-                param.type.typeName,
-                `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("foo.bar", "MyClass.MyList"),
-                        CommonTypeNames.STRING, USER_TYPE_NAME
-                    ) as TypeName
-                )
-            )
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-        }
-    }
-
-    @Test
-    fun insertDifferentTypes() {
-        singleInsertMethod(
-            """
-                @Insert
-                abstract public void foo(User u1, Book b1);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.parameters.size, `is`(2))
-            assertThat(
-                insertion.parameters[0].type.typeName.toString(),
-                `is`("foo.bar.User")
-            )
-            assertThat(
-                insertion.parameters[1].type.typeName.toString(),
-                `is`("foo.bar.Book")
-            )
-            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "b1")))
-            assertThat(insertion.returnType.typeName, `is`(TypeName.VOID))
-            assertThat(insertion.entities.size, `is`(2))
-            assertThat(insertion.entities["u1"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["b1"]?.pojo?.typeName, `is`(BOOK_TYPE_NAME))
-        }
-    }
+    override fun noAdapter(): String = CANNOT_FIND_INSERT_RESULT_ADAPTER
 
     @Test
     fun onConflict_Default() {
-        singleInsertMethod(
+        singleInsertUpsertShortcutMethod(
             """
                 @Insert
                 abstract public void foo(User user);
                 """
         ) { insertion, _ ->
-            assertThat(insertion.onConflict, `is`(OnConflictStrategy.ABORT))
+            assertThat(insertion.onConflict).isEqualTo(OnConflictStrategy.ABORT)
         }
     }
 
     @Test
     fun onConflict_Invalid() {
-        singleInsertMethod(
+        singleInsertUpsertShortcutMethod(
             """
                 @Insert(onConflict = -1)
                 abstract public void foo(User user);
@@ -387,672 +82,22 @@
             Pair("FAIL", 4),
             Pair("IGNORE", 5)
         ).forEach { pair ->
-            singleInsertMethod(
+            singleInsertUpsertShortcutMethod(
                 """
                 @Insert(onConflict=OnConflictStrategy.${pair.first})
                 abstract public void foo(User user);
                 """
             ) { insertion, _ ->
-                assertThat(insertion.onConflict, `is`(pair.second))
+                assertThat(insertion.onConflict).isEqualTo(pair.second)
             }
         }
     }
 
-    @Test
-    fun invalidReturnType() {
-        listOf(
-            "int",
-            "${RxJava2TypeNames.SINGLE}<Int>",
-            "${RxJava2TypeNames.MAYBE}<Int>",
-            "${RxJava2TypeNames.SINGLE}<String>",
-            "${RxJava2TypeNames.MAYBE}<String>",
-            "${RxJava2TypeNames.SINGLE}<User>",
-            "${RxJava2TypeNames.MAYBE}<User>"
-        ).forEach { type ->
-            singleInsertMethod(
-                """
-                @Insert
-                abstract public $type foo(User user);
-                """
-            ) { insertion, invocation ->
-                // TODO: (b/240491383) remove methodBinder nullability
-                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
-                invocation.assertCompilationResult {
-                    hasErrorContaining(
-                        ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
-                    )
-                }
-            }
-        }
-    }
-
-    @Test
-    fun mismatchedReturnType() {
-        listOf(
-            "long[]",
-            "Long[]",
-            "List<Long>",
-            "${RxJava2TypeNames.SINGLE}<List<Long>>",
-            "${RxJava2TypeNames.MAYBE}<List<Long>>"
-        ).forEach { type ->
-            singleInsertMethod(
-                """
-                @Insert
-                abstract public $type foo(User user);
-                """
-            ) { insertion, invocation ->
-                // TODO: (b/240491383) remove methodBinder nullability
-                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
-                invocation.assertCompilationResult {
-                    hasErrorContaining(
-                        ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
-                    )
-                }
-            }
-        }
-    }
-
-    @Test
-    fun mismatchedReturnType2() {
-        listOf(
-            "long",
-            "Long",
-            "${RxJava2TypeNames.SINGLE}<Long>",
-            "${RxJava2TypeNames.MAYBE}<Long>"
-        ).forEach { type ->
-            singleInsertMethod(
-                """
-                @Insert
-                abstract public $type foo(User... user);
-                """
-            ) { insertion, invocation ->
-                // TODO: (b/240491383) remove methodBinder nullability
-                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
-                invocation.assertCompilationResult {
-                    hasErrorContaining(
-                        ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
-                    )
-                }
-            }
-        }
-    }
-
-    @Test
-    fun mismatchedReturnType3() {
-        listOf(
-            "long",
-            "Long",
-            "${RxJava2TypeNames.SINGLE}<Long>",
-            "${RxJava2TypeNames.MAYBE}<Long>"
-        ).forEach { type ->
-            singleInsertMethod(
-                """
-                @Insert
-                abstract public $type foo(User user1, User user2);
-                """
-            ) { insertion, invocation ->
-                // TODO: (b/240491383) remove methodBinder nullability
-                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
-                invocation.assertCompilationResult {
-                    hasErrorContaining(
-                        ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
-                    )
-                }
-            }
-        }
-    }
-
-    @Test
-    fun validReturnTypes() {
-        listOf(
-            Pair("void", InsertMethodAdapter.InsertionType.INSERT_VOID),
-            Pair("long", InsertMethodAdapter.InsertionType.INSERT_SINGLE_ID),
-            Pair("long[]", InsertMethodAdapter.InsertionType.INSERT_ID_ARRAY),
-            Pair("Long[]", InsertMethodAdapter.InsertionType.INSERT_ID_ARRAY_BOX),
-            Pair("List<Long>", InsertMethodAdapter.InsertionType.INSERT_ID_LIST),
-            Pair(
-                RxJava2TypeNames.COMPLETABLE,
-                InsertMethodAdapter.InsertionType.INSERT_VOID_OBJECT
-            ),
-            Pair(
-                "${RxJava2TypeNames.SINGLE}<Long>",
-                InsertMethodAdapter.InsertionType.INSERT_SINGLE_ID
-            ),
-            Pair(
-                "${RxJava2TypeNames.SINGLE}<List<Long>>",
-                InsertMethodAdapter.InsertionType.INSERT_ID_LIST
-            ),
-            Pair(
-                "${RxJava2TypeNames.MAYBE}<Long>",
-                InsertMethodAdapter.InsertionType.INSERT_SINGLE_ID
-            ),
-            Pair(
-                "${RxJava2TypeNames.MAYBE}<List<Long>>",
-                InsertMethodAdapter.InsertionType.INSERT_ID_LIST
-            ),
-            Pair(
-                RxJava3TypeNames.COMPLETABLE,
-                InsertMethodAdapter.InsertionType.INSERT_VOID_OBJECT
-            ),
-            Pair(
-                "${RxJava3TypeNames.SINGLE}<Long>",
-                InsertMethodAdapter.InsertionType.INSERT_SINGLE_ID
-            ),
-            Pair(
-                "${RxJava3TypeNames.SINGLE}<List<Long>>",
-                InsertMethodAdapter.InsertionType.INSERT_ID_LIST
-            ),
-            Pair(
-                "${RxJava3TypeNames.MAYBE}<Long>",
-                InsertMethodAdapter.InsertionType.INSERT_SINGLE_ID
-            ),
-            Pair(
-                "${RxJava3TypeNames.MAYBE}<List<Long>>",
-                InsertMethodAdapter.InsertionType.INSERT_ID_LIST
-            )
-        ).forEach { pair ->
-            val dots = if (pair.second in setOf(
-                    InsertMethodAdapter.InsertionType.INSERT_ID_LIST,
-                    InsertMethodAdapter.InsertionType.INSERT_ID_ARRAY,
-                    InsertMethodAdapter.InsertionType.INSERT_ID_ARRAY_BOX
-                )
-            ) {
-                "..."
-            } else {
-                ""
-            }
-            singleInsertMethod(
-                """
-                @Insert
-                abstract public ${pair.first} foo(User$dots user);
-                """
-            ) { insertion, _ ->
-                assertThat(insertion.methodBinder?.adapter, `is`(notNullValue()))
-            }
-        }
-    }
-
-    @Test
-    fun targetEntitySingle() {
-        val usernameSource = Source.java(
-            "foo.bar.Username",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Username {
-                int uid;
-                String name;
-                @ColumnInfo(name = "ageColumn")
-                int age;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public long foo(Username username);
-            """,
-            additionalSources = listOf(usernameSource)
-        ) { insertion, _ ->
-            assertThat(insertion.element.jvmName, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName, `is`(USERNAME_TYPE_NAME))
-            assertThat(param.pojoType?.typeName, `is`(USERNAME_TYPE_NAME))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["username"]?.isPartialEntity, `is`(true))
-            assertThat(insertion.entities["username"]?.entityTypeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["username"]?.pojo?.typeName, `is`(USERNAME_TYPE_NAME))
-        }
-    }
-
-    @Test
-    fun targetEntitySameAsPojo() {
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public long foo(User user);
-            """
-        ) { _, _ ->
-        }
-    }
-
-    @Test
-    fun targetEntityTwo() {
-        val usernameSource = Source.java(
-            "foo.bar.Username",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Username {
-                int uid;
-                String name;
-                @ColumnInfo(name = "ageColumn")
-                int age;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public void foo(Username usernameA, Username usernameB);
-            """,
-            additionalSources = listOf(usernameSource)
-        ) { _, _ ->
-        }
-    }
-
-    @Test
-    fun targetEntityMissingRequiredColumn() {
-        val usernameSource = Source.java(
-            "foo.bar.Username",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Username {
-                int uid;
-                String name;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public void foo(Username username);
-            """,
-            additionalSources = listOf(usernameSource)
-        ) { _, invocation ->
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.missingRequiredColumnsInPartialEntity(
-                        partialEntityName = USERNAME_TYPE_NAME.toString(),
-                        missingColumnNames = listOf("ageColumn")
-                    )
-                )
-            }
-        }
-    }
-
-    @Test
-    fun targetEntityColumnDefaultValue() {
-        val petNameSource = Source.java(
-            "foo.bar.PetName",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class PetName {
-                @ColumnInfo(name = "name")
-                String string;
-            }
-            """
-        )
-        val petSource = Source.java(
-            "foo.bar.Pet",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            @Entity
-            public class Pet {
-                @PrimaryKey(autoGenerate = true)
-                int petId;
-                String name;
-                @ColumnInfo(defaultValue = "0")
-                int age;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = Pet.class)
-                abstract public long foo(PetName petName);
-            """,
-            additionalSources = listOf(petNameSource, petSource)
-        ) { _, _ ->
-        }
-    }
-
-    @Test
-    fun targetEntityMissingPrimaryKey() {
-        val petNameSource = Source.java(
-            "foo.bar.PetName",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class PetName {
-                @ColumnInfo(name = "name")
-                String string;
-            }
-            """
-        )
-        val petSource = Source.java(
-            "foo.bar.Pet",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            @Entity
-            public class Pet {
-                @PrimaryKey
-                int petId;
-                String name;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = Pet.class)
-                abstract public long foo(PetName petName);
-            """,
-            additionalSources = listOf(petNameSource, petSource)
-        ) { _, invocation ->
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.missingPrimaryKeysInPartialEntityForInsert(
-                        partialEntityName = "foo.bar.PetName",
-                        primaryKeyNames = listOf("petId")
-                    )
-                )
-            }
-        }
-    }
-
-    @Test
-    fun targetEntityAutoGeneratedPrimaryKey() {
-        val petNameSource = Source.java(
-            "foo.bar.PetName",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class PetName {
-                @ColumnInfo(name = "name")
-                String string;
-            }
-            """
-        )
-        val petSource = Source.java(
-            "foo.bar.Pet",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            @Entity
-            public class Pet {
-                @PrimaryKey(autoGenerate = true)
-                int petId;
-                String name;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = Pet.class)
-                abstract public long foo(PetName petName);
-            """,
-            additionalSources = listOf(petNameSource, petSource)
-        ) { _, _ ->
-        }
-    }
-
-    @Test
-    fun targetEntityExtraColumn() {
-        val usernameSource = Source.java(
-            "foo.bar.Username",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Username {
-                int uid;
-                String name;
-                long extraField;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public long foo(Username username);
-            """,
-            additionalSources = listOf(usernameSource)
-        ) { _, invocation ->
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.cannotFindAsEntityField("foo.bar.User")
-                )
-            }
-        }
-    }
-
-    @Test
-    fun targetEntityExtraColumnIgnored() {
-        val usernameSource = Source.java(
-            "foo.bar.Username",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Username {
-                int uid;
-                String name;
-                @ColumnInfo(name = "ageColumn")
-                int age;
-                @Ignore
-                long extraField;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public long foo(Username username);
-            """,
-            additionalSources = listOf(usernameSource)
-        ) { _, _ ->
-        }
-    }
-
-    @Test
-    fun targetEntityWithEmbedded() {
-        val usernameSource = Source.java(
-            "foo.bar.Username",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Username {
-                int uid;
-                @Embedded
-                Fullname name;
-                @ColumnInfo(name = "ageColumn")
-                int age;
-            }
-            """
-        )
-        val fullnameSource = Source.java(
-            "foo.bar.Fullname",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            public class Fullname {
-                @ColumnInfo(name = "name")
-                String firstName;
-                String lastName;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public long foo(Username username);
-            """,
-            additionalSources = listOf(usernameSource, fullnameSource)
-        ) { _, _ ->
-        }
-    }
-
-    @Test
-    fun targetEntityWithRelation() {
-        val userPetsSource = Source.java(
-            "foo.bar.UserPets",
-            """
-            package foo.bar;
-            import androidx.room.*;
-            import java.util.List;
-
-            public class UserPets {
-                int uid;
-                @Relation(parentColumn = "uid", entityColumn = "ownerId")
-                List<Pet> pets;
-            }
-            """
-        )
-        val petSource = Source.java(
-            "foo.bar.Pet",
-            """
-            package foo.bar;
-            import androidx.room.*;
-
-            @Entity
-            public class Pet {
-                @PrimaryKey
-                int petId;
-                int ownerId;
-            }
-            """
-        )
-        singleInsertMethod(
-            """
-                @Insert(entity = User.class)
-                abstract public long foo(UserPets userPets);
-                """,
-            additionalSources = listOf(userPetsSource, petSource)
-        ) { _, invocation ->
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.INVALID_RELATION_IN_PARTIAL_ENTITY
-                )
-            }
-        }
-    }
-
-    @Test
-    fun suspendReturnsDeferredType() {
-        listOf(
-            "${RxJava2TypeNames.FLOWABLE}<Int>",
-            "${RxJava2TypeNames.OBSERVABLE}<Int>",
-            "${RxJava2TypeNames.MAYBE}<Int>",
-            "${RxJava2TypeNames.SINGLE}<Int>",
-            "${RxJava2TypeNames.COMPLETABLE}",
-            "${RxJava3TypeNames.FLOWABLE}<Int>",
-            "${RxJava3TypeNames.OBSERVABLE}<Int>",
-            "${RxJava3TypeNames.MAYBE}<Int>",
-            "${RxJava3TypeNames.SINGLE}<Int>",
-            "${RxJava3TypeNames.COMPLETABLE}",
-            "${LifecyclesTypeNames.LIVE_DATA}<Int>",
-            "${LifecyclesTypeNames.COMPUTABLE_LIVE_DATA}<Int>",
-            "${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Int>",
-            "${ReactiveStreamsTypeNames.PUBLISHER}<Int>",
-            "${KotlinTypeNames.FLOW}<Int>"
-        ).forEach { type ->
-            singleInsertMethodKotlin(
-                """
-                @Insert
-                abstract suspend fun foo(user: User): $type
-                """
-            ) { _, invocation ->
-                invocation.assertCompilationResult {
-                    val rawTypeName = type.substringBefore("<")
-                    hasErrorContaining(ProcessorErrors.suspendReturnsDeferredType(rawTypeName))
-                }
-            }
-        }
-    }
-
-    fun singleInsertMethod(
-        vararg input: String,
-        additionalSources: List<Source> = emptyList(),
-        handler: (InsertionMethod, XTestInvocation) -> Unit
-    ) {
-        val inputSource = Source.java(
-            "foo.bar.MyClass",
-            DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
-        )
-        val commonSources = listOf(
-            COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
-            COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX3_COMPLETABLE,
-            COMMON.RX3_MAYBE, COMMON.RX3_SINGLE
-        )
-
-        runProcessorTest(
-            sources = commonSources + additionalSources + inputSource
-        ) { invocation ->
-            val (owner, methods) = invocation.roundEnv
-                .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
-                .filterIsInstance<XTypeElement>()
-                .map {
-                    Pair(
-                        it,
-                        it.getAllMethods().filter {
-                            it.hasAnnotation(Insert::class)
-                        }.toList()
-                    )
-                }.first { it.second.isNotEmpty() }
-            val processor = InsertionMethodProcessor(
-                baseContext = invocation.context,
-                containing = owner.type,
-                executableElement = methods.first()
-            )
-            val processed = processor.process()
-            handler(processed, invocation)
-        }
-    }
-
-    fun singleInsertMethodKotlin(
-        vararg input: String,
-        additionalSources: List<Source> = emptyList(),
-        handler: (InsertionMethod, XTestInvocation) -> Unit
-    ) {
-        val inputSource = Source.kotlin(
-            "MyClass.kt",
-            DAO_PREFIX_KT + input.joinToString("\n") + DAO_SUFFIX
-        )
-        val commonSources = listOf(
-            COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
-            COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX2_FLOWABLE, COMMON.RX2_OBSERVABLE,
-            COMMON.RX3_COMPLETABLE, COMMON.RX3_MAYBE, COMMON.RX3_SINGLE, COMMON.RX3_FLOWABLE,
-            COMMON.RX3_OBSERVABLE, COMMON.LISTENABLE_FUTURE, COMMON.LIVE_DATA,
-            COMMON.COMPUTABLE_LIVE_DATA, COMMON.PUBLISHER, COMMON.FLOW, COMMON.GUAVA_ROOM
-        )
-
-        runProcessorTest(
-            sources = commonSources + additionalSources + inputSource
-        ) { invocation ->
-            val (owner, methods) = invocation.roundEnv
-                .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
-                .filterIsInstance<XTypeElement>()
-                .map {
-                    Pair(
-                        it,
-                        it.getAllMethods().filter {
-                            it.hasAnnotation(Insert::class)
-                        }.toList()
-                    )
-                }.first { it.second.isNotEmpty() }
-            val processor = InsertionMethodProcessor(
-                baseContext = invocation.context,
-                containing = owner.type,
-                executableElement = methods.first()
-            )
-            val processed = processor.process()
-            handler(processed, invocation)
-        }
+    override fun process(
+        baseContext: Context,
+        containing: XType,
+        executableElement: XMethodElement
+    ): InsertionMethod {
+        return InsertionMethodProcessor(baseContext, containing, executableElement).process()
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/NewInsertionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/NewInsertionMethodProcessorTest.kt
deleted file mode 100644
index 2f1b27f..0000000
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/NewInsertionMethodProcessorTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 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.processor
-
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.compiler.processing.XMethodElement
-import androidx.room.compiler.processing.XType
-import androidx.room.processor.ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
-import androidx.room.vo.InsertionMethod
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-// TODO: Rename NewInsertionMethodProcessorTest.kt to InsertionMethodProcessorTest (remove old file)
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(JUnit4::class)
-class NewInsertionMethodProcessorTest :
-    InsertOrUpsertShortcutMethodProcessorTest<InsertionMethod>(Insert::class) {
-    override fun noParamsError(): String = INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
-
-    override fun missingPrimaryKey(partialEntityName: String, primaryKeyName: List<String>):
-    String {
-        return ProcessorErrors.missingPrimaryKeysInPartialEntityForInsert(
-            partialEntityName,
-            primaryKeyName
-        )
-    }
-
-    @Test
-    fun onConflict_Default() {
-        singleInsertUpsertShortcutMethod(
-            """
-                @Insert
-                abstract public void foo(User user);
-                """
-        ) { insertion, _ ->
-            assertThat(insertion.onConflict).isEqualTo(OnConflictStrategy.ABORT)
-        }
-    }
-
-    @Test
-    fun onConflict_Invalid() {
-        singleInsertUpsertShortcutMethod(
-            """
-                @Insert(onConflict = -1)
-                abstract public void foo(User user);
-                """
-        ) { _, invocation ->
-            invocation.assertCompilationResult {
-                hasErrorContaining(
-                    ProcessorErrors.INVALID_ON_CONFLICT_VALUE
-                )
-            }
-        }
-    }
-
-    @Test
-    fun onConflict_EachValue() {
-        listOf(
-            Pair("NONE", 0),
-            Pair("REPLACE", 1),
-            Pair("ROLLBACK", 2),
-            Pair("ABORT", 3),
-            Pair("FAIL", 4),
-            Pair("IGNORE", 5)
-        ).forEach { pair ->
-            singleInsertUpsertShortcutMethod(
-                """
-                @Insert(onConflict=OnConflictStrategy.${pair.first})
-                abstract public void foo(User user);
-                """
-            ) { insertion, _ ->
-                assertThat(insertion.onConflict).isEqualTo(pair.second)
-            }
-        }
-    }
-
-    override fun process(
-        baseContext: Context,
-        containing: XType,
-        executableElement: XMethodElement
-    ): InsertionMethod {
-        return InsertionMethodProcessor(baseContext, containing, executableElement).process()
-    }
-}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt
index 4370d53..26ecca7 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt
@@ -20,6 +20,7 @@
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.processor.ProcessorErrors.UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_UPSERT_RESULT_ADAPTER
 import androidx.room.vo.UpsertionMethod
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -38,6 +39,8 @@
         )
     }
 
+    override fun noAdapter(): String = CANNOT_FIND_UPSERT_RESULT_ADAPTER
+
     override fun process(
         baseContext: Context,
         containing: XType,