Add support for arg constructors

This CL adds constructor support to Pojo and Entity processors.
We try to map each constructor parameter to a field based on
its name. If there are conflicts, we fail the compilation.
If there are multiple matching constructors, we fail the compilation
and developer can annotate unwanted ones with @Ignore to workaround
the issue.

This CL also prioritizes PojoRowAdapter over EntityRowAdapter since
it is faster and proguard does a good job on shrinking the generated
code.

Bug: 34611719
Bug: 36473441
Bug: 34754519
Test: ConstructorTest, PojoProcessorTest

Change-Id: I70592b129c7dcb9cacfb072b2c63169630eadf9a
diff --git a/room/common/src/main/java/com/android/support/room/Entity.java b/room/common/src/main/java/com/android/support/room/Entity.java
index 06d57f7..db9f566 100644
--- a/room/common/src/main/java/com/android/support/room/Entity.java
+++ b/room/common/src/main/java/com/android/support/room/Entity.java
@@ -24,9 +24,14 @@
 /**
  * Marks a class as an entity. This class will have a mapping SQLite table in the database.
  * <p>
- * Each entity must have at least 1 field annotated with {@link PrimaryKey} and it must have a
- * no-arg constructor. You can also use {@link #primaryKeys()} attribute to define the primary
- * key.
+ * Each entity must have at least 1 field annotated with {@link PrimaryKey}.
+ * You can also use {@link #primaryKeys()} attribute to define the primary key.
+ * <p>
+ * Each entity must either have a no-arg constructor or a constructor whose parameters match
+ * fields (based on type and name). Constructor does not have to receive all fields as parameters
+ * but if a field is not passed into the constructor, it should either be public or have a public
+ * setter. If a matching constructor is available, Room will always use it. If you don't want it
+ * to use a constructor, you can annotate it with {@link Ignore}.
  * <p>
  * When a class is marked as an Entity, all of its fields are persisted. If you would like to
  * exclude some of its fields, you can mark them with {@link Ignore}.
@@ -36,12 +41,20 @@
  * {@literal @}Entity
  * public class User {
  *   {@literal @}PrimaryKey
- *   private int uid;
+ *   private final int uid;
  *   private String name;
  *   {@literal @}ColumnInfo(name = "last_name")
  *   private String lastName;
- *   // getters and setters are ignored for brevity but they are required for Room to work or the
- *   // fields should be public.
+ *
+ *   public User(int uid) {
+ *       this.uid = uid;
+ *   }
+ *   public String getLastName() {
+ *       return lastName;
+ *   }
+ *   public void setLastName(String lastName) {
+ *       this.lastName = lastName;
+ *   }
  * }
  * </pre>
  *
diff --git a/room/common/src/main/java/com/android/support/room/Ignore.java b/room/common/src/main/java/com/android/support/room/Ignore.java
index c8f8f9d..36888a0 100644
--- a/room/common/src/main/java/com/android/support/room/Ignore.java
+++ b/room/common/src/main/java/com/android/support/room/Ignore.java
@@ -27,7 +27,7 @@
  * This annotation can be used in multiple places where Room processor runs. For instance, you can
  * add it to a field of an {@link Entity} and Room will not persist that field.
  */
-@Target({ElementType.METHOD, ElementType.FIELD})
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
 @Retention(RetentionPolicy.CLASS)
 public @interface Ignore {
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt
index a7f8cfa..a1c86e9 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt
@@ -105,3 +105,13 @@
 object RoomRxJava2TypeNames {
     val RX_ROOM = ClassName.get("com.android.support.room", "RxRoom")
 }
+
+fun TypeName.defaultValue() : String {
+    return if (!isPrimitive) {
+        "null"
+    } else if (this == TypeName.BOOLEAN) {
+        "false"
+    } else {
+        "0"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
index 279fb62..2c7ab96 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
@@ -130,7 +130,8 @@
                 decomposedFields = pojo.decomposedFields,
                 indices = indices,
                 primaryKey = primaryKey,
-                foreignKeys = entityForeignKeys)
+                foreignKeys = entityForeignKeys,
+                constructor = pojo.constructor)
 
         return entity
     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt
index 96520d0..85e3432 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/PojoProcessor.kt
@@ -34,6 +34,7 @@
 import com.android.support.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
 import com.android.support.room.processor.cache.Cache
 import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Constructor
 import com.android.support.room.vo.Field
 import com.android.support.room.vo.FieldGetter
 import com.android.support.room.vo.DecomposedField
@@ -56,6 +57,7 @@
 import javax.lang.model.type.DeclaredType
 import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
 
 /**
  * Processes any class as if it is a Pojo.
@@ -148,28 +150,124 @@
         val setterCandidates = methods.filter {
             it.parameters.size == 1 && it.returnType.kind == TypeKind.VOID
         }
+        // don't try to find a constructor for binding to statement.
+        val constructor = if (bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT) {
+            // we don't need to construct this POJO.
+            null
+        } else {
+            chooseConstructor(myFields, decomposedFields)
+        }
 
         assignGetters(myFields, getterCandidates)
-        assignSetters(myFields, setterCandidates)
+        assignSetters(myFields, setterCandidates, constructor)
 
         decomposedFields.forEach {
             assignGetter(it.field, getterCandidates)
-            assignSetter(it.field, setterCandidates)
+            assignSetter(it.field, setterCandidates, constructor)
         }
 
         myRelationsList.forEach {
             assignGetter(it.field, getterCandidates)
-            assignSetter(it.field, setterCandidates)
+            assignSetter(it.field, setterCandidates, constructor)
         }
 
         val pojo = Pojo(element = element,
                 type = declaredType,
                 fields = fields,
                 decomposedFields = decomposedFields,
-                relations = relations)
+                relations = relations,
+                constructor = constructor)
         return pojo
     }
 
+    private fun chooseConstructor(myFields: List<Field>, decomposed : List<DecomposedField>)
+            : Constructor? {
+        val constructors = ElementFilter.constructorsIn(element.enclosedElements)
+                .filterNot { it.hasAnnotation(Ignore::class) || it.hasAnyOf(PRIVATE) }
+        val fieldMap = myFields.associateBy { it.name }
+        val decomposedMap = decomposed.associateBy { it.field.name }
+        val typeUtils = context.processingEnv.typeUtils
+        val failedConstructors = mutableMapOf<ExecutableElement, List<Constructor.Param?>>()
+        val goodConstructors = constructors.map { constructor ->
+            val params = constructor.parameters.map param@ { param ->
+                val paramName = param.simpleName.toString()
+                val paramType = param.asType()
+
+                val matches = fun(field: Field?): Boolean {
+                    return if (field == null) {
+                        false
+                    } else if (!field.nameWithVariations.contains(paramName)) {
+                        false
+                    } else {
+                        typeUtils.isAssignable(paramType, field.type)
+                    }
+                }
+
+                val exactFieldMatch = fieldMap[paramName]
+
+                if (matches(exactFieldMatch)) {
+                    return@param Constructor.FieldParam(exactFieldMatch!!)
+                }
+                val exactDecomposedMatch = decomposedMap[paramName]
+                if (matches(exactDecomposedMatch?.field)) {
+                    return@param Constructor.DecomposedParam(exactDecomposedMatch!!)
+                }
+
+                val matchingFields = myFields.filter {
+                    matches(it)
+                }
+                val decomposedMatches = decomposed.filter {
+                    matches(it.field)
+                }
+                if (matchingFields.isEmpty() && decomposedMatches.isEmpty()) {
+                    null
+                } else if (matchingFields.size + decomposedMatches.size == 1) {
+                    if (matchingFields.isNotEmpty()) {
+                        Constructor.FieldParam(matchingFields.first())
+                    } else {
+                        Constructor.DecomposedParam(decomposedMatches.first())
+                    }
+                } else {
+                    context.logger.e(param, ProcessorErrors.ambigiousConstructor(
+                            pojo = element.qualifiedName.toString(),
+                            paramName = param.simpleName.toString(),
+                            matchingFields = matchingFields.map { it.getPath() }
+                                    + decomposed.map { it.field.getPath() }
+                    ))
+                    null
+                }
+            }
+            if (params.any { it == null }) {
+                failedConstructors.put(constructor, params)
+                null
+            } else {
+                @Suppress("UNCHECKED_CAST")
+                Constructor(constructor, params as List<Constructor.Param>)
+            }
+        }.filterNotNull()
+        if (goodConstructors.isEmpty()) {
+            if (failedConstructors.isNotEmpty()) {
+                val failureMsg = failedConstructors.entries.joinToString("\n") { entry ->
+                    val paramsMatching = entry.key.parameters.withIndex().joinToString(", ") {
+                        "${it.value.simpleName} : ${entry.value[it.index]?.log()}"
+                    }
+                    "${entry.key} : [$paramsMatching]"
+                }
+                context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR +
+                        "\nTried the following constructors but they failed to match:\n$failureMsg")
+            }
+            context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+            return null
+        }
+        if (goodConstructors.size > 1) {
+            goodConstructors.forEach {
+                context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
+            }
+            return null
+        }
+        return goodConstructors.first()
+    }
+
     private fun processDecomposedField(declaredType: DeclaredType?, it: Element): DecomposedField {
         val fieldPrefix = it.getAnnotationValue(Decompose::class.java, "prefix")
                 ?.toString() ?: ""
@@ -323,13 +421,19 @@
         context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD)
     }
 
-    private fun assignSetters(fields: List<Field>, setterCandidates: List<ExecutableElement>) {
+    private fun assignSetters(fields: List<Field>, setterCandidates: List<ExecutableElement>,
+                              constructor : Constructor?) {
         fields.forEach { field ->
-            assignSetter(field, setterCandidates)
+            assignSetter(field, setterCandidates, constructor)
         }
     }
 
-    private fun assignSetter(field: Field, setterCandidates: List<ExecutableElement>) {
+    private fun assignSetter(field: Field, setterCandidates: List<ExecutableElement>,
+                             constructor: Constructor?) {
+        if (constructor != null && constructor.hasField(field)) {
+            field.setter = FieldSetter(field.name, field.type, CallType.CONSTRUCTOR)
+            return
+        }
         val success = chooseAssignment(field = field,
                 candidates = setterCandidates,
                 nameVariations = field.setterNameWithVariations,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
index 6f8bec6..7917e68 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
@@ -424,4 +424,24 @@
     }
     val MISSING_ROOM_RXJAVA2_ARTIFACT = "To use RxJava2 features, you must add `rxjava2`" +
             " artifact from Room as a dependency. com.android.support.room:rxjava2:<version>"
+
+    fun ambigiousConstructor(pojo : String, paramName:String, matchingFields : List<String>)
+            : String {
+        return """
+            Ambiguous constructor. The parameter ($paramName) in $pojo matches multiple fields:
+            [${matchingFields.joinToString(",")}]. If you don't want to use this constructor,
+            you can annotate it with @Ignore. If you want Room to use this constructor, you can
+            rename the parameters to exactly match the field name to fix the ambiguity.
+            """.trim()
+    }
+
+    val MISSING_POJO_CONSTRUCTOR = """
+            Entities and Pojos must have a usable public constructor. You can have an empty
+            constructor or a constructor whose parameters match the fields (by name and type).
+            """.trim()
+
+    val TOO_MANY_POJO_CONSTRUCTORS = """
+            Room cannot pick a constructor since multiple constructors are suitable. Try to annotate
+            unwanted constructors with @Ignore.
+            """.trim()
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt
index 9183923..1f7b4ba 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/TypeAdapterStore.kt
@@ -354,14 +354,6 @@
                 // TODO one day support this
                 return null
             }
-            val asElement = MoreTypes.asElement(typeMirror)
-            if (asElement.hasAnnotation(Entity::class)) {
-                // TODO we might parse this too much, would be nice to scope these parsers
-                // at least for entities.
-                return EntityRowAdapter(EntityProcessor(context,
-                        MoreElements.asType(asElement)).process())
-            }
-
             val resultInfo = query.resultInfo
 
             val (rowAdapter, rowAdapterLogs) = if (resultInfo != null && query.errors.isEmpty()
@@ -384,6 +376,15 @@
                 Pair(null, null)
             }
 
+            if (rowAdapter == null && query.resultInfo == null) {
+                // we don't know what query returns. Check for entity.
+                val asElement = MoreTypes.asElement(typeMirror)
+                if (asElement.hasAnnotation(Entity::class)) {
+                    return EntityRowAdapter(EntityProcessor(context,
+                            MoreElements.asType(asElement)).process())
+                }
+            }
+
             if (rowAdapter != null && !(rowAdapterLogs?.hasErrors() ?: false)) {
                 rowAdapterLogs?.writeTo(context.processingEnv)
                 return rowAdapter
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
index 254fc49..424d8b2 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/solver/query/result/PojoRowAdapter.kt
@@ -101,15 +101,18 @@
             val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
             scope.builder().addStatement("final $T $L = $L.getColumnIndexOrThrow($S)",
                     TypeName.INT, indexVar, cursorVarName, it.columnName)
-            FieldWithIndex(it, indexVar)
+            FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = true)
         }
     }
 
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
-            addStatement("$L = new $T()", outVarName, out.typeName())
-            FieldReadWriteWriter.readFromCursor(outVarName, cursorVarName,
-                    mapping.fieldsWithIndices, scope)
+            FieldReadWriteWriter.readFromCursor(
+                    outVar = outVarName,
+                    outPojo = pojo,
+                    cursorVar = cursorVarName,
+                    fieldsWithIndices = mapping.fieldsWithIndices,
+                    scope = scope)
             relationCollectors.forEach {
                 it.writeReadParentKeyCode(
                         cursorVarName = cursorVarName,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt
index aa30cb1..4e1f473 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt
@@ -18,5 +18,6 @@
 
 enum class CallType {
     FIELD,
-    METHOD
+    METHOD,
+    CONSTRUCTOR
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Constructor.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Constructor.kt
new file mode 100644
index 0000000..b356837
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Constructor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 com.android.support.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * For each Entity / Pojo we process has a constructor. It might be the empty constructor or a
+ * constructor with fields.
+ */
+data class Constructor(val element : ExecutableElement, val params : List<Param>) {
+
+    fun hasField(field : Field) : Boolean {
+        return params.any {
+            when (it) {
+                is FieldParam -> it.field === field
+                is DecomposedParam -> it.decomposed.field === field
+                else -> false
+            }
+        }
+    }
+
+    class FieldParam(val field : Field) : Param(ParamType.FIELD) {
+        override fun log(): String = field.getPath()
+
+    }
+
+    class DecomposedParam(val decomposed: DecomposedField) : Param(ParamType.DECOMPOSED) {
+        override fun log(): String = decomposed.field.getPath()
+
+    }
+
+    abstract class Param(val type : ParamType) {
+        abstract fun log() : String;
+    }
+
+    enum class ParamType {
+        FIELD,
+        DECOMPOSED
+    }
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
index a9fa995..db70dcb 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
@@ -25,8 +25,9 @@
 class Entity(element: TypeElement, val tableName: String, type: DeclaredType,
              fields: List<Field>, decomposedFields: List<DecomposedField>,
              val primaryKey: PrimaryKey, val indices: List<Index>,
-             val foreignKeys: List<ForeignKey>)
-    : Pojo(element, type, fields, decomposedFields, emptyList()) {
+             val foreignKeys: List<ForeignKey>,
+             constructor: Constructor?)
+    : Pojo(element, type, fields, decomposedFields, emptyList(), constructor) {
 
     val createTableQuery by lazy {
         createTableQuery(tableName)
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
index 9b9f7a3..d6fe529 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
@@ -27,7 +27,10 @@
         val stmt = when (callType) {
             CallType.FIELD -> "final $T $L = $L.$L"
             CallType.METHOD -> "final $T $L = $L.$L()"
+            CallType.CONSTRUCTOR -> null
         }
-        builder.addStatement(stmt, TypeName.get(type), outVar, ownerVar, name)
+        stmt?.let {
+            builder.addStatement(stmt, TypeName.get(type), outVar, ownerVar, name)
+        }
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
index eb5d736..51ac9d5 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
@@ -25,7 +25,10 @@
         val stmt = when (callType) {
             CallType.FIELD -> "$L.$L = $L"
             CallType.METHOD -> "$L.$L($L)"
+            CallType.CONSTRUCTOR -> null
         }
-        builder.addStatement(stmt, ownerVar, name, inVar)
+        stmt?.let {
+            builder.addStatement(stmt, ownerVar, name, inVar)
+        }
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldWithIndex.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldWithIndex.kt
index 13a643e..4cca0dd 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldWithIndex.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldWithIndex.kt
@@ -19,5 +19,18 @@
 /**
  * A common value object when we need to associate a Field with an Index
  * variable.
+ * <p>
+ * If we are sure that the field will be there at compile time, we set it to always Exists so that
+ * the generated code does not check for -1 column indices.
  */
-data class FieldWithIndex(val field : Field, val indexVar : String)
+data class FieldWithIndex(val field : Field, val indexVar : String, val alwaysExists : Boolean) {
+    companion object {
+        fun byOrder(fields : List<Field>) : List<FieldWithIndex> {
+            return fields.mapIndexed { index, field ->
+                FieldWithIndex(field = field,
+                        indexVar = "${index + 1}",
+                        alwaysExists = true)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Pojo.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Pojo.kt
index 4516457..d79e141 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/Pojo.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Pojo.kt
@@ -27,6 +27,7 @@
  */
 // TODO make data class when move to kotlin 1.1
 open class Pojo(val element : TypeElement, val type: DeclaredType, val fields : List<Field>,
-                val decomposedFields : List<DecomposedField>, val relations: List<Relation>) {
+                val decomposedFields : List<DecomposedField>, val relations: List<Relation>,
+                val constructor : Constructor? = null) {
     val typeName: TypeName by lazy { type.typeName() }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt
index a1b8c68..c9f95af 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityCursorConverterWriter.kt
@@ -60,109 +60,27 @@
 
     private fun buildConvertMethodBody(writer: ClassWriter, cursorParam: ParameterSpec)
             : CodeBlock {
-        // TODO support arg constructor
         val scope = CodeGenScope(writer)
         val entityVar = scope.getTmpVar("_entity")
         scope.builder().apply {
-            addStatement("$T $L = new $T()", entity.typeName, entityVar, entity.typeName)
-            val allParents = FieldReadWriteWriter.getAllParents(entity.fields)
-            val sortedParents = allParents
-                    .sortedBy {
-                        depth(it)
-                    }
-                    .associate {
-                        Pair(it, scope.getTmpVar("_tmp${it.field.name}"))
-                    }
-            // for each field parent, create a not null var so that we can set it at the end
-            val parentNotNullVars = declareParents(sortedParents, scope)
-
-            val colNameVar = scope.getTmpVar("_columnName")
-            val colIndexVar = scope.getTmpVar("_columnIndex")
-            addStatement("$T $L = 0", TypeName.INT, colIndexVar)
-            beginControlFlow("for ($T $L : $N.getColumnNames())",
-                    TypeName.get(String::class.java), colNameVar, cursorParam).apply {
-                beginControlFlow("switch($L.hashCode())", colNameVar).apply {
-                    entity.fields.groupBy { it.columnName.hashCode() }.forEach {
-                        val hash = it.key
-                        beginControlFlow("case $L:", hash).apply {
-                            val fields = it.value
-                            fields.forEach { field ->
-                                val subOwner = field.parent?.let {
-                                    sortedParents[it]
-                                } ?: entityVar
-                                beginControlFlow("if ($S.equals($L))", field.columnName, colNameVar)
-                                val notNullVar = field.parent?.let {
-                                    parentNotNullVars[it]
-                                }
-                                if (notNullVar != null) {
-                                    beginControlFlow("if (!cursor.isNull($L))", colIndexVar).apply {
-                                        addStatement("$L = true", notNullVar)
-                                        readField(field, cursorParam, colIndexVar, subOwner, scope)
-                                    }
-                                    endControlFlow()
-                                } else {
-                                    readField(field, cursorParam, colIndexVar, subOwner, scope)
-                                }
-                                endControlFlow()
-                            }
-                        }
-                        endControlFlow()
-                    }
-                }
-                endControlFlow()
-                addStatement("$L ++", colIndexVar)
+            scope.builder().addStatement("final $T $L", entity.typeName, entityVar)
+            val fieldsWithIndices = entity.fields.map {
+                val indexVar = scope.getTmpVar(
+                        "_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+                scope.builder().addStatement("final $T $L = $N.getColumnIndex($S)",
+                        TypeName.INT, indexVar, cursorParam, it.columnName)
+                FieldWithIndex(field = it,
+                        indexVar = indexVar,
+                        alwaysExists = false)
             }
-            endControlFlow()
-            // assign parents
-            assignParents(entityVar, parentNotNullVars, sortedParents, scope)
+            FieldReadWriteWriter.readFromCursor(
+                    outVar = entityVar,
+                    outPojo = entity,
+                    cursorVar = cursorParam.name,
+                    fieldsWithIndices = fieldsWithIndices,
+                    scope = scope)
             addStatement("return $L", entityVar)
         }
         return scope.builder().build()
     }
-
-    private fun declareParents(parentVars: Map<DecomposedField, String>,
-                               scope: CodeGenScope): Map<DecomposedField, String> {
-        val parentNotNullVars = hashMapOf<DecomposedField, String>()
-        scope.builder().apply {
-            parentVars.forEach {
-                addStatement("final $T $L = new $T()", it.key.pojo.typeName,
-                        it.value, it.key.pojo.typeName)
-                val notNullVar = scope.getTmpVar("_notNull${it.key.field.name}")
-                parentNotNullVars[it.key] = notNullVar
-                addStatement("$T $L = false", TypeName.BOOLEAN, notNullVar)
-            }
-        }
-        return parentNotNullVars
-    }
-
-    private fun assignParents(entityVar: String, parentNotNullVars: Map<DecomposedField, String>,
-                              sortedParents: Map<DecomposedField, String>, scope: CodeGenScope) {
-        scope.builder().apply {
-            sortedParents.forEach {
-                val parent = it.key
-                val varName = it.value
-                val allNotNullVars = parent.pojo.fields
-                        .map { parentNotNullVars[it.parent] }.distinct()
-                val ifCheck = allNotNullVars.joinToString(" || ")
-                beginControlFlow("if ($L)", ifCheck).apply {
-                    val grandParentVar = parent.parent?.let {
-                        sortedParents[it]
-                    } ?: entityVar
-                    parent.field.setter.writeSet(grandParentVar, varName, this)
-                }
-                endControlFlow()
-            }
-        }
-    }
-
-    private fun readField(field: Field, cursorParam: ParameterSpec,
-                          indexVar: String, ownerVar: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            FieldReadWriteWriter(FieldWithIndex(field, indexVar)).readFromCursor(
-                    ownerVar = ownerVar,
-                    cursorVar = cursorParam.name,
-                    scope = scope
-            )
-        }
-    }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt
index 5b323ac..c7f8930 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityDeletionAdapterWriter.kt
@@ -58,9 +58,7 @@
                 addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
                 returns(TypeName.VOID)
                 addModifiers(PUBLIC)
-                val mapped = entity.primaryKey.fields.mapIndexed { index, field ->
-                    FieldWithIndex(field, "${index + 1}")
-                }
+                val mapped = FieldWithIndex.byOrder(entity.primaryKey.fields)
                 FieldReadWriteWriter.bindToStatement(ownerVar = valueParam,
                         stmtParamVar = stmtParam,
                         fieldsWithIndices = mapped,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt
index bd031a4..ab2803f 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityInsertionAdapterWriter.kt
@@ -82,9 +82,7 @@
                 addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
                 returns(TypeName.VOID)
                 addModifiers(PUBLIC)
-                val mapped = entity.fields.mapIndexed { index, field ->
-                    FieldWithIndex(field, "${index + 1}")
-                }
+                val mapped = FieldWithIndex.byOrder(entity.fields)
                 FieldReadWriteWriter.bindToStatement(
                         ownerVar = valueParam,
                         stmtParamVar = stmtParam,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityUpdateAdapterWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityUpdateAdapterWriter.kt
index 957f2d3..f0b2e58 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityUpdateAdapterWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/EntityUpdateAdapterWriter.kt
@@ -60,9 +60,7 @@
                 addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
                 returns(TypeName.VOID)
                 addModifiers(PUBLIC)
-                val mappedField = entity.fields.mapIndexed { index, field ->
-                    FieldWithIndex(field, "${index + 1}")
-                }
+                val mappedField = FieldWithIndex.byOrder(entity.fields)
                 FieldReadWriteWriter.bindToStatement(
                         ownerVar = valueParam,
                         stmtParamVar = stmtParam,
@@ -71,7 +69,9 @@
                 )
                 val pkeyStart = entity.fields.size
                 val mappedPrimaryKeys = entity.primaryKey.fields.mapIndexed { index, field ->
-                    FieldWithIndex(field, "${pkeyStart + index + 1}")
+                    FieldWithIndex(field = field,
+                            indexVar = "${pkeyStart + index + 1}",
+                            alwaysExists = true)
                 }
                 FieldReadWriteWriter.bindToStatement(
                         ownerVar = valueParam,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/writer/FieldReadWriteWriter.kt b/room/compiler/src/main/kotlin/com/android/support/room/writer/FieldReadWriteWriter.kt
index e041e9f..2ae9d5f 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/writer/FieldReadWriteWriter.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/writer/FieldReadWriteWriter.kt
@@ -18,12 +18,16 @@
 
 import com.android.support.room.ext.L
 import com.android.support.room.ext.T
+import com.android.support.room.ext.defaultValue
 import com.android.support.room.ext.typeName
 import com.android.support.room.solver.CodeGenScope
 import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Constructor
 import com.android.support.room.vo.Field
 import com.android.support.room.vo.DecomposedField
 import com.android.support.room.vo.FieldWithIndex
+import com.android.support.room.vo.Pojo
+import com.squareup.javapoet.TypeName
 
 /**
  * Handles writing a field into statement or reading it form statement.
@@ -31,6 +35,7 @@
 class FieldReadWriteWriter(fieldWithIndex: FieldWithIndex) {
     val field = fieldWithIndex.field
     val indexVar = fieldWithIndex.indexVar
+    val alwaysExists = fieldWithIndex.alwaysExists
 
     companion object {
         /*
@@ -122,50 +127,132 @@
             visitNode(createNodeTree(ownerVar, fieldsWithIndices, scope))
         }
 
-        fun readFromCursor(ownerVar: String, cursorVar: String,
+        /**
+         * Just constructs the given item, does NOT DECLARE. Declaration happens outside the
+         * reading statement since we may never read if the cursor does not have necessary
+         * columns.
+         */
+        private fun construct(outVar : String, constructor : Constructor?, typeName : TypeName,
+                              localVariableNames : Map<String, FieldWithIndex>,
+                              localDecomposeds : List<Node>, scope: CodeGenScope) {
+            if (constructor == null) {
+                // best hope code generation
+                scope.builder().apply {
+                    addStatement("$L = new $T()", outVar, typeName)
+                }
+                return
+            }
+            val variableNames = constructor.params.map { param ->
+                when(param) {
+                    is Constructor.FieldParam -> localVariableNames.entries.firstOrNull {
+                        it.value.field === param.field
+                    }?.key
+                    is Constructor.DecomposedParam -> localDecomposeds.firstOrNull {
+                        it.fieldParent === param.decomposed
+                    }?.varName
+                    else -> null
+                }
+            }
+            val args = variableNames.joinToString(",") { it ?: "null"}
+            scope.builder().apply {
+                addStatement("$L = new $T($L)", outVar, typeName, args)
+            }
+        }
+
+        /**
+         * Reads the row into the given variable. It does not declare it but constructs it.
+         */
+        fun readFromCursor(outVar: String,
+                           outPojo : Pojo,
+                           cursorVar: String,
                            fieldsWithIndices: List<FieldWithIndex>,
                            scope: CodeGenScope) {
             fun visitNode(node: Node) {
                 val fieldParent = node.fieldParent
                 fun readNode() {
-                    if (fieldParent != null) {
-                        scope.builder()
-                                .addStatement("final $T $L = new $T()", fieldParent.pojo.typeName,
-                                        node.varName, fieldParent.pojo.typeName)
+                    // read constructor parameters into local fields
+                    val constructorFields = node.directFields.filter {
+                        it.field.setter.callType == CallType.CONSTRUCTOR
+                    }.associateBy { fwi ->
+                        FieldReadWriteWriter(fwi).readIntoTmpVar(cursorVar, scope)
                     }
-                    node.directFields.forEach {
-                        FieldReadWriteWriter(it).readFromCursor(node.varName, cursorVar, scope)
-                    }
+                    // read decomposed fields
                     node.subNodes.forEach(::visitNode)
+                    // construct the object
                     if (fieldParent != null) {
-                        fieldParent.setter.writeSet(
-                                ownerVar = node.parentNode!!.varName,
-                                inVar = node.varName,
-                                builder = scope.builder()
-                        )
+                        construct(outVar = node.varName,
+                                constructor = fieldParent.pojo.constructor,
+                                typeName = fieldParent.field.typeName,
+                                localDecomposeds = node.subNodes,
+                                localVariableNames = constructorFields,
+                                scope = scope)
+                    } else {
+                        construct(outVar = node.varName,
+                                constructor = outPojo.constructor,
+                                typeName = outPojo.typeName,
+                                localDecomposeds = node.subNodes,
+                                localVariableNames = constructorFields,
+                                scope = scope)
+                    }
+                    // ready any field that was not part of the constructor
+                    node.directFields.filterNot {
+                        it.field.setter.callType == CallType.CONSTRUCTOR
+                    }.forEach { fwi ->
+                        FieldReadWriteWriter(fwi).readFromCursor(
+                                ownerVar = node.varName,
+                                cursorVar = cursorVar,
+                                scope = scope)
+                    }
+                    // assign sub modes to fields if they were not part of the constructor.
+                    node.subNodes.map {
+                        val setter = it.fieldParent?.setter
+                        if (setter != null && setter.callType != CallType.CONSTRUCTOR) {
+                            Pair(it.varName, setter)
+                        } else {
+                            null
+                        }
+                    }.filterNotNull().forEach { pair ->
+                        val varName = pair.first
+                        val setter = pair.second
+                        setter.writeSet(
+                                ownerVar = node.varName,
+                                inVar = varName,
+                                builder = scope.builder())
                     }
                 }
                 if (fieldParent == null) {
                     // root element
+                    // always declared by the caller so we don't declare this
                     readNode()
                 } else {
+                    // always declare, we'll set below
+                    scope.builder().addStatement("final $T $L", fieldParent.pojo.typeName,
+                                        node.varName)
                     if (fieldParent.nonNull) {
                         readNode()
                     } else {
                         val myDescendants = node.allFields()
                         val allNullCheck = myDescendants.joinToString(" && ") {
-                            "$cursorVar.isNull(${it.indexVar})"
+                            if (it.alwaysExists) {
+                                "$cursorVar.isNull(${it.indexVar})"
+                            } else {
+                                "( ${it.indexVar} == -1 || $cursorVar.isNull(${it.indexVar}))"
+                            }
+
                         }
                         scope.builder().apply {
                             beginControlFlow("if (! ($L))", allNullCheck).apply {
                                 readNode()
                             }
+                            nextControlFlow(" else ").apply {
+                                addStatement("$L = null", node.varName)
+                            }
                             endControlFlow()
                         }
                     }
                 }
             }
-            visitNode(createNodeTree(ownerVar, fieldsWithIndices, scope))
+            visitNode(createNodeTree(outVar, fieldsWithIndices, scope))
         }
     }
 
@@ -192,23 +279,61 @@
      * @param cursorVar The cursor variable
      * @param scope The code generation scope
      */
-    fun readFromCursor(ownerVar: String, cursorVar: String, scope: CodeGenScope) {
-        field.cursorValueReader?.let { reader ->
-            scope.builder().apply {
-                when (field.setter.callType) {
-                    CallType.FIELD -> {
-                        reader.readFromCursor("$ownerVar.${field.getter.name}", cursorVar,
-                                indexVar, scope)
-                    }
-                    CallType.METHOD -> {
-                        val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
-                        addStatement("final $T $L", field.getter.type.typeName(), tmpField)
-                        reader.readFromCursor(tmpField, cursorVar, indexVar, scope)
-                        addStatement("$L.$L($L)", ownerVar, field.setter.name, tmpField)
+    private fun readFromCursor(ownerVar: String, cursorVar: String, scope: CodeGenScope) {
+        fun toRead() {
+            field.cursorValueReader?.let { reader ->
+                scope.builder().apply {
+                    when (field.setter.callType) {
+                        CallType.FIELD -> {
+                            reader.readFromCursor("$ownerVar.${field.getter.name}", cursorVar,
+                                    indexVar, scope)
+                        }
+                        CallType.METHOD -> {
+                            val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+                            addStatement("final $T $L", field.getter.type.typeName(), tmpField)
+                            reader.readFromCursor(tmpField, cursorVar, indexVar, scope)
+                            addStatement("$L.$L($L)", ownerVar, field.setter.name, tmpField)
+                        }
+                        CallType.CONSTRUCTOR -> {
+                            // no-op
+                        }
                     }
                 }
             }
         }
+        if (alwaysExists) {
+            toRead()
+        } else {
+            scope.builder().apply {
+                beginControlFlow("if ($L != -1)", indexVar).apply {
+                    toRead()
+                }
+                endControlFlow()
+            }
+        }
+    }
+
+    /**
+     * Reads the value into a temporary local variable.
+     */
+    fun readIntoTmpVar(cursorVar: String, scope: CodeGenScope) : String {
+        val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+        val typeName = field.getter.type.typeName()
+        scope.builder().apply {
+            addStatement("final $T $L", typeName, tmpField)
+            if (alwaysExists) {
+                field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
+            } else {
+                beginControlFlow("if ($L == -1)", indexVar).apply {
+                    addStatement("$L = $L", tmpField, typeName.defaultValue())
+                }
+                nextControlFlow("else").apply {
+                    field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
+                }
+                endControlFlow()
+            }
+        }
+        return tmpField
     }
 
     /**
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index 1b955cd..bbf05dd 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -54,9 +54,19 @@
         _statement.bindLong(_argIndex, id);
         final Cursor _cursor = __db.query(_statement);
         try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
             final User _result;
             if(_cursor.moveToFirst()) {
-                _result = __entityCursorConverter_fooBarUser(_cursor);
+                _result = new User();
+                _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                _result.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _result.setLastName(_tmpLastName);
+                _result.age = _cursor.getInt(_cursorIndexOfAge);
             } else {
                 _result = null;
             }
@@ -85,9 +95,19 @@
         }
         final Cursor _cursor = __db.query(_statement);
         try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
             final User _result;
             if(_cursor.moveToFirst()) {
-                _result = __entityCursorConverter_fooBarUser(_cursor);
+                _result = new User();
+                _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                _result.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _result.setLastName(_tmpLastName);
+                _result.age = _cursor.getInt(_cursorIndexOfAge);
             } else {
                 _result = null;
             }
@@ -115,10 +135,20 @@
         }
         final Cursor _cursor = __db.query(_statement);
         try {
+            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
             final List<User> _result = new ArrayList<User>(_cursor.getCount());
             while(_cursor.moveToNext()) {
                 final User _item_1;
-                _item_1 = __entityCursorConverter_fooBarUser(_cursor);
+                _item_1 = new User();
+                _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+                _item_1.name = _cursor.getString(_cursorIndexOfName);
+                final String _tmpLastName;
+                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                _item_1.setLastName(_tmpLastName);
+                _item_1.age = _cursor.getInt(_cursorIndexOfAge);
                 _result.add(_item_1);
             }
             return _result;
@@ -241,9 +271,19 @@
                 }
                 final Cursor _cursor = __db.query(_statement);
                 try {
+                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
                     final User _result;
                     if(_cursor.moveToFirst()) {
-                        _result = __entityCursorConverter_fooBarUser(_cursor);
+                        _result = new User();
+                        _result.uid = _cursor.getInt(_cursorIndexOfUid);
+                        _result.name = _cursor.getString(_cursorIndexOfName);
+                        final String _tmpLastName;
+                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                        _result.setLastName(_tmpLastName);
+                        _result.age = _cursor.getInt(_cursorIndexOfAge);
                     } else {
                         _result = null;
                     }
@@ -291,10 +331,20 @@
                 }
                 final Cursor _cursor = __db.query(_statement);
                 try {
+                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
                     final List<User> _result = new ArrayList<User>(_cursor.getCount());
                     while(_cursor.moveToNext()) {
                         final User _item_1;
-                        _item_1 = __entityCursorConverter_fooBarUser(_cursor);
+                        _item_1 = new User();
+                        _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+                        _item_1.name = _cursor.getString(_cursorIndexOfName);
+                        final String _tmpLastName;
+                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+                        _item_1.setLastName(_tmpLastName);
+                        _item_1.age = _cursor.getInt(_cursorIndexOfAge);
                         _result.add(_item_1);
                     }
                     return _result;
@@ -309,37 +359,4 @@
             }
         }.getLiveData();
     }
-
-    private User __entityCursorConverter_fooBarUser(Cursor cursor) {
-        User _entity = new User();
-        int _columnIndex = 0;
-        for (String _columnName : cursor.getColumnNames()) {
-            switch(_columnName.hashCode()) {
-                case 115792: {
-                    if ("uid".equals(_columnName)) {
-                        _entity.uid = cursor.getInt(_columnIndex);
-                    }
-                }
-                case 3373707: {
-                    if ("name".equals(_columnName)) {
-                        _entity.name = cursor.getString(_columnIndex);
-                    }
-                }
-                case -1459599807: {
-                    if ("lastName".equals(_columnName)) {
-                        final String _tmpLastName;
-                        _tmpLastName = cursor.getString(_columnIndex);
-                        _entity.setLastName(_tmpLastName);
-                    }
-                }
-                case 1358970165: {
-                    if ("ageColumn".equals(_columnName)) {
-                        _entity.age = cursor.getInt(_columnIndex);
-                    }
-                }
-            }
-            _columnIndex ++;
-        }
-        return _entity;
-    }
 }
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt
index f1af084..3a3e952 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt
@@ -539,8 +539,25 @@
     fun cache_entity() {
         singleDb("""
                 @Database(entities = {User.class}, version = 42)
+                @SkipQueryVerification
                 public abstract class MyDb extends RoomDatabase {
-                    public abstract UserDao userDao();
+                    public abstract MyUserDao userDao();
+                    @Dao
+                    interface MyUserDao {
+                        @Insert
+                        public void insert(User... users);
+
+                        @Query("SELECT * FROM user where uid = ?")
+                        public User loadOne(int uid);
+
+                        @TypeConverters(Converter.class)
+                        @Query("SELECT * FROM user where uid = ?")
+                        public User loadWithConverter(int uid);
+                    }
+                    public static class Converter {
+                        @TypeConverter
+                        public static java.util.Date foo(Long input) {return null;}
+                    }
                 }
                 """, USER, USER_DAO){ db, invocation ->
             val userDao = db.daoMethods.first().dao
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/PojoProcessorTest.kt
index dbde69a..cec2d36 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/PojoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/PojoProcessorTest.kt
@@ -25,6 +25,8 @@
 import com.android.support.room.processor.ProcessorErrors.relationCannotFindEntityField
 import com.android.support.room.processor.ProcessorErrors.relationCannotFindParentEntityField
 import com.android.support.room.testing.TestInvocation
+import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Constructor
 import com.android.support.room.vo.DecomposedField
 import com.android.support.room.vo.Field
 import com.android.support.room.vo.Pojo
@@ -34,6 +36,7 @@
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeName
 import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
 import org.hamcrest.CoreMatchers.not
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.CoreMatchers.nullValue
@@ -452,6 +455,159 @@
         }.compilesWithoutError()
     }
 
+    @Test
+    fun constructor_empty() {
+        val pojo = """
+            public String mName;
+            """
+        singleRun(pojo) { pojo ->
+            assertThat(pojo.constructor, notNullValue())
+            assertThat(pojo.constructor?.params, `is`(emptyList<Constructor.Param>()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_twoFieldsExcatMatch() {
+        val pojo = """
+            public String mName;
+            public String _name;
+            public MyPojo(String mName) {
+            }
+            """
+        singleRun(pojo) { pojo ->
+            val param = pojo.constructor?.params?.first()
+            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
+            assertThat((param as Constructor.FieldParam).field.name,  `is`("mName"))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_oneTypeMatches() {
+        val pojo = """
+            public String mName;
+            public int _name;
+            public MyPojo(String name) {
+            }
+            """
+        singleRun(pojo) { pojo ->
+            val param = pojo.constructor?.params?.first()
+            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
+            assertThat((param as Constructor.FieldParam).field.name,  `is`("mName"))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_twoFields() {
+        val pojo = """
+            String mName;
+            String _name;
+            public MyPojo(String name) {
+            }
+            """
+        singleRun(pojo) { pojo ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.ambigiousConstructor(MY_POJO.toString(),
+                        "name", listOf("mName", "_name"))
+        )
+    }
+
+    @Test
+    fun constructor_noMatchBadType() {
+        singleRun("""
+            int foo;
+            public MyPojo(String foo) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_noMatch() {
+        singleRun("""
+            String mName;
+            String _name;
+            public MyPojo(String foo) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_noMatchMultiArg() {
+        singleRun("""
+            String mName;
+            int bar;
+            public MyPojo(String foo, String name) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_multipleMatching() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            public MyPojo(String name) {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { pojo ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
+    }
+
+    @Test
+    fun constructor_multipleMatchingWithIgnored() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            @Ignore
+            public MyPojo(String name) {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { pojo ->
+            assertThat(pojo.constructor, notNullValue())
+            assertThat(pojo.constructor?.params?.size, `is`(2))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+            assertThat(pojo.fields.find { it.name == "mLastName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_dontTryForBindToScope() {
+        singleRun("""
+            String mName;
+            String mLastName;
+        """) { pojo, invocation ->
+            val process2 = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
+                    parent = null).process()
+            assertThat(process2.constructor, nullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_bindForTwoWay() {
+        singleRun("""
+            String mName;
+            String mLastName;
+        """) { pojo, invocation ->
+            val process2 = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                    parent = null).process()
+            assertThat(process2.constructor, notNullValue())
+        }.compilesWithoutError()
+    }
+
     fun singleRun(code: String, vararg jfos:JavaFileObject, handler: (Pojo) -> Unit)
             : CompileTester {
         return singleRun(code, *jfos) { pojo, invocation ->
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt
index 8d08887..54ae061 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/verifier/DatabaseVerifierTest.kt
@@ -22,6 +22,7 @@
 import com.android.support.room.processor.Context
 import com.android.support.room.testing.TestInvocation
 import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Constructor
 import com.android.support.room.vo.Database
 import com.android.support.room.vo.Entity
 import com.android.support.room.vo.Field
@@ -39,6 +40,7 @@
 import simpleRun
 import java.sql.Connection
 import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
 import javax.lang.model.element.TypeElement
 import javax.lang.model.type.DeclaredType
 import javax.lang.model.type.PrimitiveType
@@ -195,7 +197,8 @@
                 decomposedFields = emptyList(),
                 indices = emptyList(),
                 primaryKey = PrimaryKey(null, fields.take(1), false),
-                foreignKeys = emptyList()
+                foreignKeys = emptyList(),
+                constructor = Constructor(mock(ExecutableElement::class.java), emptyList())
         )
     }
 
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt
index 393613f..3766328 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/writer/EntityCursorConverterWriterTest.kt
@@ -32,7 +32,6 @@
         val OUT_PREFIX = """
             package foo.bar;
             import android.database.Cursor;
-            import java.lang.String;
             public class MyContainerClass {
             """.trimIndent()
         const val OUT_SUFFIX = "}"
@@ -52,34 +51,25 @@
                 """,
                 """
                 private MyEntity __entityCursorConverter_fooBarMyEntity(Cursor cursor) {
-                  MyEntity _entity = new MyEntity();
-                  int _columnIndex = 0;
-                  for (String _columnName : cursor.getColumnNames()) {
-                    switch(_columnName.hashCode()) {
-                     case ${"id".hashCode()}: {
-                        if ("id".equals(_columnName)) {
-                          final int _tmpId;
-                          _tmpId = cursor.getInt(_columnIndex);
-                          _entity.setId(_tmpId);
-                        }
-                      }
-                      case ${"name".hashCode()}: {
-                        if ("name".equals(_columnName)) {
-                          _entity.name = cursor.getString(_columnIndex);
-                        }
-                      }
-                      case ${"lastName".hashCode()}: {
-                        if ("lastName".equals(_columnName)) {
-                          _entity.lastName = cursor.getString(_columnIndex);
-                        }
-                      }
-                      case ${"age".hashCode()}: {
-                        if ("age".equals(_columnName)) {
-                          _entity.age = cursor.getInt(_columnIndex);
-                        }
-                      }
-                    }
-                    _columnIndex ++;
+                  final MyEntity _entity;
+                  final int _cursorIndexOfId = cursor.getColumnIndex("id");
+                  final int _cursorIndexOfName = cursor.getColumnIndex("name");
+                  final int _cursorIndexOfLastName = cursor.getColumnIndex("lastName");
+                  final int _cursorIndexOfAge = cursor.getColumnIndex("age");
+                  _entity = new MyEntity();
+                  if (_cursorIndexOfId != -1) {
+                    final int _tmpId;
+                    _tmpId = cursor.getInt(_cursorIndexOfId);
+                    _entity.setId(_tmpId);
+                  }
+                  if (_cursorIndexOfName != -1) {
+                    _entity.name = cursor.getString(_cursorIndexOfName);
+                  }
+                  if (_cursorIndexOfLastName != -1) {
+                    _entity.lastName = cursor.getString(_cursorIndexOfLastName);
+                  }
+                  if (_cursorIndexOfAge != -1) {
+                    _entity.age = cursor.getInt(_cursorIndexOfAge);
                   }
                   return _entity;
                 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java
index 29b97ce..f3cb500 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/TestDatabase.java
@@ -61,5 +61,4 @@
             }
         }
     }
-
 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ConstructorTest.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ConstructorTest.java
new file mode 100644
index 0000000..52b0184
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ConstructorTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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 com.android.support.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.support.room.Dao;
+import com.android.support.room.Database;
+import com.android.support.room.Decompose;
+import com.android.support.room.Entity;
+import com.android.support.room.Insert;
+import com.android.support.room.PrimaryKey;
+import com.android.support.room.Query;
+import com.android.support.room.Room;
+import com.android.support.room.RoomDatabase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("SqlNoDataSourceInspection")
+@SmallTest
+public class ConstructorTest {
+    @Database(version = 1, entities = {FullConstructor.class, PartialConstructor.class},
+            exportSchema = false)
+    abstract static class MyDb extends RoomDatabase {
+        abstract MyDao dao();
+    }
+
+    @Dao
+    interface MyDao {
+        @Insert
+        void insertFull(FullConstructor... full);
+
+        @Query("SELECT * FROM fc WHERE a = :a")
+        FullConstructor loadFull(int a);
+
+        @Insert
+        void insertPartial(PartialConstructor... partial);
+
+        @Query("SELECT * FROM pc WHERE a = :a")
+        PartialConstructor loadPartial(int a);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(tableName = "fc")
+    static class FullConstructor {
+        @PrimaryKey
+        public final int a;
+        public final int b;
+        @Decompose
+        public final MyDecomposed decomposed;
+
+        FullConstructor(int a, int b, MyDecomposed decomposed) {
+            this.a = a;
+            this.b = b;
+            this.decomposed = decomposed;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            FullConstructor that = (FullConstructor) o;
+
+            if (a != that.a) return false;
+            //noinspection SimplifiableIfStatement
+            if (b != that.b) return false;
+            return decomposed != null ? decomposed.equals(that.decomposed)
+                    : that.decomposed == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = a;
+            result = 31 * result + b;
+            result = 31 * result + (decomposed != null ? decomposed.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(tableName = "pc")
+    static class PartialConstructor {
+        @PrimaryKey
+        public final int a;
+        public int b;
+        @Decompose
+        private MyDecomposed mDecomposed;
+
+        PartialConstructor(int a) {
+            this.a = a;
+        }
+
+        public MyDecomposed getDecomposed() {
+            return mDecomposed;
+        }
+
+        public void setDecomposed(MyDecomposed decomposed) {
+            mDecomposed = decomposed;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            PartialConstructor that = (PartialConstructor) o;
+
+            if (a != that.a) return false;
+            //noinspection SimplifiableIfStatement
+            if (b != that.b) return false;
+            return mDecomposed != null ? mDecomposed.equals(that.mDecomposed)
+                    : that.mDecomposed == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = a;
+            result = 31 * result + b;
+            result = 31 * result + (mDecomposed != null ? mDecomposed.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class MyDecomposed {
+        public final String text;
+
+        MyDecomposed(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            MyDecomposed that = (MyDecomposed) o;
+
+            return text != null ? text.equals(that.text) : that.text == null;
+        }
+
+        @Override
+        public int hashCode() {
+            return text != null ? text.hashCode() : 0;
+        }
+    }
+
+    private MyDb mDb;
+    private MyDao mDao;
+
+    @Before
+    public void init() {
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), MyDb.class)
+                .build();
+        mDao = mDb.dao();
+    }
+
+    @Test
+    public void insertAndReadFullConstructor() {
+        FullConstructor inserted = new FullConstructor(1, 2, null);
+        mDao.insertFull(inserted);
+        final FullConstructor load = mDao.loadFull(1);
+        assertThat(load, is(inserted));
+    }
+
+    @Test
+    public void insertAndReadPartial() {
+        PartialConstructor item = new PartialConstructor(3);
+        item.b = 7;
+        mDao.insertPartial(item);
+        PartialConstructor load = mDao.loadPartial(3);
+        assertThat(load, is(item));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ForeignKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ForeignKeyTest.java
index 40ba336..3f30d4b 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ForeignKeyTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/ForeignKeyTest.java
@@ -33,6 +33,7 @@
 import com.android.support.room.Delete;
 import com.android.support.room.Entity;
 import com.android.support.room.ForeignKey;
+import com.android.support.room.Ignore;
 import com.android.support.room.Index;
 import com.android.support.room.Insert;
 import com.android.support.room.PrimaryKey;
@@ -111,13 +112,11 @@
         public String name;
         public String lastName;
 
-        A() {
-        }
-
         A(String name) {
             this.name = name;
         }
 
+        @Ignore
         A(String name, String lastName) {
             this.name = name;
             this.lastName = lastName;
@@ -135,9 +134,6 @@
         public int id;
         public String aName;
 
-        B() {
-        }
-
         B(String aName) {
             this.aName = aName;
         }
@@ -154,9 +150,6 @@
         public int id;
         public String aName;
 
-        C() {
-        }
-
         C(String aName) {
             this.aName = aName;
         }
@@ -174,9 +167,6 @@
         public int id;
         public String aName;
 
-        D() {
-        }
-
         D(String aName) {
             this.aName = aName;
         }
@@ -198,6 +188,7 @@
         E() {
         }
 
+        @Ignore
         E(String aName, String aLastName) {
             this.aName = aName;
             this.aLastName = aLastName;
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java
index a9d93d1..6eed2c3 100644
--- a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/AvgWeightByAge.java
@@ -17,6 +17,7 @@
 package com.android.support.room.integration.testapp.vo;
 
 import com.android.support.room.ColumnInfo;
+import com.android.support.room.Ignore;
 
 @SuppressWarnings("unused")
 public class AvgWeightByAge {
@@ -29,6 +30,7 @@
     public AvgWeightByAge() {
     }
 
+    @Ignore
     public AvgWeightByAge(int age, float weight) {
         mAge = age;
         mWeight = weight;