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 }