1 /* <lambda>null2 * 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.compiler.processing.XAnnotation 23 import androidx.room.compiler.processing.XMethodElement 24 import androidx.room.compiler.processing.XType 25 import androidx.room.ext.isNotError 26 import androidx.room.parser.ParsedQuery 27 import androidx.room.parser.QueryType 28 import androidx.room.parser.SqlParser 29 import androidx.room.processor.ProcessorErrors.cannotMapSpecifiedColumn 30 import androidx.room.solver.TypeAdapterExtras 31 import androidx.room.solver.query.result.DataClassRowAdapter 32 import androidx.room.verifier.ColumnInfo 33 import androidx.room.verifier.DatabaseVerificationErrors 34 import androidx.room.verifier.DatabaseVerifier 35 import androidx.room.vo.MapInfo 36 import androidx.room.vo.QueryFunction 37 import androidx.room.vo.QueryParameter 38 import androidx.room.vo.ReadQueryFunction 39 import androidx.room.vo.Warning 40 import androidx.room.vo.WriteQueryFunction 41 42 class QueryFunctionProcessor( 43 baseContext: Context, 44 val containing: XType, 45 val executableElement: XMethodElement, 46 val dbVerifier: DatabaseVerifier? = null 47 ) { 48 val context = baseContext.fork(executableElement) 49 50 /** 51 * The processing of the function might happen in multiple steps if we decide to rewrite the 52 * query after the first processing. To allow it while respecting the Context, it is implemented 53 * as a sub procedure in [InternalQueryProcessor]. 54 */ 55 fun process(): QueryFunction { 56 val annotation = executableElement.getAnnotation(Query::class) 57 context.checker.check( 58 annotation != null, 59 executableElement, 60 ProcessorErrors.MISSING_QUERY_ANNOTATION 61 ) 62 63 /** 64 * Run the first process without reporting any errors / warnings as we might be able to fix 65 * them for the developer. 66 */ 67 val (initialResult, logs) = 68 context.collectLogs { 69 InternalQueryProcessor( 70 context = it, 71 executableElement = executableElement, 72 dbVerifier = dbVerifier, 73 containing = containing 74 ) 75 .processQuery(annotation?.getAsString("value")) 76 } 77 // check if want to swap the query for a better one 78 val finalResult = 79 if (initialResult is ReadQueryFunction) { 80 val resultAdapter = initialResult.queryResultBinder.adapter 81 val originalQuery = initialResult.query 82 val finalQuery = 83 resultAdapter?.let { 84 context.queryRewriter.rewrite(originalQuery, resultAdapter) 85 } ?: originalQuery 86 if (finalQuery != originalQuery) { 87 // ok parse again 88 return InternalQueryProcessor( 89 context = context, 90 executableElement = executableElement, 91 dbVerifier = dbVerifier, 92 containing = containing 93 ) 94 .processQuery(finalQuery.original) 95 } else { 96 initialResult 97 } 98 } else { 99 initialResult 100 } 101 if (finalResult == initialResult) { 102 // if we didn't rewrite it, send all logs to the calling context. 103 logs.writeTo(context) 104 } 105 return finalResult 106 } 107 } 108 109 private class InternalQueryProcessor( 110 val context: Context, 111 val executableElement: XMethodElement, 112 val containing: XType, 113 val dbVerifier: DatabaseVerifier? = null 114 ) { processQuerynull115 fun processQuery(input: String?): QueryFunction { 116 val delegate = FunctionProcessorDelegate.createFor(context, containing, executableElement) 117 val returnType = delegate.extractReturnType() 118 119 val returnsDeferredType = delegate.returnsDeferredType() 120 val isSuspendFunction = delegate.executableElement.isSuspendFunction() 121 context.checker.check( 122 !isSuspendFunction || !returnsDeferredType, 123 executableElement, 124 ProcessorErrors.suspendReturnsDeferredType(returnType.rawType.typeName.toString()) 125 ) 126 127 val query = 128 if (!isSuspendFunction && !returnsDeferredType && !context.isAndroidOnlyTarget()) { 129 // A blocking function that does not return a deferred return type is not allowed 130 // if the target platforms include non-Android targets. 131 context.logger.e( 132 executableElement, 133 ProcessorErrors.INVALID_BLOCKING_DAO_FUNCTION_NON_ANDROID 134 ) 135 // Early return so we don't generate redundant code. 136 ParsedQuery.MISSING 137 } else if (input != null) { 138 val query = SqlParser.parse(input) 139 context.checker.check( 140 query.errors.isEmpty(), 141 executableElement, 142 query.errors.joinToString("\n") 143 ) 144 validateQuery(query) 145 context.checker.check( 146 returnType.isNotError(), 147 executableElement, 148 ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE, 149 executableElement 150 ) 151 query 152 } else { 153 ParsedQuery.MISSING 154 } 155 156 context.checker.notUnbound( 157 returnType, 158 executableElement, 159 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_FUNCTIONS 160 ) 161 162 val isPreparedQuery = PREPARED_TYPES.contains(query.type) 163 val queryFunction = 164 if (isPreparedQuery) { 165 getPreparedQueryFunction(delegate, returnType, query) 166 } else { 167 getQueryFunction(delegate, returnType, query) 168 } 169 170 return processQueryFunction(queryFunction) 171 } 172 processQueryFunctionnull173 private fun processQueryFunction(queryFunction: QueryFunction): QueryFunction { 174 val missing = 175 queryFunction.sectionToParamMapping.filter { it.second == null }.map { it.first.text } 176 if (missing.isNotEmpty()) { 177 context.logger.e( 178 executableElement, 179 ProcessorErrors.missingParameterForBindVariable(missing) 180 ) 181 } 182 183 val unused = 184 queryFunction.parameters 185 .filterNot { param -> 186 queryFunction.sectionToParamMapping.any { it.second == param } 187 } 188 .map(QueryParameter::sqlName) 189 190 if (unused.isNotEmpty()) { 191 context.logger.e( 192 executableElement, 193 ProcessorErrors.unusedQueryFunctionParameter(unused) 194 ) 195 } 196 return queryFunction 197 } 198 validateQuerynull199 private fun validateQuery(query: ParsedQuery) { 200 val skipQueryVerification = executableElement.hasAnnotation(SkipQueryVerification::class) 201 if (skipQueryVerification) { 202 return 203 } 204 query.resultInfo = dbVerifier?.analyze(query.original) 205 if (query.resultInfo?.error != null) { 206 context.logger.e( 207 executableElement, 208 DatabaseVerificationErrors.cannotVerifyQuery(query.resultInfo!!.error!!) 209 ) 210 } 211 } 212 getPreparedQueryFunctionnull213 private fun getPreparedQueryFunction( 214 delegate: FunctionProcessorDelegate, 215 returnType: XType, 216 query: ParsedQuery 217 ): WriteQueryFunction { 218 val resultBinder = delegate.findPreparedResultBinder(returnType, query) 219 context.checker.check( 220 resultBinder.adapter != null, 221 executableElement, 222 ProcessorErrors.cannotFindPreparedQueryResultAdapter( 223 returnType.asTypeName().toString(context.codeLanguage), 224 query.type 225 ) 226 ) 227 228 val parameters = delegate.extractQueryParams(query) 229 return WriteQueryFunction( 230 element = executableElement, 231 query = query, 232 returnType = returnType, 233 parameters = parameters, 234 preparedQueryResultBinder = resultBinder 235 ) 236 } 237 238 @Suppress("DEPRECATION") // Due to MapInfo usage getQueryFunctionnull239 private fun getQueryFunction( 240 delegate: FunctionProcessorDelegate, 241 returnType: XType, 242 query: ParsedQuery 243 ): QueryFunction { 244 val resultBinder = 245 delegate.findResultBinder(returnType, query) { 246 delegate.executableElement.getAnnotation(androidx.room.MapInfo::class)?.let { 247 processMapInfo(it, query, delegate.executableElement, this) 248 } 249 } 250 context.checker.check( 251 resultBinder.adapter != null, 252 executableElement, 253 ProcessorErrors.cannotFindQueryResultAdapter( 254 returnType.asTypeName().toString(context.codeLanguage) 255 ) 256 ) 257 258 val inTransaction = executableElement.hasAnnotation(Transaction::class) 259 if (query.type == QueryType.SELECT && !inTransaction) { 260 // put a warning if it is has relations and not annotated w/ transaction 261 val hasRelations = 262 resultBinder.adapter?.rowAdapters?.any { adapter -> 263 adapter is DataClassRowAdapter && adapter.relationCollectors.isNotEmpty() 264 } == true 265 if (hasRelations) { 266 context.logger.w( 267 Warning.RELATION_QUERY_WITHOUT_TRANSACTION, 268 executableElement, 269 ProcessorErrors.TRANSACTION_MISSING_ON_RELATION 270 ) 271 } 272 } 273 274 query.resultInfo?.let { queryResultInfo -> 275 val mappings = resultBinder.adapter?.mappings ?: return@let 276 // If there are no mapping (e.g. might be a primitive return type result), then we 277 // can't reasonable determine cursor mismatch. 278 if ( 279 mappings.isEmpty() || mappings.none { it is DataClassRowAdapter.DataClassMapping } 280 ) { 281 return@let 282 } 283 val usedColumns = mappings.flatMap { it.usedColumns } 284 val columnNames = queryResultInfo.columns.map { it.name } 285 val unusedColumns = columnNames - usedColumns 286 val dataClassMappings = 287 mappings.filterIsInstance<DataClassRowAdapter.DataClassMapping>() 288 val pojoUnusedFields = 289 dataClassMappings 290 .filter { it.unusedFields.isNotEmpty() } 291 .associate { 292 it.dataClass.typeName.toString(context.codeLanguage) to it.unusedFields 293 } 294 if (unusedColumns.isNotEmpty() || pojoUnusedFields.isNotEmpty()) { 295 val warningMsg = 296 ProcessorErrors.queryPropertyDataClassMismatch( 297 dataClassTypeNames = 298 dataClassMappings.map { 299 it.dataClass.typeName.toString(context.codeLanguage) 300 }, 301 unusedColumns = unusedColumns, 302 allColumns = columnNames, 303 dataClassUnusedProperties = pojoUnusedFields, 304 ) 305 context.logger.w(Warning.QUERY_MISMATCH, executableElement, warningMsg) 306 } 307 } 308 309 val parameters = delegate.extractQueryParams(query) 310 311 return ReadQueryFunction( 312 element = executableElement, 313 query = query, 314 returnType = returnType, 315 parameters = parameters, 316 inTransaction = inTransaction, 317 queryResultBinder = resultBinder 318 ) 319 } 320 321 /** 322 * Parse @MapInfo annotation, validate its inputs and put information in the bag of extras, it 323 * will be later used by the TypeAdapterStore. 324 */ 325 @Suppress("DEPRECATION") // Due to @MapInfo usage processMapInfonull326 private fun processMapInfo( 327 mapInfoAnnotation: XAnnotation, 328 query: ParsedQuery, 329 queryExecutableElement: XMethodElement, 330 adapterExtras: TypeAdapterExtras, 331 ) { 332 val keyColumn = mapInfoAnnotation["keyColumn"]?.asString() ?: "" 333 val keyTable = mapInfoAnnotation["keyTable"]?.asString()?.ifEmpty { null } 334 val valueColumn = mapInfoAnnotation["valueColumn"]?.asString() ?: "" 335 val valueTable = mapInfoAnnotation["valueTable"]?.asString()?.ifEmpty { null } 336 337 val resultTableAliases = query.tables.associate { it.name to it.alias } 338 // Checks if this list of columns contains one with matching name and origin table. 339 // Takes into account that projection tables names might be aliased but originTable uses 340 // sqlite3_column_origin_name which is un-aliased. 341 fun List<ColumnInfo>.contains(columnName: String, tableName: String?) = 342 any { resultColumn -> 343 val resultTableAlias = 344 resultColumn.originTable?.let { resultTableAliases[it] ?: it } 345 resultColumn.name == columnName && 346 (if (tableName != null) { 347 resultTableAlias == tableName || resultColumn.originTable == tableName 348 } else true) 349 } 350 351 context.checker.check( 352 keyColumn.isNotEmpty() || valueColumn.isNotEmpty(), 353 queryExecutableElement, 354 ProcessorErrors.MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED 355 ) 356 357 val resultColumns = query.resultInfo?.columns 358 359 if (resultColumns != null) { 360 context.checker.check( 361 keyColumn.isEmpty() || resultColumns.contains(keyColumn, keyTable), 362 queryExecutableElement 363 ) { 364 cannotMapSpecifiedColumn( 365 (if (keyTable != null) "$keyTable." else "") + keyColumn, 366 resultColumns.map { it.name }, 367 androidx.room.MapInfo::class.java.simpleName 368 ) 369 } 370 context.checker.check( 371 valueColumn.isEmpty() || resultColumns.contains(valueColumn, valueTable), 372 queryExecutableElement 373 ) { 374 cannotMapSpecifiedColumn( 375 (if (valueTable != null) "$valueTable." else "") + valueColumn, 376 resultColumns.map { it.name }, 377 androidx.room.MapInfo::class.java.simpleName 378 ) 379 } 380 } 381 382 adapterExtras.putData(MapInfo::class, MapInfo(keyColumn, valueColumn)) 383 } 384 385 companion object { 386 val PREPARED_TYPES = arrayOf(QueryType.INSERT, QueryType.DELETE, QueryType.UPDATE) 387 } 388 } 389