blob: 1f247e9d4761b7f4777b3fa513fe3a497193896f [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.room.processor
import COMMON
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.ext.PagingTypeNames
import androidx.room.ext.SupportDbTypeNames
import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
import androidx.room.testing.TestInvocation
import androidx.room.testing.TestProcessor
import androidx.room.vo.RawQueryMethod
import androidx.sqlite.db.SupportSQLiteQuery
import com.google.common.truth.Truth
import com.google.testing.compile.CompileTester
import com.google.testing.compile.JavaFileObjects
import com.google.testing.compile.JavaSourcesSubjectFactory
import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import simpleRun
class RawQueryMethodProcessorTest {
@Test
fun supportRawQuery() {
singleQueryMethod(
"""
@RawQuery
abstract public int[] foo(SupportSQLiteQuery query);
""") { query, _ ->
assertThat(query.name, `is`("foo"))
assertThat(query.runtimeQueryParam, `is`(
RawQueryMethod.RuntimeQueryParameter(
paramName = "query",
type = SupportDbTypeNames.QUERY
)
))
assertThat(query.returnType.typeName,
`is`(ArrayTypeName.of(TypeName.INT) as TypeName))
}.compilesWithoutError()
}
@Test
fun stringRawQuery() {
singleQueryMethod(
"""
@RawQuery
abstract public int[] foo(String query);
""") { _, _ ->
}.failsToCompile().withErrorContaining(RAW_QUERY_STRING_PARAMETER_REMOVED)
}
@Test
fun withObservedEntities() {
singleQueryMethod(
"""
@RawQuery(observedEntities = User.class)
abstract public LiveData<User> foo(SupportSQLiteQuery query);
""") { query, _ ->
assertThat(query.name, `is`("foo"))
assertThat(query.runtimeQueryParam, `is`(
RawQueryMethod.RuntimeQueryParameter(
paramName = "query",
type = SupportDbTypeNames.QUERY
)
))
assertThat(query.observedTableNames.size, `is`(1))
assertThat(query.observedTableNames, `is`(setOf("User")))
}.compilesWithoutError()
}
@Test
fun observableWithoutEntities() {
singleQueryMethod(
"""
@RawQuery(observedEntities = {})
abstract public LiveData<User> foo(SupportSQLiteQuery query);
""") { query, _ ->
assertThat(query.name, `is`("foo"))
assertThat(query.runtimeQueryParam, `is`(
RawQueryMethod.RuntimeQueryParameter(
paramName = "query",
type = SupportDbTypeNames.QUERY
)
))
assertThat(query.observedTableNames, `is`(emptySet()))
}.failsToCompile()
.withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
}
@Test
fun observableWithoutEntities_dataSourceFactory() {
singleQueryMethod(
"""
@RawQuery
abstract public ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, User> getOne();
""") { _, _ ->
// do nothing
}.failsToCompile()
.withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
}
@Test
fun observableWithoutEntities_positionalDataSource() {
singleQueryMethod(
"""
@RawQuery
abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne();
""") { _, _ ->
// do nothing
}.failsToCompile()
.withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
}
@Test
fun positionalDataSource() {
singleQueryMethod(
"""
@RawQuery(observedEntities = {User.class})
abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne(
SupportSQLiteQuery query);
""") { _, _ ->
// do nothing
}.compilesWithoutError()
}
@Test
fun pojo() {
val pojo: TypeName = ClassName.get("foo.bar.MyClass", "MyPojo")
singleQueryMethod(
"""
public class MyPojo {
public String foo;
public String bar;
}
@RawQuery
abstract public MyPojo foo(SupportSQLiteQuery query);
""") { query, _ ->
assertThat(query.name, `is`("foo"))
assertThat(query.runtimeQueryParam, `is`(
RawQueryMethod.RuntimeQueryParameter(
paramName = "query",
type = SupportDbTypeNames.QUERY
)
))
assertThat(query.returnType.typeName, `is`(pojo))
assertThat(query.observedTableNames, `is`(emptySet()))
}.compilesWithoutError()
}
@Test
fun void() {
singleQueryMethod(
"""
@RawQuery
abstract public void foo(SupportSQLiteQuery query);
""") { _, _ ->
}.failsToCompile().withErrorContaining(
ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE
)
}
interface RawQuerySuspendUnitDao {
@RawQuery
suspend fun foo(query: SupportSQLiteQuery)
}
@Test
fun suspendUnit() {
simpleRun { invocation ->
val daoElement =
invocation.processingEnv.requireTypeElement(RawQuerySuspendUnitDao::class)
val daoFunctionElement = daoElement.getDeclaredMethods().first()
RawQueryMethodProcessor(
baseContext = invocation.context,
containing = daoElement.asDeclaredType(),
executableElement = daoFunctionElement
).process()
}.failsToCompile().withErrorContaining(
ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE
)
}
@Test
fun noArgs() {
singleQueryMethod(
"""
@RawQuery
abstract public int[] foo();
""") { _, _ ->
}.failsToCompile().withErrorContaining(
ProcessorErrors.RAW_QUERY_BAD_PARAMS
)
}
@Test
fun tooManyArgs() {
singleQueryMethod(
"""
@RawQuery
abstract public int[] foo(SupportSQLiteQuery query,
SupportSQLiteQuery query2);
""") { _, _ ->
}.failsToCompile().withErrorContaining(
ProcessorErrors.RAW_QUERY_BAD_PARAMS
)
}
@Test
fun varargs() {
singleQueryMethod(
"""
@RawQuery
abstract public int[] foo(SupportSQLiteQuery... query);
""") { _, _ ->
}.failsToCompile().withErrorContaining(
ProcessorErrors.RAW_QUERY_BAD_PARAMS
)
}
@Test
fun observed_notAnEntity() {
singleQueryMethod(
"""
@RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
abstract public int[] foo(SupportSQLiteQuery 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(SupportSQLiteQuery 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(SupportSQLiteQuery query);
""") { method, _ ->
assertThat(method.observedTableNames, `is`(setOf("User")))
}.compilesWithoutError()
}
private fun singleQueryMethod(
vararg input: String,
handler: (RawQueryMethod, TestInvocation) -> Unit
): CompileTester {
return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
DAO_PREFIX +
input.joinToString("\n") +
DAO_SUFFIX
), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
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)
.nextRunHandler { invocation ->
val (owner, methods) = invocation.roundEnv
.getElementsAnnotatedWith(Dao::class.java)
.map {
Pair(it,
it.asTypeElement().getAllMethods().filter {
it.hasAnnotation(RawQuery::class)
}
)
}.first { it.second.isNotEmpty() }
val parser = RawQueryMethodProcessor(
baseContext = invocation.context,
containing = owner.asDeclaredType(),
executableElement = methods.first())
val parsedQuery = parser.process()
handler(parsedQuery, invocation)
true
}
.build())
}
companion object {
private const val DAO_PREFIX = """
package foo.bar;
import androidx.annotation.NonNull;
import androidx.room.*;
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.lifecycle.LiveData;
@Dao
abstract class MyClass {
"""
private const val DAO_SUFFIX = "}"
}
}