Merge "Switch beginWithUniqueTag to beginWithName." into flatfoot-background
diff --git a/room/common/src/main/java/android/arch/persistence/room/RawQuery.java b/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
index b41feab..46f2193 100644
--- a/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
+++ b/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
@@ -137,8 +137,8 @@
* Denotes the list of entities which are accessed in the provided query and should be observed
* for invalidation if the query is observable.
* <p>
- * The listed classes should be {@link Entity Entities} that are linked from the containing
- * {@link Database}.
+ * The listed classes should either be annotated with {@link Entity} or they should reference to
+ * at least 1 Entity (via {@link Embedded} or {@link Relation}).
* <p>
* Providing this field in a non-observable query has no impact.
* <pre>
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
index 313f968..3859028 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
@@ -480,7 +480,7 @@
}
companion object {
- private fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String {
+ fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String {
val annotationValue = AnnotationMirrors
.getAnnotationValue(annotation, "tableName").value.toString()
return if (annotationValue == "") {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
index 3e7dd9c..9c3fecb 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
@@ -504,4 +504,13 @@
" String or SupportSQLiteQuery"
val RAW_QUERY_BAD_RETURN_TYPE = "RawQuery methods must return a non-void type."
+
+ fun rawQueryBadEntity(typeName: TypeName): String {
+ return """
+ observedEntities field in RawQuery must either reference a class that is annotated
+ with @Entity or it should reference a Pojo that either contains @Embedded fields that
+ are annotated with @Entity or @Relation fields.
+ $typeName does not have these properties, did you mean another class?
+ """.trim()
+ }
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
index 6858ccc..b26ff6e 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
@@ -23,7 +23,6 @@
import android.arch.persistence.room.ext.toListOfClassTypes
import android.arch.persistence.room.ext.typeName
import android.arch.persistence.room.parser.SqlParser
-import android.arch.persistence.room.vo.Entity
import android.arch.persistence.room.vo.RawQueryMethod
import com.google.auto.common.AnnotationMirrors
import com.google.auto.common.MoreElements
@@ -50,9 +49,8 @@
val returnTypeName = TypeName.get(executableType.returnType)
context.checker.notUnbound(returnTypeName, executableElement,
ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
- val observedEntities = processObservedTables()
- val query = SqlParser.rawQueryForTables(
- observedEntities.map { it.tableName }.toSet())
+ val observedTableNames = processObservedTables()
+ val query = SqlParser.rawQueryForTables(observedTableNames)
// build the query but don't calculate result info since we just guessed it.
val resultBinder = context.typeAdapterStore
.findQueryResultBinder(executableType.returnType, query)
@@ -62,7 +60,7 @@
val rawQueryMethod = RawQueryMethod(
element = executableElement,
name = executableElement.simpleName.toString(),
- observedEntities = observedEntities,
+ observedTableNames = observedTableNames,
returnType = executableType.returnType,
runtimeQueryParam = runtimeQueryParam,
inTransaction = inTransaction,
@@ -73,20 +71,40 @@
return rawQueryMethod
}
- private fun processObservedTables(): List<Entity> {
+ private fun processObservedTables(): Set<String> {
val annotation = MoreElements
.getAnnotationMirror(executableElement,
android.arch.persistence.room.RawQuery::class.java)
- .orNull() ?: return emptyList()
+ .orNull() ?: return emptySet()
val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities")
return entityList
.toListOfClassTypes()
.map {
- EntityProcessor(
- baseContext = context,
- element = MoreTypes.asTypeElement(it)
- ).process()
+ MoreTypes.asTypeElement(it)
}
+ .flatMap {
+ if (it.hasAnnotation(android.arch.persistence.room.Entity::class)) {
+ val entity = EntityProcessor(
+ baseContext = context,
+ element = it
+ ).process()
+ arrayListOf(entity.tableName)
+ } else {
+ val pojo = PojoProcessor(
+ baseContext = context,
+ element = it,
+ bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+ parent = null
+ ).process()
+ val tableNames = pojo.accessedTableNames()
+ // if it is empty, report error as it does not make sense
+ if (tableNames.isEmpty()) {
+ context.logger.e(executableElement,
+ ProcessorErrors.rawQueryBadEntity(it.asType().typeName()))
+ }
+ tableNames
+ }
+ }.toSet()
}
private fun findRuntimeQueryParameter(): RawQueryMethod.RuntimeQueryParameter? {
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
index 629c6b8..34ec61c 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
@@ -17,6 +17,8 @@
package android.arch.persistence.room.vo
import android.arch.persistence.room.ext.typeName
+import android.arch.persistence.room.processor.EntityProcessor
+import com.google.auto.common.MoreElements
import com.squareup.javapoet.TypeName
import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType
@@ -24,9 +26,30 @@
/**
* A class is turned into a Pojo if it is used in a query response.
*/
-// TODO make data class when move to kotlin 1.1
-open class Pojo(val element: TypeElement, val type: DeclaredType, val fields: List<Field>,
- val embeddedFields: List<EmbeddedField>, val relations: List<Relation>,
- val constructor: Constructor? = null) {
+open class Pojo(
+ val element: TypeElement,
+ val type: DeclaredType,
+ val fields: List<Field>,
+ val embeddedFields: List<EmbeddedField>,
+ val relations: List<Relation>,
+ val constructor: Constructor? = null) {
val typeName: TypeName by lazy { type.typeName() }
+
+ /**
+ * All table names that are somehow accessed by this Pojo.
+ * Might be via Embedded or Relation.
+ */
+ fun accessedTableNames(): List<String> {
+ val entityAnnotation = MoreElements.getAnnotationMirror(element,
+ android.arch.persistence.room.Entity::class.java).orNull()
+ return if (entityAnnotation != null) {
+ listOf(EntityProcessor.extractTableName(element, entityAnnotation))
+ } else {
+ embeddedFields.flatMap {
+ it.pojo.accessedTableNames()
+ } + relations.map {
+ it.entity.tableName
+ }
+ }
+ }
}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
index ad1f10d..5099906 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
@@ -33,7 +33,7 @@
val name: String,
val returnType: TypeMirror,
val inTransaction: Boolean,
- val observedEntities: List<Entity>,
+ val observedTableNames: Set<String>,
val runtimeQueryParam: RuntimeQueryParameter?,
val queryResultBinder: QueryResultBinder) {
val returnsValue by lazy {
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
index 17e8ad9..0a9be4a 100644
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
@@ -97,10 +97,8 @@
type = SupportDbTypeNames.QUERY
)
))
- assertThat(query.observedEntities.size, `is`(1))
- assertThat(
- query.observedEntities.first().typeName,
- `is`(COMMON.USER_TYPE_NAME as TypeName))
+ assertThat(query.observedTableNames.size, `is`(1))
+ assertThat(query.observedTableNames, `is`(setOf("User")))
}.compilesWithoutError()
}
@@ -118,7 +116,7 @@
type = SupportDbTypeNames.QUERY
)
))
- assertThat(query.observedEntities, `is`(emptyList()))
+ assertThat(query.observedTableNames, `is`(emptySet()))
}.failsToCompile()
.withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
}
@@ -180,7 +178,7 @@
)
))
assertThat(query.returnType.typeName(), `is`(pojo))
- assertThat(query.observedEntities, `is`(emptyList()))
+ assertThat(query.observedTableNames, `is`(emptySet()))
}.compilesWithoutError()
}
@@ -232,6 +230,53 @@
)
}
+ @Test
+ fun observed_notAnEntity() {
+ singleQueryMethod(
+ """
+ @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
+ abstract public int[] foo(String query);
+ """) { _, _ ->
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME)
+ )
+ }
+
+ @Test
+ fun observed_relationPojo() {
+ singleQueryMethod(
+ """
+ public static class MyPojo {
+ public String foo;
+ @Relation(
+ parentColumn = "foo",
+ entityColumn = "name"
+ )
+ public java.util.List<User> users;
+ }
+ @RawQuery(observedEntities = MyPojo.class)
+ abstract public int[] foo(String query);
+ """) { method, _ ->
+ assertThat(method.observedTableNames, `is`(setOf("User")))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun observed_embedded() {
+ singleQueryMethod(
+ """
+ public static class MyPojo {
+ public String foo;
+ @Embedded
+ public User users;
+ }
+ @RawQuery(observedEntities = MyPojo.class)
+ abstract public int[] foo(String query);
+ """) { method, _ ->
+ assertThat(method.observedTableNames, `is`(setOf("User")))
+ }.compilesWithoutError()
+ }
+
private fun singleQueryMethod(
vararg input: String,
handler: (RawQueryMethod, TestInvocation) -> Unit
@@ -242,7 +287,8 @@
+ input.joinToString("\n")
+ DAO_SUFFIX
), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
- COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE))
+ COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE,
+ COMMON.NOT_AN_ENTITY))
.processedWith(TestProcessor.builder()
.forAnnotations(Query::class, Dao::class, ColumnInfo::class,
Entity::class, PrimaryKey::class, RawQuery::class)
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
index 0f4a73d..ae09350 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
@@ -33,28 +33,46 @@
public interface RawDao {
@RawQuery
User getUser(String query);
+
@RawQuery
UserAndAllPets getUserAndAllPets(String query);
+
+ @RawQuery(observedEntities = UserAndAllPets.class)
+ LiveData<UserAndAllPets> getUserAndAllPetsObservable(String query);
+
@RawQuery
User getUser(SupportSQLiteQuery query);
+
@RawQuery
UserAndPet getUserAndPet(String query);
+
@RawQuery
NameAndLastName getUserNameAndLastName(String query);
+
@RawQuery(observedEntities = User.class)
NameAndLastName getUserNameAndLastName(SupportSQLiteQuery query);
+
@RawQuery
int count(String query);
+
@RawQuery
List<User> getUserList(String query);
+
@RawQuery
List<UserAndPet> getUserAndPetList(String query);
+
+ @RawQuery(observedEntities = UserAndPet.class)
+ LiveData<List<UserAndPet>> getUserAndPetListObservable(String query);
+
@RawQuery(observedEntities = User.class)
LiveData<User> getUserLiveData(String query);
+
@RawQuery(observedEntities = User.class)
LiveData<User> getUserLiveData(SupportSQLiteQuery query);
+
@RawQuery
UserNameAndBirthday getUserAndBirthday(String query);
+
class UserNameAndBirthday {
@ColumnInfo(name = "mName")
public final String name;
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
index 310eb5c..0957901 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
@@ -20,6 +20,8 @@
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static java.util.Collections.emptyList;
+
import android.arch.core.executor.testing.CountingTaskExecutorRule;
import android.arch.lifecycle.LiveData;
import android.arch.persistence.db.SimpleSQLiteQuery;
@@ -30,6 +32,7 @@
import android.arch.persistence.room.integration.testapp.vo.User;
import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
import android.arch.persistence.room.integration.testapp.vo.UserAndPet;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -204,6 +207,71 @@
assertThat(count, is(4));
}
+ @Test
+ public void embedded_liveData() throws TimeoutException, InterruptedException {
+ LiveData<List<UserAndPet>> liveData = mRawDao.getUserAndPetListObservable(
+ "SELECT * FROM User LEFT JOIN Pet ON (User.mId = Pet.mUserId)"
+ + " ORDER BY mId ASC, mPetId ASC");
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> liveData.observeForever(user -> {
+ })
+ );
+ drain();
+ assertThat(liveData.getValue(), is(emptyList()));
+
+ User[] users = TestUtil.createUsersArray(3, 5);
+ Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+ mUserDao.insertAll(users);
+ drain();
+ List<UserAndPet> justUsers = liveData.getValue();
+ //noinspection ConstantConditions
+ assertThat(justUsers.size(), is(2));
+ assertThat(justUsers.get(0).getUser(), is(users[0]));
+ assertThat(justUsers.get(1).getUser(), is(users[1]));
+ assertThat(justUsers.get(0).getPet(), is(nullValue()));
+ assertThat(justUsers.get(1).getPet(), is(nullValue()));
+
+ mPetDao.insertAll(pets);
+ drain();
+ List<UserAndPet> allItems = liveData.getValue();
+ //noinspection ConstantConditions
+ assertThat(allItems.size(), is(3));
+ // row 0
+ assertThat(allItems.get(0).getUser(), is(users[0]));
+ assertThat(allItems.get(0).getPet(), is(pets[0]));
+ // row 1
+ assertThat(allItems.get(1).getUser(), is(users[0]));
+ assertThat(allItems.get(1).getPet(), is(pets[1]));
+ // row 2
+ assertThat(allItems.get(2).getUser(), is(users[1]));
+ assertThat(allItems.get(2).getPet(), is(nullValue()));
+
+ mDatabase.clearAllTables();
+ drain();
+ assertThat(liveData.getValue(), is(emptyList()));
+ }
+
+ @Test
+ public void relation_liveData() throws TimeoutException, InterruptedException {
+ LiveData<UserAndAllPets> liveData = mRawDao
+ .getUserAndAllPetsObservable("SELECT * FROM User WHERE mId = 3");
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> liveData.observeForever(user -> {
+ })
+ );
+ drain();
+ User user = TestUtil.createUser(3);
+ mUserDao.insert(user);
+ drain();
+ assertThat(liveData.getValue().user, is(user));
+ assertThat(liveData.getValue().pets, is(emptyList()));
+ Pet[] pets = TestUtil.createPetsForUser(3, 1, 5);
+ mPetDao.insertAll(pets);
+ drain();
+ assertThat(liveData.getValue().user, is(user));
+ assertThat(liveData.getValue().pets, is(Arrays.asList(pets)));
+ }
+
private void drain() throws TimeoutException, InterruptedException {
mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
}
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index 89c87f6..ab5b162 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -22,6 +22,15 @@
project.ext.noDocs = true
+android {
+ buildTypes {
+ getByName("release") {
+ minifyEnabled = true
+ proguardFiles("proguard-rules.pro")
+ }
+ }
+}
+
dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
compile project(':arch:runtime')
diff --git a/work/workmanager/proguard-rules.pro b/work/workmanager/proguard-rules.pro
index 19a807d..202a02c 100644
--- a/work/workmanager/proguard-rules.pro
+++ b/work/workmanager/proguard-rules.pro
@@ -3,3 +3,6 @@
public void debug(...);
public void info(...);
}
+
+-keep class * extends androidx.work.Worker
+-keep class * extends androidx.work.InputMerger
diff --git a/work/workmanager/src/main/java/androidx/work/State.java b/work/workmanager/src/main/java/androidx/work/State.java
index 8390df4..a654456 100644
--- a/work/workmanager/src/main/java/androidx/work/State.java
+++ b/work/workmanager/src/main/java/androidx/work/State.java
@@ -50,5 +50,14 @@
/**
* The status for work that has been cancelled and will not execute
*/
- CANCELLED
+ CANCELLED;
+
+ /**
+ * Returns {@code true} if this State is considered finished.
+ *
+ * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and {@link #CANCELLED} States
+ */
+ public boolean isFinished() {
+ return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
+ }
}
diff --git a/work/workmanager/src/main/java/androidx/work/Worker.java b/work/workmanager/src/main/java/androidx/work/Worker.java
index 8dcb8be..cac7984 100644
--- a/work/workmanager/src/main/java/androidx/work/Worker.java
+++ b/work/workmanager/src/main/java/androidx/work/Worker.java
@@ -17,6 +17,7 @@
package androidx.work;
import android.content.Context;
+import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
@@ -90,6 +91,7 @@
return mOutput;
}
+ @Keep
private void internalInit(
Context appContext,
@NonNull String id,