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 androidx.room.RawQuery 20 import androidx.room.Transaction 21 import androidx.room.ext.SupportDbTypeNames 22 import androidx.room.ext.hasAnnotation 23 import androidx.room.ext.toListOfClassTypes 24 import androidx.room.ext.typeName 25 import androidx.room.parser.SqlParser 26 import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED 27 import androidx.room.vo.RawQueryMethod 28 import com.google.auto.common.AnnotationMirrors 29 import com.google.auto.common.MoreElements 30 import com.google.auto.common.MoreTypes 31 import com.squareup.javapoet.TypeName 32 import javax.lang.model.element.ExecutableElement 33 import javax.lang.model.type.DeclaredType 34 35 class RawQueryMethodProcessor( 36 baseContext: Context, 37 val containing: DeclaredType, 38 val executableElement: ExecutableElement) { 39 val context = baseContext.fork(executableElement) 40 fun process(): RawQueryMethod { 41 val types = context.processingEnv.typeUtils 42 val asMember = types.asMemberOf(containing, executableElement) 43 val executableType = MoreTypes.asExecutable(asMember) 44 45 val annotation = MoreElements.getAnnotationMirror(executableElement, 46 RawQuery::class.java).orNull() 47 context.checker.check(annotation != null, executableElement, 48 ProcessorErrors.MISSING_RAWQUERY_ANNOTATION) 49 50 val returnTypeName = TypeName.get(executableType.returnType) 51 context.checker.notUnbound(returnTypeName, executableElement, 52 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS) 53 val observedTableNames = processObservedTables() 54 val query = SqlParser.rawQueryForTables(observedTableNames) 55 // build the query but don't calculate result info since we just guessed it. 56 val resultBinder = context.typeAdapterStore 57 .findQueryResultBinder(executableType.returnType, query) 58 59 val runtimeQueryParam = findRuntimeQueryParameter() 60 val inTransaction = executableElement.hasAnnotation(Transaction::class) 61 val rawQueryMethod = RawQueryMethod( 62 element = executableElement, 63 name = executableElement.simpleName.toString(), 64 observedTableNames = observedTableNames, 65 returnType = executableType.returnType, 66 runtimeQueryParam = runtimeQueryParam, 67 inTransaction = inTransaction, 68 queryResultBinder = resultBinder 69 ) 70 context.checker.check(rawQueryMethod.returnsValue, executableElement, 71 ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE) 72 return rawQueryMethod 73 } 74 75 private fun processObservedTables(): Set<String> { 76 val annotation = MoreElements 77 .getAnnotationMirror(executableElement, 78 androidx.room.RawQuery::class.java) 79 .orNull() ?: return emptySet() 80 val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities") 81 return entityList 82 .toListOfClassTypes() 83 .map { 84 MoreTypes.asTypeElement(it) 85 } 86 .flatMap { 87 if (it.hasAnnotation(androidx.room.Entity::class)) { 88 val entity = EntityProcessor( 89 baseContext = context, 90 element = it 91 ).process() 92 arrayListOf(entity.tableName) 93 } else { 94 val pojo = PojoProcessor( 95 baseContext = context, 96 element = it, 97 bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR, 98 parent = null 99 ).process() 100 val tableNames = pojo.accessedTableNames() 101 // if it is empty, report error as it does not make sense 102 if (tableNames.isEmpty()) { 103 context.logger.e(executableElement, 104 ProcessorErrors.rawQueryBadEntity(it.asType().typeName())) 105 } 106 tableNames 107 } 108 }.toSet() 109 } 110 111 private fun findRuntimeQueryParameter(): RawQueryMethod.RuntimeQueryParameter? { 112 val types = context.processingEnv.typeUtils 113 if (executableElement.parameters.size == 1 && !executableElement.isVarArgs) { 114 val param = MoreTypes.asMemberOf( 115 types, 116 containing, 117 executableElement.parameters[0]) 118 val elementUtils = context.processingEnv.elementUtils 119 val supportQueryType = elementUtils 120 .getTypeElement(SupportDbTypeNames.QUERY.toString()).asType() 121 val isSupportSql = types.isAssignable(param, supportQueryType) 122 if (isSupportSql) { 123 return RawQueryMethod.RuntimeQueryParameter( 124 paramName = executableElement.parameters[0].simpleName.toString(), 125 type = supportQueryType.typeName()) 126 } 127 val stringType = elementUtils.getTypeElement("java.lang.String").asType() 128 val isString = types.isAssignable(param, stringType) 129 if (isString) { 130 // special error since this was initially allowed but removed in 1.1 beta1 131 context.logger.e(executableElement, RAW_QUERY_STRING_PARAMETER_REMOVED) 132 return null 133 } 134 } 135 context.logger.e(executableElement, ProcessorErrors.RAW_QUERY_BAD_PARAMS) 136 return null 137 } 138 }