1 /* 2 * Copyright (C) 2016 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.Query 20 import androidx.room.SkipQueryVerification 21 import androidx.room.Transaction 22 import androidx.room.ext.KotlinMetadataProcessor 23 import androidx.room.ext.hasAnnotation 24 import androidx.room.parser.ParsedQuery 25 import androidx.room.parser.QueryType 26 import androidx.room.parser.SqlParser 27 import androidx.room.solver.query.result.LiveDataQueryResultBinder 28 import androidx.room.solver.query.result.PojoRowAdapter 29 import androidx.room.verifier.DatabaseVerificaitonErrors 30 import androidx.room.verifier.DatabaseVerifier 31 import androidx.room.vo.QueryMethod 32 import androidx.room.vo.QueryParameter 33 import androidx.room.vo.Warning 34 import com.google.auto.common.AnnotationMirrors 35 import com.google.auto.common.MoreElements 36 import com.google.auto.common.MoreTypes 37 import com.squareup.javapoet.TypeName 38 import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata 39 import me.eugeniomarletti.kotlin.metadata.kotlinMetadata 40 import javax.annotation.processing.ProcessingEnvironment 41 import javax.lang.model.element.ExecutableElement 42 import javax.lang.model.type.DeclaredType 43 import javax.lang.model.type.TypeKind 44 45 class QueryMethodProcessor( 46 baseContext: Context, 47 val containing: DeclaredType, 48 val executableElement: ExecutableElement, 49 val dbVerifier: DatabaseVerifier? = null 50 ) : KotlinMetadataProcessor { 51 val context = baseContext.fork(executableElement) 52 53 // for kotlin metadata 54 override val processingEnv: ProcessingEnvironment 55 get() = context.processingEnv 56 57 private val classMetadata = 58 try { 59 containing.asElement().kotlinMetadata 60 } catch (throwable: Throwable) { 61 context.logger.d(executableElement, 62 "failed to read get kotlin metadata from %s", executableElement) 63 } as? KotlinClassMetadata 64 65 fun process(): QueryMethod { 66 val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement) 67 val executableType = MoreTypes.asExecutable(asMember) 68 69 val annotation = MoreElements.getAnnotationMirror(executableElement, 70 Query::class.java).orNull() 71 context.checker.check(annotation != null, executableElement, 72 ProcessorErrors.MISSING_QUERY_ANNOTATION) 73 74 val query = if (annotation != null) { 75 val query = SqlParser.parse( 76 AnnotationMirrors.getAnnotationValue(annotation, "value").value.toString()) 77 context.checker.check(query.errors.isEmpty(), executableElement, 78 query.errors.joinToString("\n")) 79 if (!executableElement.hasAnnotation(SkipQueryVerification::class)) { 80 query.resultInfo = dbVerifier?.analyze(query.original) 81 } 82 if (query.resultInfo?.error != null) { 83 context.logger.e(executableElement, 84 DatabaseVerificaitonErrors.cannotVerifyQuery(query.resultInfo!!.error!!)) 85 } 86 87 context.checker.check(executableType.returnType.kind != TypeKind.ERROR, 88 executableElement, ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE, 89 executableElement) 90 query 91 } else { 92 ParsedQuery.MISSING 93 } 94 95 val returnTypeName = TypeName.get(executableType.returnType) 96 context.checker.notUnbound(returnTypeName, executableElement, 97 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS) 98 99 if (query.type == QueryType.DELETE) { 100 context.checker.check( 101 returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT, 102 executableElement, 103 ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT 104 ) 105 } 106 val resultBinder = context.typeAdapterStore 107 .findQueryResultBinder(executableType.returnType, query) 108 context.checker.check(resultBinder.adapter != null || query.type != QueryType.SELECT, 109 executableElement, ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER) 110 if (resultBinder is LiveDataQueryResultBinder) { 111 context.checker.check(query.type == QueryType.SELECT, executableElement, 112 ProcessorErrors.LIVE_DATA_QUERY_WITHOUT_SELECT) 113 } 114 115 val inTransaction = when (query.type) { 116 QueryType.SELECT -> executableElement.hasAnnotation(Transaction::class) 117 else -> true 118 } 119 120 if (query.type == QueryType.SELECT && !inTransaction) { 121 // put a warning if it is has relations and not annotated w/ transaction 122 resultBinder.adapter?.rowAdapter?.let { rowAdapter -> 123 if (rowAdapter is PojoRowAdapter 124 && rowAdapter.relationCollectors.isNotEmpty()) { 125 context.logger.w(Warning.RELATION_QUERY_WITHOUT_TRANSACTION, 126 executableElement, ProcessorErrors.TRANSACTION_MISSING_ON_RELATION) 127 } 128 } 129 } 130 val kotlinParameterNames = classMetadata?.getParameterNames(executableElement) 131 132 val parameters = executableElement.parameters 133 .mapIndexed { index, variableElement -> 134 QueryParameterProcessor( 135 baseContext = context, 136 containing = containing, 137 element = variableElement, 138 sqlName = kotlinParameterNames?.getOrNull(index)).process() 139 } 140 val queryMethod = QueryMethod( 141 element = executableElement, 142 query = query, 143 name = executableElement.simpleName.toString(), 144 returnType = executableType.returnType, 145 parameters = parameters, 146 inTransaction = inTransaction, 147 queryResultBinder = resultBinder) 148 149 val missing = queryMethod.sectionToParamMapping 150 .filter { it.second == null } 151 .map { it.first.text } 152 if (missing.isNotEmpty()) { 153 context.logger.e(executableElement, 154 ProcessorErrors.missingParameterForBindVariable(missing)) 155 } 156 157 val unused = queryMethod.parameters.filterNot { param -> 158 queryMethod.sectionToParamMapping.any { it.second == param } 159 }.map(QueryParameter::sqlName) 160 161 if (unused.isNotEmpty()) { 162 context.logger.e(executableElement, ProcessorErrors.unusedQueryMethodParameter(unused)) 163 } 164 return queryMethod 165 } 166 } 167