Filling in upsert processor; merged insert and UpsertMethod and binder

To reduce code and increase reusability: created InsertOrUpsertShortcutMethod.kt that holds InsertionMethod and UpsertionMethod; combined the Insert and Upsert binders into InsertOrUpsertMethodBinder.kt; created abstract class that holds Insert and Upsert's adapters; created an InsertionUpsertionMethodProcessorTest.kt that will run the shared tests for both insert and upsert.

Bug: 117855270
Test: InsertionUpsertionMethodProcessorTest.kt
Change-Id: I8fa1211da1882a0ae2b1fe9720886e2d649c82aa
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
index 104295d..f4f41f4 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
@@ -45,7 +45,7 @@
     companion object {
         val PROCESSED_ANNOTATIONS = listOf(
             Insert::class, Delete::class, Query::class,
-            Update::class, RawQuery::class
+            Update::class, Upsert::class, RawQuery::class
         )
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 8fcba1b..ffad3f1 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -371,7 +371,7 @@
             }
         }
         daoMethods.forEach { daoMethod ->
-            daoMethod.dao.shortcutMethods.forEach { method ->
+            daoMethod.dao.deleteOrUpdateShortcutMethods.forEach { method ->
                 method.entities.forEach {
                     check(method.element, daoMethod.dao, it.value.entityTypeName)
                 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index c500f7e..31bd866 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -38,8 +38,7 @@
 import androidx.room.solver.shortcut.binder.CallableInsertMethodBinder.Companion.createInsertBinder
 import androidx.room.solver.shortcut.binder.CallableUpsertMethodBinder.Companion.createUpsertBinder
 import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.solver.transaction.binder.CoroutineTransactionMethodBinder
 import androidx.room.solver.transaction.binder.InstantTransactionMethodBinder
 import androidx.room.solver.transaction.binder.TransactionMethodBinder
@@ -89,14 +88,14 @@
     abstract fun findInsertMethodBinder(
         returnType: XType,
         params: List<ShortcutQueryParameter>
-    ): InsertMethodBinder
+    ): InsertOrUpsertMethodBinder
 
     abstract fun findDeleteOrUpdateMethodBinder(returnType: XType): DeleteOrUpdateMethodBinder
 
     abstract fun findUpsertMethodBinder(
         returnType: XType,
         params: List<ShortcutQueryParameter>
-    ): UpsertMethodBinder
+    ): InsertOrUpsertMethodBinder
 
     abstract fun findTransactionMethodBinder(
         callType: TransactionMethod.CallType
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 0b9d0e4..7c08dd0 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
@@ -18,6 +18,7 @@
 
 import androidx.room.Delete
 import androidx.room.Insert
+import androidx.room.Upsert
 import androidx.room.Query
 import androidx.room.RawQuery
 import androidx.room.RewriteQueriesToDropUnusedColumns
@@ -42,6 +43,7 @@
     val MISSING_INSERT_ANNOTATION = "Insertion methods must be annotated with ${Insert::class.java}"
     val MISSING_DELETE_ANNOTATION = "Deletion methods must be annotated with ${Delete::class.java}"
     val MISSING_UPDATE_ANNOTATION = "Update methods must be annotated with ${Update::class.java}"
+    val MISSING_UPSERT_ANNOTATION = "Upsertion methods must be annotated with ${Upsert::class.java}"
     val MISSING_RAWQUERY_ANNOTATION = "RawQuery methods must be annotated with" +
         " ${RawQuery::class.java}"
     val INVALID_ON_CONFLICT_VALUE = "On conflict value must be one of @OnConflictStrategy values."
@@ -57,6 +59,8 @@
         " methods. It must be bound to a type through base Dao class."
     val CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS = "Cannot use unbound generics in" +
         " insertion methods. It must be bound to a type through base Dao class."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_UPSERTION_METHODS = "Cannot use unbound generics in" +
+        " upsertion methods. It must be bound to a type through base Dao class."
     val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
     val CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES = "Cannot use unbound generics in Dao classes." +
         " If you are trying to create a base DAO, create a normal class, extend it with type" +
@@ -143,6 +147,9 @@
     val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
         " @Insert but does not have any parameters to insert."
 
+    val UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT = "Method annotated with" +
+        " @Upsert but does not have any parameters to insert or update."
+
     val DELETION_MISSING_PARAMS = "Method annotated with" +
         " @Delete but does not have any parameters to delete."
 
@@ -179,6 +186,8 @@
 
     val CANNOT_FIND_INSERT_RESULT_ADAPTER = "Not sure how to handle insert method's return type."
 
+    val CANNOT_FIND_UPSERT_RESULT_ADAPTER = "Not sure how to handle upsert method's return type."
+
     val UPDATE_MISSING_PARAMS = "Method annotated with" +
         " @Update but does not have any parameters to update."
 
@@ -774,6 +783,13 @@
         "(${primaryKeyNames.joinToString()}) needed to perform an INSERT. If your single " +
         "primary key is auto generated then the fields are optional."
 
+    fun missingPrimaryKeysInPartialEntityForUpsert(
+        partialEntityName: String,
+        primaryKeyNames: List<String>
+    ) = "The partial entity $partialEntityName is missing the primary key fields " +
+        "(${primaryKeyNames.joinToString()}) needed to perform an UPSERT. If your single " +
+        "primary key is auto generated then the fields are optional."
+
     fun missingRequiredColumnsInPartialEntity(
         partialEntityName: String,
         missingColumnNames: List<String>
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 a586df6..7d87c5a 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
@@ -16,15 +16,11 @@
 
 package androidx.room.processor
 
+import androidx.room.Upsert
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
-import androidx.room.vo.ShortcutEntity
-import androidx.room.vo.ShortcutQueryParameter
 import androidx.room.vo.UpsertionMethod
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.TypeSpec
+import androidx.room.vo.findFieldByColumnName
 
 class UpsertionMethodProcessor(
     baseContext: Context,
@@ -32,21 +28,68 @@
     val executableElement: XMethodElement
 ) {
     val context = baseContext.fork(executableElement)
-    class StubUpsertMethodBinder : UpsertMethodBinder(null) {
-        override fun convertAndReturn(
-            parameters: List<ShortcutQueryParameter>,
-            upsertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
-            dbField: FieldSpec,
-            scope: CodeGenScope
-        ) {}
-    }
     fun process(): UpsertionMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+
+        val annotation = delegate.extractAnnotation(
+            Upsert::class,
+            ProcessorErrors.MISSING_UPSERT_ANNOTATION
+        )
+
+        val returnType = delegate.extractReturnType()
+        val returnTypeName = returnType.typeName
+        context.checker.notUnbound(
+            returnTypeName, executableElement,
+            ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_UPSERTION_METHODS
+        )
+
+        val (entities, params) = delegate.extractParams(
+            targetEntityType = annotation?.getAsType("entity"),
+            missingParamError = ProcessorErrors.UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT,
+            onValidatePartialEntity = { entity, pojo ->
+                val missingPrimaryKeys = entity.primaryKey.fields.any {
+                    pojo.findFieldByColumnName(it.columnName) == null
+                }
+                context.checker.check(
+                    entity.primaryKey.autoGenerateId || !missingPrimaryKeys,
+                    executableElement,
+                    ProcessorErrors.missingPrimaryKeysInPartialEntityForUpsert(
+                        partialEntityName = pojo.typeName.toString(),
+                        primaryKeyNames = entity.primaryKey.fields.columnNames
+                    )
+                )
+
+                // Verify all non null columns without a default value are in the POJO otherwise
+                // the UPSERT will fail with a NOT NULL constraint.
+                val missingRequiredFields = (entity.fields - entity.primaryKey.fields).filter {
+                    it.nonNull && it.defaultValue == null &&
+                        pojo.findFieldByColumnName(it.columnName) == null
+                }
+                context.checker.check(
+                    missingRequiredFields.isEmpty(),
+                    executableElement,
+                    ProcessorErrors.missingRequiredColumnsInPartialEntity(
+                        partialEntityName = pojo.typeName.toString(),
+                        missingColumnNames = missingRequiredFields.map { it.columnName }
+                    )
+                )
+            }
+        )
+
+        val methodBinder = delegate.findUpsertMethodBinder(returnType, params)
+        // TODO: (b/240491114) Uncomment code below for UpsertMethodAdapter is implemented
+        /*context.checker.check(
+            methodBinder.adapter != null,
+            executableElement,
+            ProcessorErrors.CANNOT_FIND_UPSERT_RESULT_ADAPTER
+        )*/
+
         return UpsertionMethod(
             element = executableElement,
-            returnType = executableElement.returnType,
-            entities = emptyMap<String, ShortcutEntity>(),
-            parameters = emptyList<ShortcutQueryParameter>(),
-            methodBinder = StubUpsertMethodBinder()
+            returnType = returnType,
+            entities = entities,
+            parameters = params,
+            methodBinder = methodBinder
         )
     }
 }
\ 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 cc06a34..b7d3188 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
@@ -80,14 +80,12 @@
 import androidx.room.solver.query.result.SingleEntityQueryResultAdapter
 import androidx.room.solver.query.result.SingleNamedColumnRowAdapter
 import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.solver.shortcut.binderprovider.DeleteOrUpdateMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureDeleteOrUpdateMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureUpsertMethodBinderProvider
-import androidx.room.solver.shortcut.binderprovider.InsertMethodBinderProvider
-import androidx.room.solver.shortcut.binderprovider.UpsertMethodBinderProvider
+import androidx.room.solver.shortcut.binderprovider.InsertOrUpsertMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.InstantDeleteOrUpdateMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.InstantInsertMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.InstantUpsertMethodBinderProvider
@@ -225,8 +223,8 @@
             add(InstantPreparedQueryResultBinderProvider(context))
         }
 
-    val insertBinderProviders: List<InsertMethodBinderProvider> =
-        mutableListOf<InsertMethodBinderProvider>().apply {
+    val insertBinderProviders: List<InsertOrUpsertMethodBinderProvider> =
+        mutableListOf<InsertOrUpsertMethodBinderProvider>().apply {
             addAll(RxCallableInsertMethodBinderProvider.getAll(context))
             add(GuavaListenableFutureInsertMethodBinderProvider(context))
             add(InstantInsertMethodBinderProvider(context))
@@ -239,8 +237,8 @@
             add(InstantDeleteOrUpdateMethodBinderProvider(context))
         }
 
-    val upsertBinderProviders: List<UpsertMethodBinderProvider> =
-        mutableListOf<UpsertMethodBinderProvider>().apply {
+    val upsertBinderProviders: List<InsertOrUpsertMethodBinderProvider> =
+        mutableListOf<InsertOrUpsertMethodBinderProvider>().apply {
             addAll(RxCallableUpsertMethodBinderProvider.getAll(context))
             add(GuavaListenableFutureUpsertMethodBinderProvider(context))
             add(InstantUpsertMethodBinderProvider(context))
@@ -398,7 +396,7 @@
     fun findInsertMethodBinder(
         typeMirror: XType,
         params: List<ShortcutQueryParameter>
-    ): InsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         return insertBinderProviders.first {
             it.matches(typeMirror)
         }.provide(typeMirror, params)
@@ -407,7 +405,7 @@
     fun findUpsertMethodBinder(
         typeMirror: XType,
         params: List<ShortcutQueryParameter>
-    ): UpsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         return upsertBinderProviders.first {
             it.matches(typeMirror)
         }.provide(typeMirror, params)
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 c053354..e73004d 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
@@ -36,7 +36,7 @@
     val typeArg: XType,
     val addStmntBlock: CodeBlock.Builder.(callableImpl: TypeSpec, dbField: FieldSpec) -> Unit,
     adapter: InsertMethodAdapter?
-) : InsertMethodBinder(adapter) {
+) : InsertOrUpsertMethodBinder(adapter) {
 
     companion object {
         fun createInsertBinder(
@@ -48,15 +48,15 @@
 
     override fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
-        insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
+        adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
     ) {
         val adapterScope = scope.fork()
         val callableImpl = CallableTypeSpecBuilder(typeArg.typeName) {
-            adapter?.createInsertionMethodBody(
+            adapter?.createMethodBody(
                 parameters = parameters,
-                insertionAdapters = insertionAdapters,
+                adapters = adapters,
                 dbField = dbField,
                 scope = adapterScope
             )
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 37aa163..7040cc4 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
@@ -36,7 +36,7 @@
     val typeArg: XType,
     val addStmntBlock: CodeBlock.Builder.(callableImpl: TypeSpec, dbField: FieldSpec) -> Unit,
     adapter: UpsertMethodAdapter?
-) : UpsertMethodBinder(adapter) {
+) : InsertOrUpsertMethodBinder(adapter) {
 
     companion object {
         fun createUpsertBinder(
@@ -48,7 +48,7 @@
 
     override fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
-        upsertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
+        adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
     ) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertMethodBinder.kt
deleted file mode 100644
index 2a6c3bc..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertMethodBinder.kt
+++ /dev/null
@@ -1,62 +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.binder
-
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.InsertMethodAdapter
-import androidx.room.vo.ShortcutQueryParameter
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.TypeSpec
-
-/**
- * Connects the insert method, the database and the [InsertMethodAdapter].
- *
- * The default implementation is [InstantInsertMethodBinder] that executes the insert synchronously.
- * If the insert is deferred, rather than synchronously, alternatives implementations can be
- * implemented using this interface (e.g. RxJava, coroutines etc).
- */
-abstract class InsertMethodBinder(val adapter: InsertMethodAdapter?) {
-
-    /**
-     * Received the insert method parameters, the insertion adapters and generations the code that
-     * runs the insert and returns the result.
-     *
-     * For example, for the DAO method:
-     * ```
-     * @Insert
-     * fun addPublishers(vararg publishers: Publisher): List<Long>
-     * ```
-     * The following code will be generated:
-     *
-     * ```
-     * __db.beginTransaction();
-     * try {
-     *  List<Long> _result = __insertionAdapterOfPublisher.insertAndReturnIdsList(publishers);
-     *  __db.setTransactionSuccessful();
-     *  return _result;
-     * } finally {
-     *  __db.endTransaction();
-     * }
-     * ```
-     */
-    abstract fun convertAndReturn(
-        parameters: List<ShortcutQueryParameter>,
-        insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
-        dbField: FieldSpec,
-        scope: CodeGenScope
-    )
-}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/UpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
similarity index 73%
rename from room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/UpsertMethodBinder.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
index 9ec0168..f909b2a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/UpsertMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/InsertOrUpsertMethodBinder.kt
@@ -16,19 +16,21 @@
 
 package androidx.room.solver.shortcut.binder
 
+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
-import com.squareup.javapoet.TypeSpec
+
 /**
- * Connects the upsert method, the database and the [UpsertMethodAdapter].
+ * Connects the insert and upsert method, the database and the [InsertMethodAdapter] or [UpsertMethodAdapter].
  */
-abstract class UpsertMethodBinder(val adapter: UpsertMethodAdapter?) {
+abstract class InsertOrUpsertMethodBinder(val adapter: InsertOrUpsertMethodAdapter?) {
 
     /**
-     * Received the upsert method parameters, the upsertion adapters and generations the code that
-     * runs the upsert and returns the result.
+     * Received an insert or upsert method parameters, their adapters and generations the code that
+     * runs the insert or upsert and returns the result.
      *
      * For example, for the DAO method:
      * ```
@@ -50,7 +52,7 @@
      */
     abstract fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
-        upsertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
+        adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
     )
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 b808080..ac4c6cd 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
@@ -22,25 +22,25 @@
 import androidx.room.vo.ShortcutQueryParameter
 import androidx.room.writer.DaoWriter
 import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.TypeSpec
 
 /**
  * Binder that knows how to write instant (blocking) insert methods.
  */
-class InstantInsertMethodBinder(adapter: InsertMethodAdapter?) : InsertMethodBinder(adapter) {
+class InstantInsertMethodBinder(adapter: InsertMethodAdapter?) :
+    InsertOrUpsertMethodBinder(adapter) {
 
     override fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
-        insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
+        adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
     ) {
         scope.builder().apply {
             addStatement("$N.assertNotSuspendingTransaction()", DaoWriter.dbField)
         }
-        adapter?.createInsertionMethodBody(
+        adapter?.createMethodBody(
             parameters = parameters,
-            insertionAdapters = insertionAdapters,
+            adapters = adapters,
             dbField = dbField,
             scope = scope
         )
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 06860f7..2043db9 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
@@ -22,16 +22,16 @@
 import androidx.room.vo.ShortcutQueryParameter
 import androidx.room.writer.DaoWriter
 import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.TypeSpec
 
 /**
  * Binder that knows how to write instant (blocking) upsert methods.
  */
-class InstantUpsertMethodBinder(adapter: UpsertMethodAdapter?) : UpsertMethodBinder(adapter) {
+class InstantUpsertMethodBinder(adapter: UpsertMethodAdapter?) :
+    InsertOrUpsertMethodBinder(adapter) {
 
     override fun convertAndReturn(
         parameters: List<ShortcutQueryParameter>,
-        upsertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
+        adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
     ) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
index 1b3e17a..d7b7864 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
@@ -25,7 +25,7 @@
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
 import androidx.room.solver.shortcut.binder.CallableInsertMethodBinder.Companion.createInsertBinder
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
@@ -33,7 +33,7 @@
  */
 class GuavaListenableFutureInsertMethodBinderProvider(
     private val context: Context
-) : InsertMethodBinderProvider {
+) : InsertOrUpsertMethodBinderProvider {
 
     private val hasGuavaRoom by lazy {
         context.processingEnv.findTypeElement(RoomGuavaTypeNames.GUAVA_ROOM) != null
@@ -46,7 +46,7 @@
     override fun provide(
         declared: XType,
         params: List<ShortcutQueryParameter>
-    ): InsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         if (!hasGuavaRoom) {
             context.logger.e(ProcessorErrors.MISSING_ROOM_GUAVA_ARTIFACT)
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureUpsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureUpsertMethodBinderProvider.kt
index 8f8cee0..ab55632 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureUpsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureUpsertMethodBinderProvider.kt
@@ -25,7 +25,7 @@
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
 import androidx.room.solver.shortcut.binder.CallableUpsertMethodBinder.Companion.createUpsertBinder
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
@@ -33,7 +33,7 @@
  */
 class GuavaListenableFutureUpsertMethodBinderProvider(
     private val context: Context
-) : UpsertMethodBinderProvider {
+) : InsertOrUpsertMethodBinderProvider {
 
     private val hasGuavaRoom by lazy {
         context.processingEnv.findTypeElement(RoomGuavaTypeNames.GUAVA_ROOM) != null
@@ -46,7 +46,7 @@
     override fun provide(
         declared: XType,
         params: List<ShortcutQueryParameter>
-    ): UpsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         if (!hasGuavaRoom) {
             context.logger.e(ProcessorErrors.MISSING_ROOM_GUAVA_ARTIFACT)
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InsertOrUpsertMethodBinderProvider.kt
similarity index 70%
rename from room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InsertMethodBinderProvider.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InsertOrUpsertMethodBinderProvider.kt
index 2ef53c7..e9ab381 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InsertOrUpsertMethodBinderProvider.kt
@@ -17,21 +17,21 @@
 package androidx.room.solver.shortcut.binderprovider
 
 import androidx.room.compiler.processing.XType
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
- * Provider for insert method binders.
+ * Provider for insert and upsert method binders.
  */
-interface InsertMethodBinderProvider {
+interface InsertOrUpsertMethodBinderProvider {
 
     /**
-     * Check whether the [XType] can be handled by the [InsertMethodBinder]
+     * Check whether the [XType] can be handled by the [InsertOrUpsertMethodBinder]
      */
     fun matches(declared: XType): Boolean
 
     /**
-     * Provider of [InsertMethodBinder], based on the [XType] and the list of parameters
+     * Provider of [InsertOrUpsertMethodBinder], based on the [XType] and the list of parameters
      */
-    fun provide(declared: XType, params: List<ShortcutQueryParameter>): InsertMethodBinder
+    fun provide(declared: XType, params: List<ShortcutQueryParameter>): InsertOrUpsertMethodBinder
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantInsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantInsertMethodBinderProvider.kt
index 90261c3..37e14d9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantInsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantInsertMethodBinderProvider.kt
@@ -18,21 +18,22 @@
 
 import androidx.room.compiler.processing.XType
 import androidx.room.processor.Context
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.solver.shortcut.binder.InstantInsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
  * Provider for instant (blocking) insert method binder.
  */
-class InstantInsertMethodBinderProvider(private val context: Context) : InsertMethodBinderProvider {
+class InstantInsertMethodBinderProvider(private val context: Context) :
+    InsertOrUpsertMethodBinderProvider {
 
     override fun matches(declared: XType) = true
 
     override fun provide(
         declared: XType,
         params: List<ShortcutQueryParameter>
-    ): InsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         return InstantInsertMethodBinder(
             context.typeAdapterStore.findInsertAdapter(declared, params)
         )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantUpsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantUpsertMethodBinderProvider.kt
index 4a3ef91..6f4e8c9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantUpsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/InstantUpsertMethodBinderProvider.kt
@@ -18,21 +18,22 @@
 
 import androidx.room.compiler.processing.XType
 import androidx.room.processor.Context
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.solver.shortcut.binder.InstantUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
  * Provider for instant (blocking) upsert method binder.
  */
-class InstantUpsertMethodBinderProvider(private val context: Context) : UpsertMethodBinderProvider {
+class InstantUpsertMethodBinderProvider(private val context: Context) :
+    InsertOrUpsertMethodBinderProvider {
 
     override fun matches(declared: XType) = true
 
     override fun provide(
         declared: XType,
         params: List<ShortcutQueryParameter>
-    ): UpsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         return InstantUpsertMethodBinder(
             context.typeAdapterStore.findUpsertAdapter(declared, params)
         )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertMethodBinderProvider.kt
index 7f8dfa2..2ccc42a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertMethodBinderProvider.kt
@@ -23,7 +23,7 @@
 import androidx.room.processor.Context
 import androidx.room.solver.RxType
 import androidx.room.solver.shortcut.binder.CallableInsertMethodBinder.Companion.createInsertBinder
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
@@ -32,7 +32,7 @@
 open class RxCallableInsertMethodBinderProvider internal constructor(
     val context: Context,
     private val rxType: RxType
-) : InsertMethodBinderProvider {
+) : InsertOrUpsertMethodBinderProvider {
 
     /**
      * [Single] and [Maybe] are generics but [Completable] is not so each implementation of this
@@ -50,7 +50,7 @@
     override fun provide(
         declared: XType,
         params: List<ShortcutQueryParameter>
-    ): InsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         val typeArg = extractTypeArg(declared)
         val adapter = context.typeAdapterStore.findInsertAdapter(typeArg, params)
         return createInsertBinder(typeArg, adapter) { callableImpl, _ ->
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableUpsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableUpsertMethodBinderProvider.kt
index df1911e..21ce3fb 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableUpsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableUpsertMethodBinderProvider.kt
@@ -23,7 +23,7 @@
 import androidx.room.processor.Context
 import androidx.room.solver.RxType
 import androidx.room.solver.shortcut.binder.CallableUpsertMethodBinder
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /**
@@ -32,7 +32,7 @@
 open class RxCallableUpsertMethodBinderProvider internal constructor(
     val context: Context,
     private val rxType: RxType
-) : UpsertMethodBinderProvider {
+) : InsertOrUpsertMethodBinderProvider {
 
     /**
      * [Single] and [Maybe] are generics but [Completable] is not so each implementation of this
@@ -50,7 +50,7 @@
     override fun provide(
         declared: XType,
         params: List<ShortcutQueryParameter>
-    ): UpsertMethodBinder {
+    ): InsertOrUpsertMethodBinder {
         val typeArg = extractTypeArg(declared)
         val adapter = context.typeAdapterStore.findUpsertAdapter(typeArg, params)
         return CallableUpsertMethodBinder.createUpsertBinder(typeArg, adapter) { callableImpl, _ ->
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/UpsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/UpsertMethodBinderProvider.kt
deleted file mode 100644
index 0858f92..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/UpsertMethodBinderProvider.kt
+++ /dev/null
@@ -1,37 +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.binderprovider
-
-import androidx.room.compiler.processing.XType
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
-import androidx.room.vo.ShortcutQueryParameter
-
-/**
- * Provider for upsert method binders.
- */
-interface UpsertMethodBinderProvider {
-
-    /**
-     * Check whether the [XType] can be handled by the [UpsertMethodBinder]
-     */
-    fun matches(declared: XType): Boolean
-
-    /**
-     * Provider of [UpsertMethodBinder], based on the [XType] and the list of parameters
-     */
-    fun provide(declared: XType, params: List<ShortcutQueryParameter>): UpsertMethodBinder
-}
\ 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
index ad230db..c910e02 100644
--- 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
@@ -33,12 +33,12 @@
 import com.squareup.javapoet.FieldSpec
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
 
 /**
  * Class that knows how to generate an insert method body.
  */
-class InsertMethodAdapter private constructor(private val insertionType: InsertionType) {
+class InsertMethodAdapter private constructor(private val insertionType: InsertionType) :
+    InsertOrUpsertMethodAdapter() {
     companion object {
         fun create(
             returnType: XType,
@@ -117,9 +117,9 @@
         }
     }
 
-    fun createInsertionMethodBody(
+    override fun createMethodBody(
         parameters: List<ShortcutQueryParameter>,
-        insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>,
+        adapters: Map<String, Pair<FieldSpec, Any>>,
         dbField: FieldSpec,
         scope: CodeGenScope
     ) {
@@ -138,7 +138,7 @@
 
             beginControlFlow("try").apply {
                 parameters.forEach { param ->
-                    val insertionAdapter = insertionAdapters[param.name]?.first
+                    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
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
new file mode 100644
index 0000000..2eeba8b
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+/**
+ * Abstract class for insert and update method adapters.
+ */
+abstract class InsertOrUpsertMethodAdapter {
+    abstract 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/solver/shortcut/result/UpsertMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/UpsertMethodAdapter.kt
index a27512f..e45ccea 100644
--- 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
@@ -16,4 +16,16 @@
 
 package androidx.room.solver.shortcut.result
 
-class UpsertMethodAdapter
\ No newline at end of file
+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/Dao.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt
index aab6986..f328947 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt
@@ -48,7 +48,7 @@
 
     val typeName: ClassName by lazy { element.className }
 
-    val shortcutMethods: List<ShortcutMethod> by lazy {
+    val deleteOrUpdateShortcutMethods: List<DeleteOrUpdateShortcutMethod> by lazy {
         deletionMethods + updateMethods
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/DeleteOrUpdateShortcutMethod.kt
similarity index 95%
rename from room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/vo/DeleteOrUpdateShortcutMethod.kt
index dc4c618..0d04901 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/DeleteOrUpdateShortcutMethod.kt
@@ -22,7 +22,7 @@
 /**
  * Base class for shortcut methods in @DAO.
  */
-abstract class ShortcutMethod(
+abstract class DeleteOrUpdateShortcutMethod(
     val element: XMethodElement,
     val entities: Map<String, ShortcutEntity>,
     val parameters: List<ShortcutQueryParameter>,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
index 0d4dbe9..1973273 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
@@ -23,4 +23,4 @@
     entities: Map<String, ShortcutEntity>,
     parameters: List<ShortcutQueryParameter>,
     methodBinder: DeleteOrUpdateMethodBinder?
-) : ShortcutMethod(element, entities, parameters, methodBinder)
+) : DeleteOrUpdateShortcutMethod(element, entities, parameters, methodBinder)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertOrUpsertShortcutMethod.kt
similarity index 74%
copy from room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt
copy to room/room-compiler/src/main/kotlin/androidx/room/vo/InsertOrUpsertShortcutMethod.kt
index dc4c618..b4ae888 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertOrUpsertShortcutMethod.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
@@ -15,16 +15,17 @@
  */
 
 package androidx.room.vo
-
 import androidx.room.compiler.processing.XMethodElement
-import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
+import androidx.room.compiler.processing.XType
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 
 /**
  * Base class for shortcut methods in @DAO.
  */
-abstract class ShortcutMethod(
+abstract class InsertOrUpsertShortcutMethod(
     val element: XMethodElement,
     val entities: Map<String, ShortcutEntity>,
+    val returnType: XType,
     val parameters: List<ShortcutQueryParameter>,
-    val methodBinder: DeleteOrUpdateMethodBinder?
-)
+    val methodBinder: InsertOrUpsertMethodBinder?
+)
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
index 2260329..9e5cbca3 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
@@ -19,13 +19,13 @@
 import androidx.room.OnConflictStrategy
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
-import androidx.room.solver.shortcut.binder.InsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 
-data class InsertionMethod(
-    val element: XMethodElement,
+class InsertionMethod(
+    element: XMethodElement,
     @OnConflictStrategy val onConflict: Int,
-    val entities: Map<String, ShortcutEntity>,
-    val returnType: XType,
-    val parameters: List<ShortcutQueryParameter>,
-    val methodBinder: InsertMethodBinder
-)
\ No newline at end of file
+    entities: Map<String, ShortcutEntity>,
+    returnType: XType,
+    parameters: List<ShortcutQueryParameter>,
+    methodBinder: InsertOrUpsertMethodBinder
+) : InsertOrUpsertShortcutMethod(element, entities, returnType, parameters, methodBinder)
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 daac19d..1df9af3 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.
+ * 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/UpdateMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/UpdateMethod.kt
index 28be8f7..bbcc6fa 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/UpdateMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/UpdateMethod.kt
@@ -26,4 +26,4 @@
     parameters: List<ShortcutQueryParameter>,
     methodBinder: DeleteOrUpdateMethodBinder?,
     @OnConflictStrategy val onConflictStrategy: Int
-) : ShortcutMethod(element, entities, parameters, methodBinder)
+) : DeleteOrUpdateShortcutMethod(element, entities, parameters, methodBinder)
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 fd9d44f..76cc0fd 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
@@ -18,12 +18,12 @@
 
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
-import androidx.room.solver.shortcut.binder.UpsertMethodBinder
+import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
 
-data class UpsertionMethod(
-    val element: XMethodElement,
-    val entities: Map<String, ShortcutEntity>,
-    val returnType: XType,
-    val parameters: List<ShortcutQueryParameter>,
-    val methodBinder: UpsertMethodBinder
-    )
\ No newline at end of file
+class UpsertionMethod(
+    element: XMethodElement,
+    entities: Map<String, ShortcutEntity>,
+    returnType: XType,
+    parameters: List<ShortcutQueryParameter>,
+    methodBinder: InsertOrUpsertMethodBinder
+    ) : InsertOrUpsertShortcutMethod(element, entities, returnType, parameters, methodBinder)
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 14073bf..7a736ef 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -45,7 +45,7 @@
 import androidx.room.vo.RawQueryMethod
 import androidx.room.vo.ReadQueryMethod
 import androidx.room.vo.ShortcutEntity
-import androidx.room.vo.ShortcutMethod
+import androidx.room.vo.DeleteOrUpdateShortcutMethod
 import androidx.room.vo.TransactionMethod
 import androidx.room.vo.UpdateMethod
 import androidx.room.vo.WriteQueryMethod
@@ -389,9 +389,10 @@
 
         val scope = CodeGenScope(this)
 
-        method.methodBinder.convertAndReturn(
+        // TODO: (b/240491383) remove methodBinder nullability
+        method.methodBinder?.convertAndReturn(
             parameters = method.parameters,
-            insertionAdapters = insertionAdapters,
+            adapters = insertionAdapters,
             dbField = dbField,
             scope = scope
         )
@@ -419,7 +420,7 @@
         }
     }
 
-    private fun <T : ShortcutMethod> createShortcutMethods(
+    private fun <T : DeleteOrUpdateShortcutMethod> createShortcutMethods(
         methods: List<T>,
         methodPrefix: String,
         implCallback: (T, ShortcutEntity) -> TypeSpec
@@ -450,7 +451,7 @@
     }
 
     private fun createDeleteOrUpdateMethodBody(
-        method: ShortcutMethod,
+        method: DeleteOrUpdateShortcutMethod,
         adapters: Map<String, Pair<FieldSpec, TypeSpec>>
     ): CodeBlock {
         if (adapters.isEmpty() || method.methodBinder == null) {
@@ -495,9 +496,9 @@
 
         val scope = CodeGenScope(this)
 
-        method.methodBinder.convertAndReturn(
+        method.methodBinder?.convertAndReturn(
             parameters = method.parameters,
-            upsertionAdapters = upsertionAdapters,
+            adapters = upsertionAdapters,
             dbField = dbField,
             scope = scope
         )
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
similarity index 99%
rename from room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
rename to room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
index bbf5cbe..db69a38 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
@@ -32,7 +32,7 @@
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.testing.context
-import androidx.room.vo.ShortcutMethod
+import androidx.room.vo.DeleteOrUpdateShortcutMethod
 import com.squareup.javapoet.ArrayTypeName
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.ParameterizedTypeName
@@ -45,7 +45,7 @@
 /**
  * Base test class for shortcut methods.
  */
-abstract class ShortcutMethodProcessorTest<out T : ShortcutMethod>(
+abstract class DeleteOrUpdateShortcutMethodProcessorTest<out T : DeleteOrUpdateShortcutMethod>(
     val annotation: KClass<out Annotation>
 ) {
     companion object {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
index d1c4ae8..f7184dd 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
@@ -26,7 +26,8 @@
 
 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 @RunWith(JUnit4::class)
-class DeletionMethodProcessorTest : ShortcutMethodProcessorTest<DeletionMethod>(Delete::class) {
+class DeletionMethodProcessorTest :
+    DeleteOrUpdateShortcutMethodProcessorTest<DeletionMethod>(Delete::class) {
     override fun invalidReturnTypeError(): String = CANNOT_FIND_DELETE_RESULT_ADAPTER
 
     override fun noParamsError(): String = DELETION_MISSING_PARAMS
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
new file mode 100644
index 0000000..93b83a8
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -0,0 +1,802 @@
+/*
+ * 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.
+ * 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 COMMON
+import androidx.room.Dao
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XType
+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.testing.context
+import androidx.room.vo.InsertOrUpsertShortcutMethod
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import kotlin.reflect.KClass
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ParameterizedTypeName
+import org.junit.Test
+
+/**
+ * Base test class for insert and upsert methods.
+ */
+abstract class InsertOrUpsertShortcutMethodProcessorTest <out T : InsertOrUpsertShortcutMethod>(
+    val annotation: KClass<out Annotation>
+) {
+    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")
+    }
+
+    @Test
+    fun readNoParams() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void foo();
+                """
+        ) { insertionUpsertion, invocation ->
+
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("foo")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(0)
+            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.entities.size).isEqualTo(0)
+            invocation.assertCompilationResult {
+                hasErrorContaining(noParamsError())
+            }
+        }
+    }
+
+    @Test
+    fun notAnEntity() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void foo(NotAnEntity notValid);
+                """
+        ) { insertionUpsertion, invocation ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("foo")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            assertThat(insertionUpsertion.entities.size).isEqualTo(0)
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+                )
+            }
+        }
+    }
+
+    abstract fun noParamsError(): String
+
+    @Test
+    fun single() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public long foo(User user);
+                """
+        ) { insertionUpsertion, _ ->
+
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("foo")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(param.pojoType?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.entities["user"]?.isPartialEntity)
+                .isEqualTo(false)
+
+            assertThat(insertionUpsertion.entities["user"]?.pojo?.typeName)
+                .isEqualTo(ClassName.get("foo.bar", "User") as TypeName)
+
+            assertThat(insertionUpsertion.returnType.typeName)
+                .isEqualTo(TypeName.LONG)
+        }
+    }
+    @Test
+    fun two() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void foo(User u1, User u2);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("foo")
+
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(2)
+            insertionUpsertion.parameters.forEach {
+                assertThat(it.type.typeName).isEqualTo(USER_TYPE_NAME)
+                assertThat(it.pojoType?.typeName).isEqualTo(USER_TYPE_NAME)
+            }
+            assertThat(insertionUpsertion.entities.size)
+                .isEqualTo(2)
+
+            assertThat(insertionUpsertion.entities["u1"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.entities["u2"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.parameters.map { it.name })
+                .isEqualTo(listOf("u1", "u2"))
+
+            assertThat(insertionUpsertion.returnType.typeName)
+                .isEqualTo(TypeName.VOID)
+        }
+    }
+
+    @Test
+    fun list() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public List<Long> insertUsers(List<User> users);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(
+                    ParameterizedTypeName.get(
+                        ClassName.get("java.util", "List"),
+                        USER_TYPE_NAME
+                    ) as TypeName
+                )
+
+            assertThat(param.pojoType?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.returnType.typeName)
+                .isEqualTo(
+                    ParameterizedTypeName.get(
+                        ClassName.get("java.util", "List"),
+                        ClassName.get("java.lang", "Long")
+                    ) as TypeName
+                )
+        }
+    }
+
+    @Test
+    fun array() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void insertUsers(User[] users);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName)
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.returnType.typeName)
+                .isEqualTo(TypeName.VOID)
+        }
+    }
+
+    @Test
+    fun set() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void insertUsers(Set<User> users);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(
+                    ParameterizedTypeName.get(
+                        ClassName.get("java.util", "Set"),
+                        COMMON.USER_TYPE_NAME
+                    ) as TypeName
+                )
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+        }
+    }
+
+    @Test
+    fun queue() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void insertUsers(Queue<User> users);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(
+                    ParameterizedTypeName.get(
+                        ClassName.get("java.util", "Queue"),
+                        USER_TYPE_NAME
+                    ) as TypeName
+                )
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+        }
+    }
+
+    @Test
+    fun iterable() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void insert(Iterable<User> users);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("insert")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(
+                    ParameterizedTypeName.get(
+                        ClassName.get("java.lang", "Iterable"),
+                        USER_TYPE_NAME
+                    ) as TypeName
+                )
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+        }
+    }
+
+    @Test
+    fun customCollection() {
+        singleInsertUpsertShortcutMethod(
+            """
+                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
+                @${annotation.java.canonicalName}
+                abstract public void insert(MyList<String, User> users);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("insert")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+            val param = insertionUpsertion.parameters.first()
+            assertThat(param.type.typeName)
+                .isEqualTo(
+                    ParameterizedTypeName.get(
+                        ClassName.get("foo.bar", "MyClass.MyList"),
+                        CommonTypeNames.STRING, USER_TYPE_NAME
+                    ) as TypeName
+                )
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+        }
+    }
+
+    @Test
+    fun differentTypes() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}
+                abstract public void foo(User u1, Book b1);
+                """
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(2)
+            assertThat(insertionUpsertion.parameters[0].type.typeName.toString())
+                .isEqualTo("foo.bar.User")
+
+            assertThat(insertionUpsertion.parameters[1].type.typeName.toString())
+                .isEqualTo("foo.bar.Book")
+
+            assertThat(insertionUpsertion.parameters.map { it.name }).isEqualTo(listOf("u1", "b1"))
+
+            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(2)
+
+            assertThat(insertionUpsertion.entities["u1"]?.pojo?.typeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.entities["b1"]?.pojo?.typeName)
+                .isEqualTo(BOOK_TYPE_NAME)
+        }
+    }
+
+    // TODO: Add in the return type tests when upsertionMethodAdapter is implemented
+    @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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(entity = User.class)
+                abstract public long foo(Username username);
+            """,
+            additionalSources = listOf(usernameSource)
+        ) { insertionUpsertion, _ ->
+            assertThat(insertionUpsertion.element.jvmName).isEqualTo("foo")
+            assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
+
+            val param = insertionUpsertion.parameters.first()
+
+            assertThat(param.type.typeName).isEqualTo(USERNAME_TYPE_NAME)
+
+            assertThat(param.pojoType?.typeName).isEqualTo(USERNAME_TYPE_NAME)
+
+            assertThat(insertionUpsertion.entities.size).isEqualTo(1)
+
+            assertThat(insertionUpsertion.entities["username"]?.isPartialEntity)
+                .isEqualTo(true)
+
+            assertThat(insertionUpsertion.entities["username"]?.entityTypeName)
+                .isEqualTo(USER_TYPE_NAME)
+
+            assertThat(insertionUpsertion.entities["username"]?.pojo?.typeName)
+                .isEqualTo(USERNAME_TYPE_NAME)
+        }
+    }
+
+    @Test
+    fun targetEntitySameAsPojo() {
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(entity = Pet.class)
+                abstract public long foo(PetName petName);
+            """,
+            additionalSources = listOf(petNameSource, petSource)
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(missingPrimaryKey(
+                    "foo.bar.PetName",
+                    listOf("petId"))
+                )
+            }
+        }
+    }
+
+    abstract fun missingPrimaryKey(partialEntityName: String, primaryKeyName: List<String>):
+        String
+
+    @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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(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;
+            }
+            """
+        )
+        singleInsertUpsertShortcutMethod(
+            """
+                @${annotation.java.canonicalName}(entity = User.class)
+                abstract public long foo(UserPets userPets);
+                """,
+            additionalSources = listOf(userPetsSource, petSource)
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.INVALID_RELATION_IN_PARTIAL_ENTITY
+                )
+            }
+        }
+    }
+
+    abstract fun process(
+        baseContext: Context,
+        containing: XType,
+        executableElement: XMethodElement
+    ): T
+
+    fun singleInsertUpsertShortcutMethod(
+        vararg input: String,
+        additionalSources: List<Source> = emptyList(),
+        handler: (T, 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(annotation)
+                        }.toList()
+                    )
+                }.first { it.second.isNotEmpty() }
+            val processed = process(
+                baseContext = invocation.context,
+                containing = owner.type,
+                executableElement = methods.first()
+            )
+            handler(processed, invocation)
+        }
+    }
+}
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 ffa2e8fa..7891b39 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
@@ -415,7 +415,8 @@
                 abstract public $type foo(User user);
                 """
             ) { insertion, invocation ->
-                assertThat(insertion.methodBinder.adapter, `is`(nullValue()))
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
                 invocation.assertCompilationResult {
                     hasErrorContaining(
                         ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
@@ -440,7 +441,8 @@
                 abstract public $type foo(User user);
                 """
             ) { insertion, invocation ->
-                assertThat(insertion.methodBinder.adapter, `is`(nullValue()))
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
                 invocation.assertCompilationResult {
                     hasErrorContaining(
                         ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
@@ -464,7 +466,8 @@
                 abstract public $type foo(User... user);
                 """
             ) { insertion, invocation ->
-                assertThat(insertion.methodBinder.adapter, `is`(nullValue()))
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
                 invocation.assertCompilationResult {
                     hasErrorContaining(
                         ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
@@ -488,7 +491,8 @@
                 abstract public $type foo(User user1, User user2);
                 """
             ) { insertion, invocation ->
-                assertThat(insertion.methodBinder.adapter, `is`(nullValue()))
+                // TODO: (b/240491383) remove methodBinder nullability
+                assertThat(insertion.methodBinder?.adapter, `is`(nullValue()))
                 invocation.assertCompilationResult {
                     hasErrorContaining(
                         ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
@@ -563,7 +567,7 @@
                 abstract public ${pair.first} foo(User$dots user);
                 """
             ) { insertion, _ ->
-                assertThat(insertion.methodBinder.adapter, `is`(notNullValue()))
+                assertThat(insertion.methodBinder?.adapter, `is`(notNullValue()))
             }
         }
     }
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
new file mode 100644
index 0000000..2f1b27f
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/NewInsertionMethodProcessorTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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/UpdateMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpdateMethodProcessorTest.kt
index e2a264a..9a2d2c1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/UpdateMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpdateMethodProcessorTest.kt
@@ -31,7 +31,8 @@
 
 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 @RunWith(JUnit4::class)
-class UpdateMethodProcessorTest : ShortcutMethodProcessorTest<UpdateMethod>(Update::class) {
+class UpdateMethodProcessorTest :
+    DeleteOrUpdateShortcutMethodProcessorTest<UpdateMethod>(Update::class) {
     override fun invalidReturnTypeError(): String = CANNOT_FIND_UPDATE_RESULT_ADAPTER
 
     override fun noParamsError(): String = UPDATE_MISSING_PARAMS
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
new file mode 100644
index 0000000..4370d53
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.Upsert
+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.vo.UpsertionMethod
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class UpsertionMethodProcessorTest :
+    InsertOrUpsertShortcutMethodProcessorTest<UpsertionMethod>(Upsert::class) {
+    override fun noParamsError(): String = UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT
+
+    override fun missingPrimaryKey(partialEntityName: String, primaryKeyName: List<String>):
+        String {
+        return ProcessorErrors.missingPrimaryKeysInPartialEntityForUpsert(
+            partialEntityName,
+            primaryKeyName
+        )
+    }
+
+    override fun process(
+        baseContext: Context,
+        containing: XType,
+        executableElement: XMethodElement
+    ): UpsertionMethod {
+        return UpsertionMethodProcessor(baseContext, containing, executableElement).process()
+    }
+}