Home | History | Annotate | Download | only in processor
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.room.processor
     18 
     19 import COMMON
     20 import androidx.room.ColumnInfo
     21 import androidx.room.Dao
     22 import androidx.room.Entity
     23 import androidx.room.PrimaryKey
     24 import androidx.room.Query
     25 import androidx.room.RawQuery
     26 import androidx.room.ext.PagingTypeNames
     27 import androidx.room.ext.SupportDbTypeNames
     28 import androidx.room.ext.hasAnnotation
     29 import androidx.room.ext.typeName
     30 import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
     31 import androidx.room.testing.TestInvocation
     32 import androidx.room.testing.TestProcessor
     33 import androidx.room.vo.RawQueryMethod
     34 import com.google.auto.common.MoreElements
     35 import com.google.auto.common.MoreTypes
     36 import com.google.common.truth.Truth
     37 import com.google.testing.compile.CompileTester
     38 import com.google.testing.compile.JavaFileObjects
     39 import com.google.testing.compile.JavaSourcesSubjectFactory
     40 import com.squareup.javapoet.ArrayTypeName
     41 import com.squareup.javapoet.ClassName
     42 import com.squareup.javapoet.TypeName
     43 import org.hamcrest.CoreMatchers.`is`
     44 import org.hamcrest.MatcherAssert.assertThat
     45 import org.junit.Test
     46 
     47 class RawQueryMethodProcessorTest {
     48     @Test
     49     fun supportRawQuery() {
     50         singleQueryMethod(
     51                 """
     52                 @RawQuery
     53                 abstract public int[] foo(SupportSQLiteQuery query);
     54                 """) { query, _ ->
     55             assertThat(query.name, `is`("foo"))
     56             assertThat(query.runtimeQueryParam, `is`(
     57                     RawQueryMethod.RuntimeQueryParameter(
     58                             paramName = "query",
     59                             type = SupportDbTypeNames.QUERY
     60                     )
     61             ))
     62             assertThat(query.returnType.typeName(),
     63                     `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
     64         }.compilesWithoutError()
     65     }
     66 
     67     @Test
     68     fun stringRawQuery() {
     69         singleQueryMethod(
     70                 """
     71                 @RawQuery
     72                 abstract public int[] foo(String query);
     73                 """) { _, _ ->
     74         }.failsToCompile().withErrorContaining(RAW_QUERY_STRING_PARAMETER_REMOVED)
     75     }
     76 
     77     @Test
     78     fun withObservedEntities() {
     79         singleQueryMethod(
     80                 """
     81                 @RawQuery(observedEntities = User.class)
     82                 abstract public LiveData<User> foo(SupportSQLiteQuery query);
     83                 """) { query, _ ->
     84             assertThat(query.name, `is`("foo"))
     85             assertThat(query.runtimeQueryParam, `is`(
     86                     RawQueryMethod.RuntimeQueryParameter(
     87                             paramName = "query",
     88                             type = SupportDbTypeNames.QUERY
     89                     )
     90             ))
     91             assertThat(query.observedTableNames.size, `is`(1))
     92             assertThat(query.observedTableNames, `is`(setOf("User")))
     93         }.compilesWithoutError()
     94     }
     95 
     96     @Test
     97     fun observableWithoutEntities() {
     98         singleQueryMethod(
     99                 """
    100                 @RawQuery(observedEntities = {})
    101                 abstract public LiveData<User> foo(SupportSQLiteQuery query);
    102                 """) { query, _ ->
    103             assertThat(query.name, `is`("foo"))
    104             assertThat(query.runtimeQueryParam, `is`(
    105                     RawQueryMethod.RuntimeQueryParameter(
    106                             paramName = "query",
    107                             type = SupportDbTypeNames.QUERY
    108                     )
    109             ))
    110             assertThat(query.observedTableNames, `is`(emptySet()))
    111         }.failsToCompile()
    112                 .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
    113     }
    114 
    115     @Test
    116     fun observableWithoutEntities_dataSourceFactory() {
    117         singleQueryMethod(
    118                 """
    119                 @RawQuery
    120                 abstract public ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, User> getOne();
    121                 """) { _, _ ->
    122             // do nothing
    123         }.failsToCompile()
    124                 .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
    125     }
    126 
    127     @Test
    128     fun observableWithoutEntities_positionalDataSource() {
    129         singleQueryMethod(
    130                 """
    131                 @RawQuery
    132                 abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne();
    133                 """) { _, _ ->
    134             // do nothing
    135         }.failsToCompile()
    136                 .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
    137     }
    138 
    139     @Test
    140     fun positionalDataSource() {
    141         singleQueryMethod(
    142                 """
    143                 @RawQuery(observedEntities = {User.class})
    144                 abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne(
    145                         SupportSQLiteQuery query);
    146                 """) { _, _ ->
    147             // do nothing
    148         }.compilesWithoutError()
    149     }
    150 
    151     @Test
    152     fun pojo() {
    153         val pojo: TypeName = ClassName.get("foo.bar.MyClass", "MyPojo")
    154         singleQueryMethod(
    155                 """
    156                 public class MyPojo {
    157                     public String foo;
    158                     public String bar;
    159                 }
    160 
    161                 @RawQuery
    162                 abstract public MyPojo foo(SupportSQLiteQuery query);
    163                 """) { query, _ ->
    164             assertThat(query.name, `is`("foo"))
    165             assertThat(query.runtimeQueryParam, `is`(
    166                     RawQueryMethod.RuntimeQueryParameter(
    167                             paramName = "query",
    168                             type = SupportDbTypeNames.QUERY
    169                     )
    170             ))
    171             assertThat(query.returnType.typeName(), `is`(pojo))
    172             assertThat(query.observedTableNames, `is`(emptySet()))
    173         }.compilesWithoutError()
    174     }
    175 
    176     @Test
    177     fun void() {
    178         singleQueryMethod(
    179                 """
    180                 @RawQuery
    181                 abstract public void foo(SupportSQLiteQuery query);
    182                 """) { _, _ ->
    183         }.failsToCompile().withErrorContaining(
    184                 ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE
    185         )
    186     }
    187 
    188     @Test
    189     fun noArgs() {
    190         singleQueryMethod(
    191                 """
    192                 @RawQuery
    193                 abstract public int[] foo();
    194                 """) { _, _ ->
    195         }.failsToCompile().withErrorContaining(
    196                 ProcessorErrors.RAW_QUERY_BAD_PARAMS
    197         )
    198     }
    199 
    200     @Test
    201     fun tooManyArgs() {
    202         singleQueryMethod(
    203                 """
    204                 @RawQuery
    205                 abstract public int[] foo(SupportSQLiteQuery query,
    206                                           SupportSQLiteQuery query2);
    207                 """) { _, _ ->
    208         }.failsToCompile().withErrorContaining(
    209                 ProcessorErrors.RAW_QUERY_BAD_PARAMS
    210         )
    211     }
    212 
    213     @Test
    214     fun varargs() {
    215         singleQueryMethod(
    216                 """
    217                 @RawQuery
    218                 abstract public int[] foo(SupportSQLiteQuery... query);
    219                 """) { _, _ ->
    220         }.failsToCompile().withErrorContaining(
    221                 ProcessorErrors.RAW_QUERY_BAD_PARAMS
    222         )
    223     }
    224 
    225     @Test
    226     fun observed_notAnEntity() {
    227         singleQueryMethod(
    228                 """
    229                 @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
    230                 abstract public int[] foo(SupportSQLiteQuery query);
    231                 """) { _, _ ->
    232         }.failsToCompile().withErrorContaining(
    233                 ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME)
    234         )
    235     }
    236 
    237     @Test
    238     fun observed_relationPojo() {
    239         singleQueryMethod(
    240                 """
    241                 public static class MyPojo {
    242                     public String foo;
    243                     @Relation(
    244                         parentColumn = "foo",
    245                         entityColumn = "name"
    246                     )
    247                     public java.util.List<User> users;
    248                 }
    249                 @RawQuery(observedEntities = MyPojo.class)
    250                 abstract public int[] foo(SupportSQLiteQuery query);
    251                 """) { method, _ ->
    252             assertThat(method.observedTableNames, `is`(setOf("User")))
    253         }.compilesWithoutError()
    254     }
    255 
    256     @Test
    257     fun observed_embedded() {
    258         singleQueryMethod(
    259                 """
    260                 public static class MyPojo {
    261                     public String foo;
    262                     @Embedded
    263                     public User users;
    264                 }
    265                 @RawQuery(observedEntities = MyPojo.class)
    266                 abstract public int[] foo(SupportSQLiteQuery query);
    267                 """) { method, _ ->
    268             assertThat(method.observedTableNames, `is`(setOf("User")))
    269         }.compilesWithoutError()
    270     }
    271 
    272     private fun singleQueryMethod(
    273             vararg input: String,
    274             handler: (RawQueryMethod, TestInvocation) -> Unit
    275     ): CompileTester {
    276         return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
    277                 .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
    278                         DAO_PREFIX
    279                                 + input.joinToString("\n")
    280                                 + DAO_SUFFIX
    281                 ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
    282                         COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE,
    283                         COMMON.NOT_AN_ENTITY))
    284                 .processedWith(TestProcessor.builder()
    285                         .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
    286                                 Entity::class, PrimaryKey::class, RawQuery::class)
    287                         .nextRunHandler { invocation ->
    288                             val (owner, methods) = invocation.roundEnv
    289                                     .getElementsAnnotatedWith(Dao::class.java)
    290                                     .map {
    291                                         Pair(it,
    292                                                 invocation.processingEnv.elementUtils
    293                                                         .getAllMembers(MoreElements.asType(it))
    294                                                         .filter {
    295                                                             it.hasAnnotation(RawQuery::class)
    296                                                         }
    297                                         )
    298                                     }.first { it.second.isNotEmpty() }
    299                             val parser = RawQueryMethodProcessor(
    300                                     baseContext = invocation.context,
    301                                     containing = MoreTypes.asDeclared(owner.asType()),
    302                                     executableElement = MoreElements.asExecutable(methods.first()))
    303                             val parsedQuery = parser.process()
    304                             handler(parsedQuery, invocation)
    305                             true
    306                         }
    307                         .build())
    308     }
    309 
    310     companion object {
    311         private const val DAO_PREFIX = """
    312                 package foo.bar;
    313                 import androidx.annotation.NonNull;
    314                 import androidx.room.*;
    315                 import androidx.sqlite.db.SupportSQLiteQuery;
    316                 import androidx.lifecycle.LiveData;
    317                 @Dao
    318                 abstract class MyClass {
    319                 """
    320         private const val DAO_SUFFIX = "}"
    321     }
    322 }