Primary Key improvements

This CL changes @PrimaryKey sytax to disallow multiple fields with
@PrimaryKey annotations. Instead, it enforces using
@Entity#primaryKeys to declare composite primary keys.

This CL also fixes the inheritance rules around PrimaryKeys such that
we'll inherit primary key from super Entity / Pojo if the Entity itself
does not declare any primary keys. If the entity declares a primary
key, the parent key is dropped with a NOTE level log.

Bug: 35706557
Test: EntityProcessorTest#primaryKey*, PrimaryKeyTest.kt
Test: SQLiteOpenHelperWriterTest.kt
Change-Id: Ifcfccf29da21395586e539d0f9b130c7537e5384
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 7f83ad8..9da90f9 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
@@ -71,4 +71,15 @@
      *         false otherwise. Defaults to false.
      */
     boolean inheritSuperIndices() default false;
+
+    /**
+     * The list of Primary Key column names.
+     * <p>
+     * If you would like to define an auto generated primary key, you can use {@link PrimaryKey}
+     * annotation on the field with {@link PrimaryKey#autoGenerate()} set to {@code true}.
+     *
+     * @return The primary key of this Entity. Can be empty if the class has a field annotated
+     * with {@link PrimaryKey}.
+     */
+    String[] primaryKeys() default {};
 }
diff --git a/room/common/src/main/java/com/android/support/room/PrimaryKey.java b/room/common/src/main/java/com/android/support/room/PrimaryKey.java
index 36c8ce5..1e40a03 100644
--- a/room/common/src/main/java/com/android/support/room/PrimaryKey.java
+++ b/room/common/src/main/java/com/android/support/room/PrimaryKey.java
@@ -22,11 +22,37 @@
 import java.lang.annotation.Target;
 
 /**
- * Marks a field in an {@link Entity} as the primary key. If there are more than one field annotated
- * with {@link PrimaryKey}, Room creates a composite primary key. The order of the fields in the
- * composite primary key matches their declaration order in the {@link Entity} class.
+ * Marks a field in an {@link Entity} as the primary key.
+ * <p>
+ * If you would like to define a composite primary key, you should use {@link Entity#primaryKeys()}
+ * method.
+ * <p>
+ * Each {@link Entity} must declare a primary key unless one of its super classes declares a
+ * primary key. If both an {@link Entity} and its super class defines a {@code PrimaryKey}, the
+ * child's {@code PrimaryKey} definition will override the parent's {@code PrimaryKey}.
+ * <p>
+ * If {@code PrimaryKey} annotation is used on a {@link Decompose}d field, all columns inherited
+ * from that decomposed field becomes the composite primary key (including its grand children
+ * fields).
  */
 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.CLASS)
 public @interface PrimaryKey {
+    /**
+     * Set to true to let SQLite generate the unique id.
+     * <p>
+     * When set to {@code true}, the SQLite type affinity for the field should be {@code INTEGER}.
+     * <p>
+     * If the field type is {@code long} or {@code int} (or its TypeConverter converts it to a
+     * {@code long} or {@code int}), {@link Insert} methods treat {@code 0} as not-set while
+     * inserting the item.
+     * <p>
+     * If the field's type is {@link Integer} or {@link Long} (or its TypeConverter converts it to
+     * an {@link Integer} or a {@link Long}), {@link Insert} methods treat {@code null} as
+     * not-set while inserting the item.
+     *
+     * @return Whether the primary key should be auto-generated by SQLite or not. Defaults
+     * to false.
+     */
+    boolean autoGenerate() default false;
 }
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 8319cdd..22b31a3 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
@@ -19,11 +19,14 @@
 import com.android.support.room.ext.getAsBoolean
 import com.android.support.room.ext.getAsString
 import com.android.support.room.ext.getAsStringList
-import com.android.support.room.ext.hasAnnotation
+import com.android.support.room.parser.SQLTypeAffinity
 import com.android.support.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
+import com.android.support.room.vo.DecomposedField
 import com.android.support.room.vo.Entity
+import com.android.support.room.vo.Field
 import com.android.support.room.vo.Index
 import com.android.support.room.vo.Pojo
+import com.android.support.room.vo.PrimaryKey
 import com.android.support.room.vo.Warning
 import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
@@ -99,17 +102,157 @@
         val indices = entityIndices + fieldIndices + superIndices
         validateIndices(indices, pojo)
 
+        val primaryKey = findPrimaryKey(pojo.fields, pojo.decomposedFields)
+        val affinity = primaryKey.fields.firstOrNull()?.affinity ?: SQLTypeAffinity.TEXT
+        context.checker.check(
+                !primaryKey.autoGenerateId || affinity == SQLTypeAffinity.INTEGER,
+                primaryKey.fields.firstOrNull()?.element ?: element,
+                ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT
+        )
         val entity = Entity(element = element,
                 tableName = tableName,
                 type = pojo.type,
                 fields = pojo.fields,
                 decomposedFields = pojo.decomposedFields,
-                indices = indices)
-        context.checker.check(entity.primaryKeys.isNotEmpty(), element,
-                ProcessorErrors.MISSING_PRIMARY_KEY)
+                indices = indices,
+                primaryKey = primaryKey)
+
         return entity
     }
 
+    private fun findPrimaryKey(fields: List<Field>, decomposedFields: List<DecomposedField>)
+            : PrimaryKey {
+        val candidates = collectPrimaryKeysFromEntityAnnotations(element, fields) +
+                collectPrimaryKeysFromPrimaryKeyAnnotations(fields) +
+                collectPrimaryKeysFromDecomposedFields(decomposedFields)
+
+        context.checker.check(candidates.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY)
+        if (candidates.size == 1) {
+            // easy :)
+            return candidates.first()
+        }
+
+        return choosePrimaryKey(candidates, element)
+    }
+
+    /**
+     * Check fields for @PrimaryKey.
+     */
+    private fun collectPrimaryKeysFromPrimaryKeyAnnotations(fields: List<Field>): List<PrimaryKey> {
+        return fields.map { field ->
+            MoreElements.getAnnotationMirror(field.element,
+                    com.android.support.room.PrimaryKey::class.java).orNull()?.let {
+                if (field.parent != null) {
+                    // the field in the entity that contains this error.
+                    val grandParentField = field.parent.rootParent.field.element
+                    // bound for entity.
+                    context.fork(grandParentField).logger.w(
+                            Warning.PRIMARY_KEY_FROM_DECOMPOSED_IS_DROPPED,
+                            grandParentField,
+                            ProcessorErrors.decomposedPrimaryKeyIsDropped(
+                                    element.qualifiedName.toString(), field.name))
+                    null
+                } else {
+                    PrimaryKey(declaredIn = field.element.enclosingElement,
+                            fields = listOf(field),
+                            autoGenerateId = AnnotationMirrors
+                                    .getAnnotationValue(it, "autoGenerate")
+                                    .getAsBoolean(false))
+                }
+            }
+        }.filterNotNull()
+    }
+
+    /**
+     * Check classes for @Entity(primaryKeys = ?).
+     */
+    private fun collectPrimaryKeysFromEntityAnnotations(typeElement: TypeElement,
+                                                        availableFields: List<Field>)
+            : List<PrimaryKey> {
+        val myPkeys = MoreElements.getAnnotationMirror(typeElement,
+                com.android.support.room.Entity::class.java).orNull()?.let {
+            val primaryKeyColumns = AnnotationMirrors.getAnnotationValue(it, "primaryKeys")
+                    .getAsStringList()
+            if (primaryKeyColumns.isEmpty()) {
+                emptyList<PrimaryKey>()
+            } else {
+                val fields = primaryKeyColumns.map { pKeyColumnName ->
+                    val field = availableFields.firstOrNull { it.columnName == pKeyColumnName }
+                    context.checker.check(field != null, typeElement,
+                            ProcessorErrors.primaryKeyColumnDoesNotExist(pKeyColumnName,
+                                    availableFields.map { it.columnName }))
+                    field
+                }.filterNotNull()
+                listOf(PrimaryKey(declaredIn = typeElement,
+                        fields = fields,
+                        autoGenerateId = false))
+            }
+        } ?: emptyList<PrimaryKey>()
+        // checks supers.
+        val mySuper = typeElement.superclass
+        val superPKeys = if (mySuper != null && mySuper.kind != TypeKind.NONE) {
+            // my super cannot see my fields so remove them.
+            val remainingFields = availableFields.filterNot {
+                it.element.enclosingElement == typeElement
+            }
+            collectPrimaryKeysFromEntityAnnotations(
+                    MoreTypes.asTypeElement(mySuper), remainingFields)
+        } else {
+            emptyList()
+        }
+        return superPKeys + myPkeys
+    }
+
+    private fun collectPrimaryKeysFromDecomposedFields(decomposedFields: List<DecomposedField>)
+            : List<PrimaryKey> {
+        return decomposedFields.map { decomposedField ->
+            MoreElements.getAnnotationMirror(decomposedField.field.element,
+                    com.android.support.room.PrimaryKey::class.java).orNull()?.let {
+                val autoGenerate = AnnotationMirrors
+                        .getAnnotationValue(it, "autoGenerate").getAsBoolean(false)
+                context.checker.check(!autoGenerate || decomposedField.pojo.fields.size == 1,
+                        decomposedField.field.element,
+                        ProcessorErrors.AUTO_INCREMENT_DECOMPOSED_HAS_MULTIPLE_FIELDS)
+                PrimaryKey(declaredIn = decomposedField.field.element.enclosingElement,
+                        fields = decomposedField.pojo.fields,
+                        autoGenerateId = autoGenerate)
+            }
+        }.filterNotNull()
+    }
+
+    // start from my element and check if anywhere in the list we can find the only well defined
+    // pkey, if so, use it.
+    private fun choosePrimaryKey(candidates: List<PrimaryKey>, typeElement: TypeElement)
+            : PrimaryKey {
+        // If 1 of these primary keys is declared in this class, then it is the winner. Just print
+        //    a note for the others.
+        // If 0 is declared, check the parent.
+        // If more than 1 primary key is declared in this class, it is an error.
+        val myPKeys = candidates.filter { candidate ->
+            candidate.declaredIn == typeElement
+        }
+        return if (myPKeys.size == 1) {
+            // just note, this is not worth an error or warning
+            (candidates - myPKeys).forEach {
+                context.logger.d(element,
+                        "${it.toHumanReadableString()} is" +
+                                " overridden by ${myPKeys.first().toHumanReadableString()}")
+            }
+            myPKeys.first()
+        } else if (myPKeys.isEmpty()) {
+            // i have not declared anything, delegate to super
+            val mySuper = typeElement.superclass
+            if (mySuper != null && mySuper.kind != TypeKind.NONE) {
+                return choosePrimaryKey(candidates, MoreTypes.asTypeElement(mySuper))
+            }
+            PrimaryKey.MISSING
+        } else {
+            context.logger.e(element, ProcessorErrors.multiplePrimaryKeyAnnotations(
+                    myPKeys.map(PrimaryKey::toHumanReadableString)))
+            PrimaryKey.MISSING
+        }
+    }
+
     private fun validateIndices(indices: List<Index>, pojo: Pojo) {
         // check for columns
         indices.forEach {
@@ -160,7 +303,9 @@
         val myIndices = MoreElements.getAnnotationMirror(parentElement,
                 com.android.support.room.Entity::class.java).orNull()?.let { annotation ->
             val indices = extractIndices(annotation, tableName = "super")
-            if (inherit) {
+            if (indices.isEmpty()) {
+                emptyList()
+            } else if (inherit) {
                 // rename them
                 indices.map {
                     Index(
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
index e0a1c54..6ae17a5 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
@@ -17,14 +17,12 @@
 package com.android.support.room.processor
 
 import com.android.support.room.ColumnInfo
-import com.android.support.room.PrimaryKey
 import com.android.support.room.ext.getAsBoolean
 import com.android.support.room.ext.getAsInt
 import com.android.support.room.ext.getAsString
 import com.android.support.room.parser.SQLTypeAffinity
 import com.android.support.room.vo.DecomposedField
 import com.android.support.room.vo.Field
-import com.android.support.room.vo.Warning
 import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
 import com.squareup.javapoet.TypeName
@@ -45,7 +43,7 @@
         val columnName: String
         val affinity : SQLTypeAffinity?
         val fieldPrefix = fieldParent?.prefix ?: ""
-        var indexed : Boolean
+        val indexed : Boolean
         if (columnInfoAnnotation.isPresent) {
             val nameInAnnotation = AnnotationMirrors
                     .getAnnotationValue(columnInfoAnnotation.get(), "name")
@@ -79,21 +77,8 @@
         context.checker.notUnbound(type, element,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
 
-        var primaryKey = MoreElements.isAnnotationPresent(element, PrimaryKey::class.java)
-
-        if (fieldParent != null && primaryKey) {
-            // bound for entity.
-            if (bindingScope == FieldProcessor.BindingScope.TWO_WAY) {
-                context.logger.w(Warning.PRIMARY_KEY_FROM_DECOMPOSED_IS_DROPPED,
-                        element, ProcessorErrors.decomposedPrimaryKeyIsDropped(
-                        fieldParent.rootParent.field.element.enclosingElement.toString(), name))
-            }
-            primaryKey = false
-        }
-
         val field = Field(name = name,
                 type = member,
-                primaryKey = primaryKey,
                 element = element,
                 columnName = columnName,
                 affinity = affinity,
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 9bee249..d85244c 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
@@ -129,8 +129,7 @@
                 it.simpleName.toString(),
                 type = context.processingEnv.typeUtils.asMemberOf(declaredType, it),
                 affinity = null,
-                parent = parent,
-                primaryKey = false)
+                parent = parent)
         val subParent = DecomposedField(
                 field = decomposedField,
                 prefix = inheritedPrefix + fieldPrefix,
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 9e95fc4..6fc9811 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
@@ -24,9 +24,11 @@
 import com.android.support.room.vo.CustomTypeConverter
 import com.android.support.room.vo.Field
 import com.squareup.javapoet.TypeName
-import javax.lang.model.element.Element
 
 object ProcessorErrors {
+    private fun String.trim(): String {
+        return this.trimIndent().replace(System.lineSeparator(), " ")
+    }
     val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
     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}"
@@ -52,6 +54,24 @@
     val CANNOT_FIND_GETTER_FOR_FIELD = "Cannot find getter for field."
     val CANNOT_FIND_SETTER_FOR_FIELD = "Cannot find setter for field."
     val MISSING_PRIMARY_KEY = "An entity must have at least 1 field annotated with @PrimaryKey"
+    val AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT = "If a primary key is annotated with" +
+            " autoGenerate, its type must be int, Integer, long or Long."
+    val AUTO_INCREMENT_DECOMPOSED_HAS_MULTIPLE_FIELDS = "When @PrimaryKey annotation is used on a" +
+            " field annotated with @Decompose, the decomposed class should have only 1 field."
+
+    fun multiplePrimaryKeyAnnotations(primaryKeys: List<String>): String {
+        return """
+                You cannot have multiple primary keys defined in an Entity. If you
+                want to declare a composite primary key, you should use @Entity#primaryKeys and
+                not use @PrimaryKey. Defined Primary Keys:
+                ${primaryKeys.joinToString(", ")}""".trim()
+    }
+
+    fun primaryKeyColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "$columnName referenced in the primary key does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
     val DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE = "Dao class must be an abstract class or" +
             " an interface"
     val DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE = "Database must be annotated with @Database"
@@ -161,26 +181,28 @@
                 these conflicting DAO methods. If you are implementing any of these to fulfill an
                 interface, don't make it abstract, instead, implement the code that calls the
                 other one.
-                """.trimIndent().replace(System.lineSeparator(), " ")
+                """.trim()
     }
 
     fun cursorPojoMismatch(pojoTypeName: TypeName,
                            unusedColumns: List<String>, allColumns: List<String>,
                            unusedFields: List<Field>, allFields: List<Field>): String {
-        val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) { """
+        val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) {
+            """
                 The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
                 use by $pojoTypeName. You can use @ColumnInfo annotation on the fields to specify
                 the mapping.
-            """.trimIndent().replace(System.lineSeparator(), " ")
+            """.trim()
         } else {
             ""
         }
-        val unusedFieldsWarning = if (unusedFields.isNotEmpty()) { """
+        val unusedFieldsWarning = if (unusedFields.isNotEmpty()) {
+            """
                 $pojoTypeName has some fields
                 [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by the
                 query. If they are not supposed to be read from the result, you can mark them with
                 @Ignore annotation.
-            """.trimIndent().replace(System.lineSeparator(), " ")
+            """.trim()
         } else {
             ""
         }
@@ -191,7 +213,7 @@
             @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH).
             Columns returned by the query: ${allColumns.joinToString(", ")}.
             Fields in $pojoTypeName: ${allFields.joinToString(", ") { it.columnName }}.
-            """.trimIndent().replace(System.lineSeparator(), " ")
+            """.trim()
     }
 
     val TYPE_CONVERTER_UNBOUND_GENERIC = "Cannot use unbound generics in Type Converters."
@@ -203,7 +225,7 @@
             " have no-argument public constructors."
     val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
 
-    fun duplicateTypeConverters(converters : List<CustomTypeConverter>) : String {
+    fun duplicateTypeConverters(converters: List<CustomTypeConverter>): String {
         return "Multiple methods define the same conversion. Conflicts with these:" +
                 " ${converters.joinToString(", ") { it.toString() }}"
     }
@@ -211,36 +233,36 @@
     // TODO must print field paths.
     val POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME = "Field has non-unique column name."
 
-    fun pojoDuplicateFieldNames(columnName : String, fieldPaths : List<String>) : String {
+    fun pojoDuplicateFieldNames(columnName: String, fieldPaths: List<String>): String {
         return "Multiple fields have the same columnName: $columnName." +
                 " Field names: ${fieldPaths.joinToString(", ")}."
     }
 
-    fun decomposedPrimaryKeyIsDropped(entityQName: String, fieldName : String) : String {
+    fun decomposedPrimaryKeyIsDropped(entityQName: String, fieldName: String): String {
         return "Primary key constraint on $fieldName is ignored when being merged into " +
                 entityQName
     }
 
     val INDEX_COLUMNS_CANNOT_BE_EMPTY = "List of columns in an index cannot be empty"
 
-    fun indexColumnDoesNotExist(columnName : String, allColumns : List<String>) : String {
+    fun indexColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
         return "$columnName referenced in the index does not exists in the Entity." +
                 " Available column names:${allColumns.joinToString(", ")}"
     }
 
-    fun duplicateIndexInEntity(indexName : String) : String {
+    fun duplicateIndexInEntity(indexName: String): String {
         return "There are multiple indices with name $indexName. This happen if you've declared" +
                 " the same index multiple times or different indices have the same name. See" +
                 " @Index documentation for details."
     }
 
-    fun duplicateIndexInDatabase(indexName: String, indexPaths : List<String>) : String {
+    fun duplicateIndexInDatabase(indexName: String, indexPaths: List<String>): String {
         return "There are multiple indices with name $indexName. You should rename " +
                 "${indexPaths.size - 1} of these to avoid the conflict:" +
                 "${indexPaths.joinToString(", ")}."
     }
 
-    fun droppedDecomposedFieldIndex(fieldPath : String, grandParent : String) : String {
+    fun droppedDecomposedFieldIndex(fieldPath: String, grandParent: String): String {
         return "The index will be dropped when being merged into $grandParent" +
                 "($fieldPath). You must re-declare it in $grandParent if you want to index this" +
                 " field in $grandParent."
@@ -249,20 +271,20 @@
     val FIELD_WITH_DECOMPOSE_AND_COLUMN_INFO = "You cannot annotate a Decomposed field" +
             " with ColumnInfo. Its sub fields are the columns."
 
-    fun droppedDecomposedIndex(entityName : String, fieldPath: String, grandParent: String)
+    fun droppedDecomposedIndex(entityName: String, fieldPath: String, grandParent: String)
             : String {
         return "Indices defined in $entityName will be dropped when it is merged into" +
                 " $grandParent ($fieldPath). You can re-declare them in $grandParent."
     }
 
-    fun droppedSuperClassIndex(childEntity : String, superEntity : String) : String {
+    fun droppedSuperClassIndex(childEntity: String, superEntity: String): String {
         return "Indices defined in $superEntity will NOT be re-used in $childEntity. If you want" +
                 " to inherit them, you must re-declare them in $childEntity." +
                 " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
     }
 
-    fun droppedSuperClassFieldIndex(fieldName : String, childEntity : String,
-                                    superEntity : String) : String {
+    fun droppedSuperClassFieldIndex(fieldName: String, childEntity: String,
+                                    superEntity: String): String {
         return "Index defined on field `$fieldName` in $superEntity will NOT be re-used in" +
                 " $childEntity. " +
                 "If you want to inherit it, you must re-declare it in $childEntity." +
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 a5916b3..62a7740 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,15 +22,15 @@
 // TODO make data class when move to kotlin 1.1
 class Entity(element: TypeElement, val tableName: String, type: DeclaredType,
              fields: List<Field>, decomposedFields: List<DecomposedField>,
-             val indices : List<Index>)
+             val primaryKey: PrimaryKey,
+             val indices: List<Index>)
     : Pojo(element, type, fields, decomposedFields) {
-    val primaryKeys by lazy {
-        fields.filter { it.primaryKey }
-    }
 
     val createTableQuery by lazy {
-        val definitions = fields.map { it.databaseDefinition } +
-                createPrimaryKeyDefinition()
+        val definitions = (fields.map {
+            val autoIncrement = primaryKey.autoGenerateId && primaryKey.fields.contains(it)
+            it.databaseDefinition(autoIncrement)
+        } + createPrimaryKeyDefinition()).filterNotNull()
         "CREATE TABLE IF NOT EXISTS `$tableName` (${definitions.joinToString(", ")})"
     }
 
@@ -40,11 +40,14 @@
         }
     }
 
-    private fun createPrimaryKeyDefinition(): String {
-        val keys = fields
-                .filter { it.primaryKey }
-                .map { "`${it.columnName}`" }
-                .joinToString(", ")
-        return "PRIMARY KEY($keys)"
+    private fun createPrimaryKeyDefinition(): String? {
+        return if (primaryKey.fields.isEmpty() || primaryKey.autoGenerateId) {
+            null
+        } else {
+            val keys = primaryKey.fields
+                    .map { "`${it.columnName}`" }
+                    .joinToString(", ")
+            "PRIMARY KEY($keys)"
+        }
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
index 5f0d7a2..79c0fc0 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
@@ -18,7 +18,6 @@
 
 import com.android.support.room.ext.typeName
 import com.android.support.room.parser.SQLTypeAffinity
-import com.android.support.room.solver.types.ColumnTypeAdapter
 import com.android.support.room.solver.types.CursorValueReader
 import com.android.support.room.solver.types.StatementValueBinder
 import com.squareup.javapoet.TypeName
@@ -26,7 +25,7 @@
 import javax.lang.model.type.TypeMirror
 
 data class Field(val element: Element, val name: String, val type: TypeMirror,
-                 val primaryKey: Boolean, var affinity: SQLTypeAffinity?,
+                 var affinity: SQLTypeAffinity?,
                  val columnName: String = name,
                  /* means that this field does not belong to parent, instead, it belongs to a
                  * decomposed child of the main Pojo*/
@@ -96,7 +95,12 @@
     /**
      * definition to be used in create query
      */
-    val databaseDefinition by lazy {
-        "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}"
+    fun databaseDefinition(autoIncrementPKey : Boolean) : String {
+        val columnSpec = if (autoIncrementPKey) {
+            " PRIMARY KEY AUTOINCREMENT"
+        } else {
+            ""
+        }
+        return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec"
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/PrimaryKey.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/PrimaryKey.kt
new file mode 100644
index 0000000..59be322
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/PrimaryKey.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.Element
+
+/**
+ * Represents a PrimaryKey for an Entity.
+ */
+data class PrimaryKey(val declaredIn : Element?, val fields: List<Field>,
+                      val autoGenerateId: Boolean) {
+    companion object {
+        val MISSING = PrimaryKey(null, emptyList(), false)
+    }
+
+    fun toHumanReadableString(): String {
+        return "PrimaryKey[" +
+                fields.joinToString(separator = ", ", transform = Field::getPath) + "]"
+    }
+}
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 2811f12..5b323ac 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
@@ -43,7 +43,7 @@
                 returns(ClassName.get("java.lang", "String"))
                 addModifiers(PUBLIC)
                 val query = "DELETE FROM `${entity.tableName}` WHERE " +
-                        entity.primaryKeys.joinToString(" AND ") {
+                        entity.primaryKey.fields.joinToString(" AND ") {
                             "`${it.columnName}` = ?"
                         }
                 addStatement("return $S", query)
@@ -58,7 +58,7 @@
                 addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
                 returns(TypeName.VOID)
                 addModifiers(PUBLIC)
-                val mapped = entity.primaryKeys.mapIndexed { index, field ->
+                val mapped = entity.primaryKey.fields.mapIndexed { index, field ->
                     FieldWithIndex(field, "${index + 1}")
                 }
                 FieldReadWriteWriter.bindToStatement(ownerVar = valueParam,
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 773dec5..bd031a4 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
@@ -38,6 +38,22 @@
             superclass(
                     ParameterizedTypeName.get(RoomTypeNames.INSERTION_ADAPTER, entity.typeName)
             )
+
+            // If there is an auto-increment primary key with primitive type, we consider 0 as
+            // not set. For such fields, we must generate a slightly different insertion SQL.
+            val primitiveAutoGenerateField = if (entity.primaryKey.autoGenerateId) {
+                entity.primaryKey.fields.firstOrNull()?.let { field ->
+                    field.statementBinder?.typeMirror()?.let { binderType ->
+                        if (binderType.kind.isPrimitive) {
+                            field
+                        } else {
+                            null
+                        }
+                    }
+                }
+            } else {
+                null
+            }
             addMethod(MethodSpec.methodBuilder("createQuery").apply {
                 addAnnotation(Override::class.java)
                 returns(ClassName.get("java.lang", "String"))
@@ -48,7 +64,11 @@
                                     "`${it.columnName}`"
                                 } + ") VALUES (" +
                                 entity.fields.joinToString(",") {
-                                    "?"
+                                    if (primitiveAutoGenerateField == it) {
+                                        "nullif(?, 0)"
+                                    } else {
+                                        "?"
+                                    }
                                 } + ")"
                 addStatement("return $S", query)
             }.build())
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 0bd0c3a..957f2d3 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
@@ -45,7 +45,7 @@
                 val query = "UPDATE OR $onConflict `${entity.tableName}` SET " +
                         entity.fields.joinToString(",") { field ->
                             "`${field.columnName}` = ?"
-                        } + " WHERE " + entity.primaryKeys.joinToString(" AND ") {
+                        } + " WHERE " + entity.primaryKey.fields.joinToString(" AND ") {
                             "`${it.columnName}` = ?"
                         }
                 addStatement("return $S", query)
@@ -70,7 +70,7 @@
                         scope = bindScope
                 )
                 val pkeyStart = entity.fields.size
-                val mappedPrimaryKeys = entity.primaryKeys.mapIndexed { index, field ->
+                val mappedPrimaryKeys = entity.primaryKey.fields.mapIndexed { index, field ->
                     FieldWithIndex(field, "${pkeyStart + index + 1}")
                 }
                 FieldReadWriteWriter.bindToStatement(
diff --git a/room/compiler/src/test/data/common/input/MultiPKeyEntity.java b/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
index 34c81b6..794bdad 100644
--- a/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
+++ b/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
@@ -16,10 +16,8 @@
 
 package foo.bar;
 import com.android.support.room.*;
-@Entity
+@Entity(primaryKeys = {"name", "lastName"})
 public class MultiPKeyEntity {
-    @PrimaryKey
     String name;
-    @PrimaryKey
     String lastName;
 }
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
index bc881ea..3943e0f 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
@@ -67,7 +67,6 @@
                     element = field.element,
                     name = fieldName,
                     type = intType,
-                    primaryKey = true,
                     columnName = fieldName,
                     affinity = SQLTypeAffinity.INTEGER)))
             assertThat(field.setter, `is`(FieldSetter(setterName, intType, CallType.METHOD)))
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
index 6799f1b..86f2f6a 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
@@ -48,12 +48,11 @@
                     element = field.element,
                     name = "id",
                     type = intType,
-                    primaryKey = true,
                     columnName = "id",
                     affinity = SQLTypeAffinity.INTEGER)))
             assertThat(field.setter, `is`(FieldSetter("setId", intType, CallType.METHOD)))
             assertThat(field.getter, `is`(FieldGetter("getId", intType, CallType.METHOD)))
-            assertThat(entity.primaryKeys, `is`(listOf(field)))
+            assertThat(entity.primaryKey.fields, `is`(listOf(field)))
         }.compilesWithoutError()
     }
 
@@ -197,18 +196,6 @@
     }
 
     @Test
-    fun multiplePrimaryKeys() {
-        singleEntity("""
-                @PrimaryKey
-                int x;
-                @PrimaryKey
-                int y;
-                """) { entity, invocation ->
-            assertThat(entity.primaryKeys.size, `is`(2))
-        }.compilesWithoutError()
-    }
-
-    @Test
     fun customName() {
         singleEntity("""
                 @PrimaryKey
@@ -260,8 +247,7 @@
                 }
                 """
         ) { entity, invocation ->
-            assertThat(entity.fields.find { it.name == "x" }!!.primaryKey, `is`(false))
-            assertThat(entity.fields.filter { it.primaryKey }.map { it.name }, `is`(listOf("id")))
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
         }.compilesWithoutError()
                 .withWarningCount(1)
                 .withWarningContaining(ProcessorErrors.decomposedPrimaryKeyIsDropped(
@@ -284,8 +270,7 @@
                 }
                 """
         ) { entity, invocation ->
-            assertThat(entity.fields.find { it.name == "x" }!!.primaryKey, `is`(false))
-            assertThat(entity.fields.filter { it.primaryKey }.map { it.name }, `is`(listOf("id")))
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
         }.compilesWithoutError().withWarningCount(0)
     }
 
@@ -742,4 +727,318 @@
                         ProcessorErrors.droppedDecomposedFieldIndex("foo > a", "foo.bar.MyEntity")
                 )
     }
+
+    @Test
+    fun primaryKey_definedInBothWays() {
+        singleEntity(
+                """
+                public int id;
+                @PrimaryKey
+                public String foo;
+                """,
+                attributes = mapOf("primaryKeys" to "\"id\"")) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.multiplePrimaryKeyAnnotations(
+                        listOf("PrimaryKey[id]", "PrimaryKey[foo]")
+                ))
+    }
+
+    @Test
+    fun primaryKey_badColumnName() {
+        singleEntity(
+                """
+                public int id;
+                """,
+                attributes = mapOf("primaryKeys" to "\"foo\"")) { entity, invocation ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.primaryKeyColumnDoesNotExist("foo", listOf("id")))
+    }
+
+    @Test
+    fun primaryKey_multipleAnnotations() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                @PrimaryKey
+                int y;
+                """) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.isEmpty(), `is`(true))
+        }.failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.multiplePrimaryKeyAnnotations(
+                                listOf("PrimaryKey[x]", "PrimaryKey[y]")))
+    }
+
+    @Test
+    fun primaryKey_fromParentField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
+
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_fromParentEntity() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentEntityViaField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentEntityViaEntity() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent),
+                attributes = mapOf("primaryKeys" to "\"id\"")) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_autoGenerate() {
+        listOf("long", "Long", "Integer", "int").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                public $type id;
+                """) { entity, invocation ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun primaryKey_autoGenerateBadType() {
+        listOf("String", "float", "Float", "Double", "double").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                public $type id;
+                """) { entity, invocation ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
+            }.failsToCompile().withErrorContaining(
+                    ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT)
+        }
+    }
+
+    @Test
+    fun primaryKey_decomposed(){
+        singleEntity(
+                """
+                public int id;
+
+                @Decompose(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_decomposedInherited(){
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Decompose(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_overrideViaDecomposed() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                @Decompose(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[foo > a, foo > b]")
+    }
+
+    @Test
+    fun primaryKey_overrideDecomposed() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import com.android.support.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Decompose(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, invocation ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("id")))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[foo > a, foo > b] is overridden by PrimaryKey[id]")
+    }
 }
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
index b448c7d..6c5f51b 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
@@ -113,7 +113,6 @@
                 assertThat(field, `is`(
                         Field(name = "x",
                                 type = primitive.typeMirror(invocation),
-                                primaryKey = false,
                                 element = field.element,
                                 affinity = primitive.affinity()
                         )))
@@ -128,7 +127,6 @@
                 assertThat(field, `is`(
                         Field(name = "y",
                                 type = primitive.box(invocation),
-                                primaryKey = false,
                                 element = field.element,
                                 affinity = primitive.affinity())))
             }.compilesWithoutError()
@@ -136,21 +134,6 @@
     }
 
     @Test
-    fun primaryKey() {
-        singleEntity("""
-            @PrimaryKey
-            int x;
-            """) { field, invocation ->
-            assertThat(field, `is`(
-                    Field(name = "x",
-                            type = TypeKind.INT.typeMirror(invocation),
-                            primaryKey = true,
-                            element = field.element,
-                            affinity = SQLTypeAffinity.INTEGER)))
-        }.compilesWithoutError()
-    }
-
-    @Test
     fun columnName() {
         singleEntity("""
             @ColumnInfo(name = "foo")
@@ -160,7 +143,6 @@
             assertThat(field, `is`(
                     Field(name = "x",
                             type = TypeKind.INT.typeMirror(invocation),
-                            primaryKey = true,
                             element = field.element,
                             columnName = "foo",
                             affinity = SQLTypeAffinity.INTEGER)))
@@ -176,7 +158,6 @@
             assertThat(field, `is`(
                     Field(name = "x",
                             type = TypeKind.INT.typeMirror(invocation),
-                            primaryKey = false,
                             element = field.element,
                             columnName = "foo",
                             affinity = SQLTypeAffinity.INTEGER,
@@ -200,7 +181,6 @@
             assertThat(field, `is`(Field(name = "arr",
                     type = invocation.processingEnv.typeUtils.getArrayType(
                             TypeKind.BYTE.typeMirror(invocation)),
-                    primaryKey = false,
                     element = field.element,
                     affinity = SQLTypeAffinity.TEXT)))
             assertThat((field.cursorValueReader as? ColumnTypeAdapter)?.typeAffinity,
@@ -217,7 +197,6 @@
                         Field(name = "arr",
                                 type = invocation.processingEnv.typeUtils.getArrayType(
                                         primitive.typeMirror(invocation)),
-                                primaryKey = false,
                                 element = field.element,
                                 affinity = if (primitive == TypeKind.BYTE) {
                                     SQLTypeAffinity.BLOB
@@ -237,7 +216,6 @@
                         Field(name = "arr",
                                 type = invocation.processingEnv.typeUtils.getArrayType(
                                         primitive.box(invocation)),
-                                primaryKey = false,
                                 element = field.element,
                                 affinity = SQLTypeAffinity.TEXT)))
             }.compilesWithoutError()
@@ -256,7 +234,6 @@
                 """) { field, invocation ->
             assertThat(field, `is`(Field(name = "item",
                     type = TypeKind.INT.box(invocation),
-                    primaryKey = false,
                     element = field.element,
                     affinity = SQLTypeAffinity.INTEGER)))
         }.compilesWithoutError()
@@ -276,12 +253,12 @@
     @Test
     fun nameVariations() {
         simpleRun {
-            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it), false,
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
             assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it),
-                    false, SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
             assertThat(Field(mock(Element::class.java), "xAll",
-                    TypeKind.BOOLEAN.typeMirror(it), false, SQLTypeAffinity.INTEGER)
+                    TypeKind.BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER)
                     .nameWithVariations, `is`(arrayListOf("xAll")))
         }
     }
@@ -290,13 +267,13 @@
     fun nameVariations_is() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX", "x")))
-            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it), false,
+            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX")))
-            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("is")))
-            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations,
                     `is`(arrayListOf("isAllItems", "allItems")))
         }
@@ -306,13 +283,13 @@
     fun nameVariations_has() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX", "x")))
-            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it), false,
+            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX")))
-            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("has")))
-            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations,
                     `is`(arrayListOf("hasAllItems", "allItems")))
         }
@@ -322,18 +299,18 @@
     fun nameVariations_m() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mall")))
-            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mallVars")))
-            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mAll", "all")))
-            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it), false,
+            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("m")))
-            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations,
                     `is`(arrayListOf("mallItems")))
-            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations,
                     `is`(arrayListOf("mAllItems", "allItems")))
         }
@@ -343,11 +320,11 @@
     fun nameVariations_underscore() {
         val elm = mock(Element::class.java)
         simpleRun {
-            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_all", "all")))
-            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it), false,
+            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_")))
-            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it), false,
+            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER).nameWithVariations,
                     `is`(arrayListOf("_allItems", "allItems")))
         }
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 d7d92e6..40b018e 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
@@ -195,7 +195,7 @@
     }
 
     @Test
-    fun dropSubPrimaryKey() {
+    fun dropSubPrimaryKeyNoWarningForPojo() {
         singleRun(
                 """
                 @PrimaryKey
@@ -209,8 +209,6 @@
                 }
                 """
         ) { pojo ->
-            assertThat(pojo.fields.find { it.name == "x" }!!.primaryKey, `is`(false))
-            assertThat(pojo.fields.filter { it.primaryKey }.map { it.name }, `is`(listOf("id")))
         }.compilesWithoutError().withWarningCount(0)
     }
 
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt
index 47fe4d9..0f6628f 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/QueryMethodProcessorTest.kt
@@ -81,7 +81,6 @@
                     element = Mockito.mock(Element::class.java),
                     name = name,
                     type = Mockito.mock(TypeMirror::class.java),
-                    primaryKey = false,
                     columnName = columnName ?: name,
                     affinity = null
             )
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 d0bc125..e134fd4 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
@@ -27,6 +27,7 @@
 import com.android.support.room.vo.Field
 import com.android.support.room.vo.FieldGetter
 import com.android.support.room.vo.FieldSetter
+import com.android.support.room.vo.PrimaryKey
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.hasItem
 import org.hamcrest.CoreMatchers.notNullValue
@@ -168,7 +169,7 @@
 
     private fun userDb(context: Context): Database {
         return database(entity("User",
-                primaryField("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER),
+                field("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER),
                 field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
                 field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
                 field("ratio", primitive(context, TypeKind.FLOAT), SQLTypeAffinity.REAL)))
@@ -182,15 +183,15 @@
                 daoMethods = emptyList())
     }
 
-    private fun entity(tableName: String, vararg fields: Field)
-            : Entity {
+    private fun entity(tableName: String, vararg fields: Field): Entity {
         return Entity(
                 element = mock(TypeElement::class.java),
                 tableName = tableName,
                 type = mock(DeclaredType::class.java),
                 fields = fields.toList(),
                 decomposedFields = emptyList(),
-                indices = emptyList()
+                indices = emptyList(),
+                primaryKey = PrimaryKey(null, fields.take(1), false)
         )
     }
 
@@ -199,20 +200,6 @@
                 element = mock(Element::class.java),
                 name = name,
                 type = type,
-                primaryKey = false,
-                columnName = name,
-                affinity = affinity
-        )
-        assignGetterSetter(f, name, type)
-        return f
-    }
-
-    private fun primaryField(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field {
-        val f = Field(
-                element = mock(Element::class.java),
-                name = name,
-                type = type,
-                primaryKey = true,
                 columnName = name,
                 affinity = affinity
         )
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/writer/SQLiteOpenHelperWriterTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/writer/SQLiteOpenHelperWriterTest.kt
index a527c10..02a8e97 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/writer/SQLiteOpenHelperWriterTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/writer/SQLiteOpenHelperWriterTest.kt
@@ -37,7 +37,7 @@
         const val ENTITY_PREFIX = """
             package foo.bar;
             import com.android.support.room.*;
-            @Entity
+            @Entity%s
             public class MyEntity {
             """
         const val ENTITY_SUFFIX = "}"
@@ -71,12 +71,10 @@
     fun multiplePrimaryKeys() {
         singleEntity(
                 """
-                @PrimaryKey
                 String uuid;
-                @PrimaryKey
                 String name;
                 int age;
-                """.trimIndent()
+                """.trimIndent(), attributes = mapOf("primaryKeys" to "{\"uuid\", \"name\"}")
         ) { database, invocation ->
             val query = SQLiteOpenHelperWriter(database)
                     .createQuery(database.entities.first())
@@ -86,11 +84,37 @@
         }.compilesWithoutError()
     }
 
+    @Test
+    fun autoIncrement() {
+        singleEntity(
+                """
+                @PrimaryKey(autoGenerate = true)
+                int uuid;
+                String name;
+                int age;
+                """.trimIndent()
+        ) { database, invocation ->
+            val query = SQLiteOpenHelperWriter(database)
+                    .createQuery(database.entities.first())
+            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                    " `MyEntity` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    " `name` TEXT, `age` INTEGER)"))
+        }.compilesWithoutError()
+    }
+
     fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
                      handler: (Database, TestInvocation) -> Unit): CompileTester {
+        val attributesReplacement : String
+        if (attributes.isEmpty()) {
+            attributesReplacement = ""
+        } else {
+            attributesReplacement = "(" +
+                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
+                    ")".trimIndent()
+        }
         return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
                 .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
-                        ENTITY_PREFIX + input + ENTITY_SUFFIX
+                        ENTITY_PREFIX.format(attributesReplacement) + input + ENTITY_SUFFIX
                 ), JavaFileObjects.forSourceString("foo.bar.MyDatabase",
                         DATABASE_CODE)))
                 .processedWith(TestProcessor.builder()
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/PKeyTestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/PKeyTestDatabase.java
new file mode 100644
index 0000000..23e4fc1
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/PKeyTestDatabase.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import com.android.support.room.Dao;
+import com.android.support.room.Database;
+import com.android.support.room.Insert;
+import com.android.support.room.Query;
+import com.android.support.room.RoomDatabase;
+import com.android.support.room.integration.testapp.vo.IntAutoIncPKeyEntity;
+import com.android.support.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+
+@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class})
+public abstract class PKeyTestDatabase extends RoomDatabase {
+    public abstract IntPKeyDao intPKeyDao();
+    public abstract IntegerPKeyDao integerPKeyDao();
+
+    @Dao
+    public interface IntPKeyDao {
+        @Insert
+        void insertMe(IntAutoIncPKeyEntity... items);
+        @Query("select * from IntAutoIncPKeyEntity WHERE pKey = ?")
+        IntAutoIncPKeyEntity getMe(int key);
+    }
+
+    @Dao
+    public interface IntegerPKeyDao {
+        @Insert
+        void insertMe(IntegerAutoIncPKeyEntity items);
+        @Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = ?")
+        IntegerAutoIncPKeyEntity getMe(int key);
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/PrimaryKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/PrimaryKeyTest.java
new file mode 100644
index 0000000..c2e9ec3
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/test/PrimaryKeyTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.CoreMatchers.notNullValue;
+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.Room;
+import com.android.support.room.integration.testapp.PKeyTestDatabase;
+import com.android.support.room.integration.testapp.vo.IntAutoIncPKeyEntity;
+import com.android.support.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PrimaryKeyTest {
+    private PKeyTestDatabase mDatabase;
+    @Before
+    public void setup() {
+        mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                PKeyTestDatabase.class).build();
+    }
+
+    @Test
+    public void integerTest() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        mDatabase.integerPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(1);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void dontOverrideNullable0() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.pKey = 0;
+        entity.data = "foo";
+        mDatabase.integerPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(0);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void intTest() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        mDatabase.intPKeyDao().insertMe(entity);
+        IntAutoIncPKeyEntity loaded = mDatabase.intPKeyDao().getMe(1);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/IntAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
new file mode 100644
index 0000000..414a7f6
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.vo;
+
+import com.android.support.room.Entity;
+import com.android.support.room.PrimaryKey;
+
+@Entity
+public class IntAutoIncPKeyEntity {
+    @PrimaryKey(autoGenerate = true)
+    public int pKey;
+    public String data;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
new file mode 100644
index 0000000..a6bc1c1
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/com/android/support/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.vo;
+
+import com.android.support.room.Entity;
+import com.android.support.room.PrimaryKey;
+
+@Entity
+public class IntegerAutoIncPKeyEntity {
+    @PrimaryKey(autoGenerate = true)
+    public Integer pKey;
+    public String data;
+}