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.Delete
20 import androidx.room.Insert
21 import androidx.room.Query
22 import androidx.room.RawQuery
23 import androidx.room.RewriteQueriesToDropUnusedColumns
24 import androidx.room.Update
25 import androidx.room.Upsert
26 import androidx.room.ext.KotlinTypeNames
27 import androidx.room.ext.RoomTypeNames
28 import androidx.room.ext.RoomTypeNames.ROOM_DB
29 import androidx.room.parser.QueryType
30 import androidx.room.parser.SQLTypeAffinity
31 import androidx.room.vo.CustomTypeConverter
32 import androidx.room.vo.Property
33 
34 object ProcessorErrors {
35     private fun String.trim(): String {
36         return this.trimIndent().replace("\n", " ")
37     }
38 
39     const val ISSUE_TRACKER_LINK = "https://issuetracker.google.com/issues/new?component=413107"
40 
41     val MISSING_QUERY_ANNOTATION = "Query functions must be annotated with ${Query::class.java}"
42     val MISSING_INSERT_ANNOTATION = "Insert functions must be annotated with ${Insert::class.java}"
43     val MISSING_DELETE_ANNOTATION = "Delete functions must be annotated with ${Delete::class.java}"
44     val MISSING_UPDATE_ANNOTATION = "Update functions must be annotated with ${Update::class.java}"
45     val MISSING_UPSERT_ANNOTATION = "Upsert functions must be annotated with ${Upsert::class.java}"
46     val MISSING_RAWQUERY_ANNOTATION =
47         "RawQuery functions must be annotated with" + " ${RawQuery::class.java}"
48     const val INVALID_ON_CONFLICT_VALUE =
49         "On conflict value must be one of @OnConflictStrategy values."
50     const val TRANSACTION_REFERENCE_DOCS =
51         "https://developer.android.com/reference/androidx/" + "room/Transaction.html"
52     val INVALID_ANNOTATION_COUNT_IN_DAO_FUNCTION =
53         "An abstract DAO function must be" +
54             " annotated with one and only one of the following annotations: " +
55             DaoProcessor.PROCESSED_ANNOTATIONS.joinToString(", ") { "@" + it.java.simpleName }
56     val INVALID_ANNOTATION_IN_DAO_PROPERTY =
57         "An abstract DAO property must be" + " annotated with @get:${Query::class.java}."
58     const val CANNOT_RESOLVE_RETURN_TYPE = "Cannot resolve return type for %s"
59     const val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_FUNCTIONS =
60         "Cannot use unbound generics in query" +
61             " functions. It must be bound to a type through base Dao class."
62     const val CANNOT_USE_UNBOUND_GENERICS_IN_INSERT_FUNCTIONS =
63         "Cannot use unbound generics in" +
64             " insert functions. It must be bound to a type through base Dao class."
65     const val CANNOT_USE_UNBOUND_GENERICS_IN_UPSERT_FUNCTIONS =
66         "Cannot use unbound generics in" +
67             " upsert functions. It must be bound to a type through base Dao class."
68     const val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_PROPERTIES =
69         "Cannot use unbound properties in entities."
70     const val CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES =
71         "Cannot use unbound generics in Dao classes." +
72             " If you are trying to create a base DAO, create a normal class, extend it with type" +
73             " params then mark the subclass with @Dao."
74     const val CANNOT_USE_MAP_COLUMN_AND_MAP_INFO_SIMULTANEOUSLY =
75         "Cannot use @MapColumn and " +
76             " @MapInfo annotation in the same function. Please prefer using @MapColumn only."
77     const val CANNOT_FIND_GETTER_FOR_PROPERTY = "Cannot find getter for property."
78     const val CANNOT_FIND_SETTER_FOR_PROPERTY = "Cannot find setter for property."
79     const val MISSING_PRIMARY_KEY =
80         "An entity must have at least 1 property annotated with @PrimaryKey"
81     const val AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT =
82         "If a primary key is annotated with" +
83             " autoGenerate, its type must be int, Integer, long or Long."
84     const val AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_PROPERTIES =
85         "When @PrimaryKey annotation is used on a" +
86             " property annotated with @Embedded, the embedded class should have only 1 property."
87     const val INVALID_INDEX_ORDERS_SIZE =
88         "The number of entries in @Index#orders() should be " +
89             "equal to the amount of columns defined in the @Index value."
90 
91     const val DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP =
92         "Do not use ImmutableMultimap as a type (as with" +
93             " Multimap itself). Instead use the subtypes such as ImmutableSetMultimap or " +
94             "ImmutableListMultimap."
95 
96     fun multiplePrimaryKeyAnnotations(primaryKeys: List<String>): String {
97         return """
98                 You cannot have multiple primary keys defined in an Entity. If you
99                 want to declare a composite primary key, you should use @Entity#primaryKeys and
100                 not use @PrimaryKey. Defined Primary Keys:
101                 ${primaryKeys.joinToString(", ")}"""
102             .trim()
103     }
104 
105     fun primaryKeyColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
106         return "$columnName referenced in the primary key does not exist in the Entity." +
107             " Available column names:${allColumns.joinToString(", ")}"
108     }
109 
110     const val DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE =
111         "Dao class must be an abstract class or" + " an interface"
112     const val DAO_MUST_BE_ANNOTATED_WITH_DAO = "Dao class must be annotated with @Dao"
113 
114     fun daoMustHaveMatchingConstructor(daoName: String, dbName: String): String {
115         return """
116                 $daoName needs to have either an empty constructor or a constructor that takes
117                 $dbName as its only parameter.
118                 """
119             .trim()
120     }
121 
122     const val ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY = "Entity class must be annotated with @Entity"
123     const val DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES =
124         "@Database annotation must specify list" + " of entities"
125     const val COLUMN_NAME_CANNOT_BE_EMPTY =
126         "Column name cannot be blank. If you don't want to set it" +
127             ", just remove the @ColumnInfo annotation or use @ColumnInfo.INHERIT_PROPERTY_NAME."
128 
129     const val ENTITY_TABLE_NAME_CANNOT_BE_EMPTY =
130         "Entity table name cannot be blank. If you don't want" +
131             " to set it, just remove the tableName property."
132 
133     const val ENTITY_TABLE_NAME_CANNOT_START_WITH_SQLITE =
134         "Entity table name cannot start with \"sqlite_\"."
135 
136     const val VIEW_MUST_BE_ANNOTATED_WITH_DATABASE_VIEW =
137         "View class must be annotated with " + "@DatabaseView"
138     const val VIEW_NAME_CANNOT_BE_EMPTY =
139         "View name cannot be blank. If you don't want" +
140             " to set it, just remove the viewName property."
141     const val VIEW_NAME_CANNOT_START_WITH_SQLITE = "View name cannot start with \"sqlite_\"."
142     const val VIEW_QUERY_MUST_BE_SELECT = "Query for @DatabaseView must be a SELECT."
143     const val VIEW_QUERY_CANNOT_TAKE_ARGUMENTS =
144         "Query for @DatabaseView cannot take any arguments."
145 
146     fun viewCircularReferenceDetected(views: List<String>): String {
147         return "Circular reference detected among views: ${views.joinToString(", ")}"
148     }
149 
150     const val CANNOT_BIND_QUERY_PARAMETER_INTO_STMT =
151         "Query function parameters should either be a" +
152             " type that can be converted into a database column or a List / Array that contains" +
153             " such type. You can consider adding a Type Adapter for this."
154 
155     const val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE =
156         "Query/Insert function parameters cannot " + "start with underscore (_)."
157 
158     fun cannotFindQueryResultAdapter(returnTypeName: String) =
159         "Not sure how to convert the query result to this function's return type ($returnTypeName)."
160 
161     fun classMustImplementEqualsAndHashCode(keyType: String) =
162         "The key" +
163             " of the provided function's multimap return type must implement equals() and " +
164             "hashCode(). Key type is: $keyType."
165 
166     const val INSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT =
167         "Function annotated with" + " @Insert but does not have any parameters to insert."
168 
169     const val UPSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT =
170         "Function annotated with" + " @Upsert but does not have any parameters to insert or update."
171 
172     const val DELETE_MISSING_PARAMS =
173         "Function annotated with" + " @Delete but does not have any parameters to delete."
174 
175     fun cannotMapSpecifiedColumn(column: String, columnsInQuery: List<String>, annotation: String) =
176         "Column specified in the provided @$annotation annotation must be present in the query. " +
177             "Provided: $column. Columns found: ${columnsInQuery.joinToString(", ")}"
178 
179     const val MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED =
180         "To use the @MapInfo annotation, you " +
181             "must provide either the key column name, value column name, or both."
182 
183     fun mayNeedMapColumn(columnArg: String): String {
184         return """
185             Looks like you may need to use @MapColumn to clarify the 'columnName' needed for
186             type argument(s) in the return type of a function. Type argument that needs
187             @MapColumn: $columnArg
188             """
189             .trim()
190     }
191 
192     const val CANNOT_FIND_DELETE_RESULT_ADAPTER =
193         "Not sure how to handle delete function's " +
194             "return type. Currently the supported return types are void, int or Int."
195 
196     const val CANNOT_FIND_UPDATE_RESULT_ADAPTER =
197         "Not sure how to handle update function's " +
198             "return type. Currently the supported return types are void, int or Int."
199 
200     fun suspendReturnsDeferredType(returnTypeName: String) =
201         "Dao functions that have a suspend " +
202             "modifier must not return a deferred/async type ($returnTypeName). Most probably this " +
203             "is an error. Consider changing the return type or removing the suspend modifier."
204 
205     const val CANNOT_FIND_INSERT_RESULT_ADAPTER =
206         "Not sure how to handle insert function's return type."
207 
208     const val CANNOT_FIND_UPSERT_RESULT_ADAPTER =
209         "Not sure how to handle upsert function's return type."
210 
211     const val INSERT_MULTI_PARAM_SINGLE_RETURN_MISMATCH =
212         "Insert function accepts multiple parameters " +
213             "but the return type is a single element. Try using a multiple element return type."
214 
215     const val UPSERT_MULTI_PARAM_SINGLE_RETURN_MISMATCH =
216         "Upsert function accepts multiple parameters " +
217             "but the return type is a single element. Try using a multiple element return type."
218 
219     const val INSERT_SINGLE_PARAM_MULTI_RETURN_MISMATCH =
220         "Insert function accepts a single parameter " +
221             "but the return type is a collection of elements. Try using a single element return type."
222 
223     const val UPSERT_SINGLE_PARAM_MULTI_RETURN_MISMATCH =
224         "Upsert function accepts a single parameter " +
225             "but the return type is a collection of elements. Try using a single element return type."
226 
227     const val UPDATE_MISSING_PARAMS =
228         "Function annotated with" + " @Update but does not have any parameters to update."
229 
230     const val TRANSACTION_FUNCTION_MODIFIERS =
231         "Function annotated with @Transaction must not be " +
232             "private, final, or abstract. It can be abstract only if the function is also" +
233             " annotated with @Query."
234 
235     fun nullableParamInShortcutFunction(param: String) =
236         "Functions annotated with [@Insert, " +
237             "@Upsert, @Update, @Delete] shouldn't declare nullable parameters ($param)."
238 
239     fun transactionFunctionAsync(returnTypeName: String) =
240         "Function annotated with @Transaction must" +
241             " not return deferred/async return type $returnTypeName. Since transactions are" +
242             " thread confined and Room cannot guarantee that all queries in the function" +
243             " implementation are performed on the same thread, only synchronous @Transaction" +
244             " implemented functions are allowed. If a transaction is started and a change of thread" +
245             " is done and waited upon then a database deadlock can occur if the additional thread" +
246             " attempts to perform a query. This restrictions prevents such situation from" +
247             " occurring."
248 
249     const val TRANSACTION_MISSING_ON_RELATION =
250         "The return value includes a data class with a @Relation." +
251             " It is usually desired to annotate this function with @Transaction to avoid" +
252             " possibility of inconsistent results between the data class and its relations. See " +
253             TRANSACTION_REFERENCE_DOCS +
254             " for details."
255 
256     const val CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER =
257         "Type of the parameter must be a class " +
258             "annotated with @Entity or a collection/array of it."
259 
260     val DB_MUST_EXTEND_ROOM_DB =
261         "Classes annotated with @Database should extend " + ROOM_DB.canonicalName
262 
263     const val OBSERVABLE_QUERY_NOTHING_TO_OBSERVE =
264         "Observable query return type (LiveData, Flowable" +
265             ", DataSource, DataSourceFactory etc) can only be used with SELECT queries that" +
266             " directly or indirectly (via @Relation, for example) access at least one table. For" +
267             " @RawQuery, you should specify the list of tables to be observed via the" +
268             " observedEntities property."
269 
270     const val RECURSIVE_REFERENCE_DETECTED =
271         "Recursive referencing through @Embedded and/or @Relation " + "detected: %s"
272 
273     private const val TOO_MANY_MATCHING_GETTERS =
274         "Ambiguous getter for %s. All of the following " +
275             "match: %s. You can @Ignore the ones that you don't want to match."
276 
277     fun tooManyMatchingGetters(property: Property, functionNames: List<String>): String {
278         return TOO_MANY_MATCHING_GETTERS.format(property, functionNames.joinToString(", "))
279     }
280 
281     private const val TOO_MANY_MATCHING_SETTERS =
282         "Ambiguous setter for %s. All of the following " +
283             "match: %s. You can @Ignore the ones that you don't want to match."
284 
285     fun tooManyMatchingSetter(property: Property, functionNames: List<String>): String {
286         return TOO_MANY_MATCHING_SETTERS.format(property, functionNames.joinToString(", "))
287     }
288 
289     const val CANNOT_FIND_COLUMN_TYPE_ADAPTER =
290         "Cannot figure out how to save this property into" +
291             " database. You can consider adding a type converter for it."
292 
293     const val VALUE_CLASS_ONLY_SUPPORTED_IN_KSP =
294         "Kotlin value classes are only supported " +
295             "in Room using KSP and generating Kotlin (room.generateKotlin=true)."
296 
297     const val CANNOT_FIND_STMT_BINDER =
298         "Cannot figure out how to bind this property into a statement."
299 
300     const val CANNOT_FIND_STMT_READER =
301         "Cannot figure out how to read this property from a statement."
302 
303     const val DEFAULT_VALUE_NULLABILITY = "Use of NULL as the default value of a non-null property"
304 
305     private const val MISSING_PARAMETER_FOR_BIND =
306         "Each bind variable in the query must have a" +
307             " matching function parameter. Cannot find function parameters for %s."
308 
309     fun missingParameterForBindVariable(bindVarName: List<String>): String {
310         return MISSING_PARAMETER_FOR_BIND.format(bindVarName.joinToString(", "))
311     }
312 
313     fun valueCollectionMustBeListOrSetOrMap(mapValueTypeName: String): String {
314         return "Multimap 'value' collection type must be a List, Set or Map. " +
315             "Found $mapValueTypeName."
316     }
317 
318     private const val UNUSED_QUERY_FUNCTION_PARAMETER = "Unused parameter%s: %s"
319 
320     fun unusedQueryFunctionParameter(unusedParams: List<String>): String {
321         return UNUSED_QUERY_FUNCTION_PARAMETER.format(
322             if (unusedParams.size > 1) "s" else "",
323             unusedParams.joinToString(",")
324         )
325     }
326 
327     private const val DUPLICATE_TABLES_OR_VIEWS =
328         "The name \"%s\" is used by multiple entities or views: %s"
329 
330     fun duplicateTableNames(tableName: String, entityNames: List<String>): String {
331         return DUPLICATE_TABLES_OR_VIEWS.format(tableName, entityNames.joinToString(", "))
332     }
333 
334     const val DAO_FUNCTION_CONFLICTS_WITH_OTHERS = "Dao function has conflicts."
335 
336     fun duplicateDao(dao: String, functionNames: List<String>): String {
337         return """
338                 All of these functions [${functionNames.joinToString(", ")}] return the same DAO
339                 class [$dao].
340                 A database can use a DAO only once so you should remove ${functionNames.size - 1} of
341                 these conflicting DAO functions. If you are implementing any of these to fulfill an
342                 interface, don't make it abstract, instead, implement the code that calls the
343                 other one.
344                 """
345             .trim()
346     }
347 
348     fun dataClassMissingNonNull(
349         dataClassTypeName: String,
350         missingDataClassProperties: List<String>,
351         allQueryColumns: List<String>
352     ): String {
353         return """
354         The columns returned by the query does not have the properties
355         [${missingDataClassProperties.joinToString(",")}] in $dataClassTypeName even though they are
356         annotated as non-null or primitive.
357         Columns returned by the query: [${allQueryColumns.joinToString(",")}]
358         """
359             .trim()
360     }
361 
362     fun queryPropertyDataClassMismatch(
363         dataClassTypeNames: List<String>,
364         unusedColumns: List<String>,
365         allColumns: List<String>,
366         dataClassUnusedProperties: Map<String, List<Property>>,
367     ): String {
368         val unusedColumnsWarning =
369             if (unusedColumns.isNotEmpty()) {
370                 val dataClassNames =
371                     if (dataClassTypeNames.size > 1) {
372                         "any of [${dataClassTypeNames.joinToString(", ")}]"
373                     } else {
374                         dataClassTypeNames.single().toString()
375                     }
376                 """
377                 The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
378                 used by $dataClassNames. You can use @ColumnInfo annotation on the properties to specify
379                 the mapping.
380                 You can annotate the function with @RewriteQueriesToDropUnusedColumns to direct Room
381                 to rewrite your query to avoid fetching unused columns.
382             """
383                     .trim()
384             } else {
385                 ""
386             }
387         val unusedPropertiesWarning =
388             dataClassUnusedProperties.map { (dataClassName, unusedProperties) ->
389                 """
390                 $dataClassName has some properties
391                 [${unusedProperties.joinToString(", ") { it.columnName }}] which are not returned by
392                 the query. If they are not supposed to be read from the result, you can mark them
393                 with @Ignore annotation.
394             """
395                     .trim()
396             }
397         return """
398             $unusedColumnsWarning
399             ${unusedPropertiesWarning.joinToString(separator = " ")}
400             You can suppress this warning by annotating the function with
401             @SuppressWarnings(RoomWarnings.QUERY_MISMATCH).
402             Columns returned by the query: ${allColumns.joinToString(", ")}.
403             """
404             .trim()
405     }
406 
407     const val TYPE_CONVERTER_UNBOUND_GENERIC = "Cannot use unbound generics in Type Converters."
408     const val TYPE_CONVERTER_BAD_RETURN_TYPE = "Invalid return type for a type converter."
409     const val TYPE_CONVERTER_MUST_RECEIVE_1_PARAM = "Type converters must receive 1 parameter."
410     const val TYPE_CONVERTER_EMPTY_CLASS =
411         "Class is referenced as a converter but it does not have any" + " converter functions."
412     const val TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR =
413         "Classes that are used as TypeConverters must" +
414             " have no-argument public constructors. Use a ProvidedTypeConverter annotation if you" +
415             " need to take control over creating an instance of a TypeConverter."
416     const val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
417     const val INNER_CLASS_TYPE_CONVERTER_MUST_BE_STATIC =
418         "An inner class TypeConverter must be " + "static."
419 
420     fun duplicateTypeConverters(converters: List<CustomTypeConverter>): String {
421         return "Multiple functions define the same conversion. Conflicts with these:" +
422             " ${converters.joinToString(", ") { it.toString() }}"
423     }
424 
425     fun typeConverterMustBeDeclared(typeName: String): String {
426         return "Invalid type converter type: $typeName. Type converters must be a class."
427     }
428 
429     // TODO must print property paths.
430     const val DATA_CLASS_PROPERTY_HAS_DUPLICATE_COLUMN_NAME = "Property has non-unique column name."
431 
432     fun dataClassDuplicatePropertyNames(columnName: String, propertyPaths: List<String>): String {
433         return "Multiple properties have the same columnName: $columnName." +
434             " Property names: ${propertyPaths.joinToString(", ")}."
435     }
436 
437     fun embeddedPrimaryKeyIsDropped(entityQName: String, propertyName: String): String {
438         return "Primary key constraint on $propertyName is ignored when being merged into " +
439             entityQName
440     }
441 
442     const val INDEX_COLUMNS_CANNOT_BE_EMPTY = "List of columns in an index cannot be empty"
443 
444     fun indexColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
445         return "$columnName referenced in the index does not exist in the Entity." +
446             " Available column names:${allColumns.joinToString(", ")}"
447     }
448 
449     fun duplicateIndexInEntity(indexName: String): String {
450         return "There are multiple indices with name $indexName. This happen if you've declared" +
451             " the same index multiple times or different indices have the same name. See" +
452             " @Index documentation for details."
453     }
454 
455     fun duplicateIndexInDatabase(indexName: String, indexPaths: List<String>): String {
456         return "There are multiple indices with name $indexName. You should rename " +
457             "${indexPaths.size - 1} of these to avoid the conflict:" +
458             "${indexPaths.joinToString(", ")}."
459     }
460 
461     fun droppedEmbeddedPropertyIndex(propertyPath: String, grandParent: String): String {
462         return "The index will be dropped when being merged into $grandParent" +
463             "($propertyPath). You must re-declare it in $grandParent if you want to index this" +
464             " property in $grandParent."
465     }
466 
467     fun droppedEmbeddedIndex(
468         entityName: String,
469         propertyPath: String,
470         grandParent: String
471     ): String {
472         return "Indices defined in $entityName will be dropped when it is merged into" +
473             " $grandParent ($propertyPath). You can re-declare them in $grandParent."
474     }
475 
476     fun droppedSuperClassIndex(childEntity: String, superEntity: String): String {
477         return "Indices defined in $superEntity will NOT be re-used in $childEntity. If you want" +
478             " to inherit them, you must re-declare them in $childEntity." +
479             " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
480     }
481 
482     fun droppedSuperClassPropertyIndex(
483         propertyName: String,
484         childEntity: String,
485         superEntity: String
486     ): String {
487         return "Index defined on property `$propertyName` in $superEntity will NOT be re-used in" +
488             " $childEntity. " +
489             "If you want to inherit it, you must re-declare it in $childEntity." +
490             " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
491     }
492 
493     const val NOT_ENTITY_OR_VIEW = "The class must be either @Entity or @DatabaseView."
494 
495     fun relationCannotFindEntityProperty(
496         entityName: String,
497         columnName: String,
498         availableColumns: List<String>
499     ): String {
500         return "Cannot find the child entity column `$columnName` in $entityName." +
501             " Options: ${availableColumns.joinToString(", ")}"
502     }
503 
504     fun relationCannotFindParentEntityProperty(
505         entityName: String,
506         columnName: String,
507         availableColumns: List<String>
508     ): String {
509         return "Cannot find the parent entity column `$columnName` in $entityName." +
510             " Options: ${availableColumns.joinToString(", ")}"
511     }
512 
513     fun relationCannotFindJunctionEntityProperty(
514         entityName: String,
515         columnName: String,
516         availableColumns: List<String>
517     ): String {
518         return "Cannot find the child entity referencing column `$columnName` in the junction " +
519             "$entityName. Options: ${availableColumns.joinToString(", ")}"
520     }
521 
522     fun relationCannotFindJunctionParentProperty(
523         entityName: String,
524         columnName: String,
525         availableColumns: List<String>
526     ): String {
527         return "Cannot find the parent entity referencing column `$columnName` in the junction " +
528             "$entityName. Options: ${availableColumns.joinToString(", ")}"
529     }
530 
531     fun junctionColumnWithoutIndex(entityName: String, columnName: String) =
532         "The column $columnName in the junction entity $entityName is being used to resolve " +
533             "a relationship but it is not covered by any index. This might cause a " +
534             "full table scan when resolving the relationship, it is highly advised to " +
535             "create an index that covers this column."
536 
537     const val RELATION_IN_ENTITY = "Entities cannot have relations."
538 
539     fun relationAffinityMismatch(
540         parentColumn: String,
541         childColumn: String,
542         parentAffinity: SQLTypeAffinity?,
543         childAffinity: SQLTypeAffinity?
544     ): String {
545         return """
546         The affinity of parent column ($parentColumn : $parentAffinity) does not match the type
547         affinity of the child column ($childColumn : $childAffinity).
548         """
549             .trim()
550     }
551 
552     fun relationJunctionParentAffinityMismatch(
553         parentColumn: String,
554         junctionParentColumn: String,
555         parentAffinity: SQLTypeAffinity?,
556         junctionParentAffinity: SQLTypeAffinity?
557     ): String {
558         return """
559         The affinity of parent column ($parentColumn : $parentAffinity) does not match the type
560         affinity of the junction parent column ($junctionParentColumn : $junctionParentAffinity).
561         """
562             .trim()
563     }
564 
565     fun relationJunctionChildAffinityMismatch(
566         childColumn: String,
567         junctionChildColumn: String,
568         childAffinity: SQLTypeAffinity?,
569         junctionChildAffinity: SQLTypeAffinity?
570     ): String {
571         return """
572         The affinity of child column ($childColumn : $childAffinity) does not match the type
573         affinity of the junction child column ($junctionChildColumn : $junctionChildAffinity).
574         """
575             .trim()
576     }
577 
578     val CANNOT_USE_MORE_THAN_ONE_DATA_CLASS_PROPERTY_ANNOTATION =
579         "A property can be annotated with only" +
580             " one of the following:" +
581             DataClassProcessor.PROCESSED_ANNOTATIONS.joinToString(",") { it.java.simpleName }
582 
583     fun missingIgnoredColumns(missingIgnoredColumns: List<String>): String {
584         return "Non-existent columns are specified to be ignored in ignoreColumns: " +
585             missingIgnoredColumns.joinToString(",")
586     }
587 
588     fun relationBadProject(
589         entityQName: String,
590         missingColumnNames: List<String>,
591         availableColumnNames: List<String>
592     ): String {
593         return """
594         $entityQName does not have the following columns: ${missingColumnNames.joinToString(",")}.
595         Available columns are: ${availableColumnNames.joinToString(",")}
596         """
597             .trim()
598     }
599 
600     const val MISSING_SCHEMA_EXPORT_DIRECTORY =
601         "Schema export directory was not provided to the" +
602             " annotation processor so Room cannot export the schema. You can either provide" +
603             " `room.schemaLocation` annotation processor argument by applying the Room Gradle plugin" +
604             " (id 'androidx.room') OR set exportSchema to false."
605 
606     const val INVALID_FOREIGN_KEY_ACTION =
607         "Invalid foreign key action. It must be one of the constants" +
608             " defined in ForeignKey.Action"
609 
610     fun foreignKeyNotAnEntity(className: String): String {
611         return """
612         Classes referenced in Foreign Key annotations must be @Entity classes. $className is not
613         an entity
614         """
615             .trim()
616     }
617 
618     const val FOREIGN_KEY_CANNOT_FIND_PARENT = "Cannot find parent entity class."
619 
620     fun foreignKeyChildColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
621         return "($columnName) referenced in the foreign key does not exist in the Entity." +
622             " Available column names:${allColumns.joinToString(", ")}"
623     }
624 
625     fun foreignKeyParentColumnDoesNotExist(
626         parentEntity: String,
627         missingColumn: String,
628         allColumns: List<String>
629     ): String {
630         return "($missingColumn) does not exist in $parentEntity. Available columns are" +
631             " ${allColumns.joinToString(",")}"
632     }
633 
634     const val FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST =
635         "Must specify at least 1 column name for the child"
636 
637     const val FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST =
638         "Must specify at least 1 column name for the parent"
639 
640     fun foreignKeyColumnNumberMismatch(
641         childColumns: List<String>,
642         parentColumns: List<String>
643     ): String {
644         return """
645                 Number of child columns in foreign key must match number of parent columns.
646                 Child reference has ${childColumns.joinToString(",")} and parent reference has
647                 ${parentColumns.joinToString(",")}
648                """
649             .trim()
650     }
651 
652     fun foreignKeyMissingParentEntityInDatabase(parentTable: String, childEntity: String): String {
653         return """
654                 $parentTable table referenced in the foreign keys of $childEntity does not exist in
655                 the database. Maybe you forgot to add the referenced entity in the entities list of
656                 the @Database annotation?"""
657             .trim()
658     }
659 
660     fun foreignKeyMissingIndexInParent(
661         parentEntity: String,
662         parentColumns: List<String>,
663         childEntity: String,
664         childColumns: List<String>
665     ): String {
666         return """
667                 $childEntity has a foreign key (${childColumns.joinToString(",")}) that references
668                 $parentEntity (${parentColumns.joinToString(",")}) but $parentEntity does not have
669                 a unique index on those columns nor the columns are its primary key.
670                 SQLite requires having a unique constraint on referenced parent columns so you must
671                 add a unique index to $parentEntity that has
672                 (${parentColumns.joinToString(",")}) column(s).
673                """
674             .trim()
675     }
676 
677     fun foreignKeyMissingIndexInChildColumns(childColumns: List<String>): String {
678         return """
679                 (${childColumns.joinToString(",")}) column(s) reference a foreign key but
680                 they are not part of an index. This may trigger full table scans whenever parent
681                 table is modified so you are highly advised to create an index that covers these
682                 columns.
683                """
684             .trim()
685     }
686 
687     fun foreignKeyMissingIndexInChildColumn(childColumn: String): String {
688         return """
689                 $childColumn column references a foreign key but it is not part of an index. This
690                 may trigger full table scans whenever parent table is modified so you are highly
691                 advised to create an index that covers this column.
692                """
693             .trim()
694     }
695 
696     fun shortcutEntityIsNotInDatabase(database: String, dao: String, entity: String): String {
697         return """
698                 $dao is part of $database but this entity is not in the database. Maybe you forgot
699                 to add $entity to the entities section of the @Database?
700                 """
701             .trim()
702     }
703 
704     const val MISSING_ROOM_GUAVA_ARTIFACT =
705         "To use Guava features, you must add `guava`" +
706             " artifact from Room as a dependency. androidx.room:room-guava:<version>"
707 
708     const val MISSING_ROOM_RXJAVA2_ARTIFACT =
709         "To use RxJava2 features, you must add `rxjava2`" +
710             " artifact from Room as a dependency. androidx.room:room-rxjava2:<version>"
711 
712     const val MISSING_ROOM_RXJAVA3_ARTIFACT =
713         "To use RxJava3 features, you must add `rxjava3`" +
714             " artifact from Room as a dependency. androidx.room:room-rxjava3:<version>"
715 
716     const val MISSING_ROOM_PAGING_ARTIFACT =
717         "To use PagingSource, you must add `room-paging`" +
718             " artifact from Room as a dependency. androidx.room:room-paging:<version>"
719 
720     const val MISSING_ROOM_PAGING_GUAVA_ARTIFACT =
721         "To use ListenableFuturePagingSource, you must " +
722             "add `room-paging-guava` artifact from Room as a dependency. " +
723             "androidx.room:room-paging-guava:<version>"
724 
725     const val MISSING_ROOM_PAGING_RXJAVA2_ARTIFACT =
726         "To use RxPagingSource, you must " +
727             "add `room-paging-rxjava2` artifact from Room as a dependency. " +
728             "androidx.room:room-paging-rxjava2:<version>"
729 
730     const val MISSING_ROOM_PAGING_RXJAVA3_ARTIFACT =
731         "To use RxPagingSource, you must " +
732             "add `room-paging-rxjava3` artifact from Room as a dependency. " +
733             "androidx.room:room-paging-rxjava3:<version>"
734 
735     fun ambiguousConstructor(
736         dataClass: String,
737         paramName: String,
738         matchingProperties: List<String>
739     ): String {
740         return """
741             Ambiguous constructor. The parameter ($paramName) in $dataClass matches multiple properties:
742             [${matchingProperties.joinToString(",")}]. If you don't want to use this constructor,
743             you can annotate it with @Ignore. If you want Room to use this constructor, you can
744             rename the parameters to exactly match the property name to fix the ambiguity.
745             """
746             .trim()
747     }
748 
749     val MISSING_DATA_CLASS_CONSTRUCTOR =
750         """
751             Entities and data classes must have a usable public constructor. You can have an empty
752             constructor or a constructor whose parameters match the properties (by name and type).
753             """
754             .trim()
755 
756     val TOO_MANY_DATA_CLASS_CONSTRUCTORS =
757         """
758             Room cannot pick a constructor since multiple constructors are suitable. Try to annotate
759             unwanted constructors with @Ignore.
760             """
761             .trim()
762 
763     val TOO_MANY_DATA_CLASS_CONSTRUCTORS_CHOOSING_NO_ARG =
764         """
765             There are multiple good constructors and Room will pick the no-arg constructor.
766             You can use the @Ignore annotation to eliminate unwanted constructors.
767             """
768             .trim()
769 
770     const val PAGING_SPECIFY_DATA_SOURCE_TYPE =
771         "For now, Room only supports PositionalDataSource class."
772 
773     const val PAGING_SPECIFY_PAGING_SOURCE_TYPE =
774         "For now, Room only supports PagingSource with Key of" + " type Int."
775 
776     const val PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE =
777         "For now, Room only supports PagingSource with" + " Value that is not of Collection type."
778 
779     fun primaryKeyNull(property: String): String {
780         return "You must annotate primary keys with @NonNull. \"$property\" is nullable. SQLite " +
781             "considers this a " +
782             "bug and Room does not allow it. See SQLite docs for details: " +
783             "https://www.sqlite.org/lang_createtable.html"
784     }
785 
786     const val INVALID_COLUMN_NAME =
787         "Invalid column name. Room does not allow using ` or \" in column" + " names"
788 
789     const val INVALID_TABLE_NAME =
790         "Invalid table name. Room does not allow using ` or \" in table names"
791 
792     const val RAW_QUERY_BAD_PARAMS =
793         "RawQuery functions should have 1 and only 1 parameter with type" + " SupportSQLiteQuery"
794 
795     fun parameterCannotBeNullable(parameterName: String) =
796         """
797         Parameter `$parameterName` cannot be nullable.
798     """
799             .trimIndent()
800 
801     const val RAW_QUERY_BAD_RETURN_TYPE = "RawQuery functions must return a non-void type."
802 
803     fun rawQueryBadEntity(typeName: String): String {
804         return """
805             observedEntities property in RawQuery must either reference a class that is annotated
806             with @Entity or it should reference a data class that either contains
807             @Embedded properties that are annotated with @Entity or @Relation properties.
808             $typeName does not have these properties, did you mean another class?
809             """
810             .trim()
811     }
812 
813     val RAW_QUERY_STRING_PARAMETER_REMOVED =
814         "@RawQuery does not allow passing a string anymore." +
815             " Please use ${RoomTypeNames.RAW_QUERY.canonicalName}."
816 
817     const val MISSING_COPY_ANNOTATIONS =
818         "Annotated property getter is missing " + "@AutoValue.CopyAnnotations."
819 
820     fun invalidAnnotationTarget(annotationName: String, elementKindName: String): String {
821         return "@$annotationName is not allowed in this $elementKindName."
822     }
823 
824     const val INDICES_IN_FTS_ENTITY = "Indices not allowed in FTS Entity."
825 
826     const val FOREIGN_KEYS_IN_FTS_ENTITY = "Foreign Keys not allowed in FTS Entity."
827 
828     const val MISSING_PRIMARY_KEYS_ANNOTATION_IN_ROW_ID =
829         "The property with column name 'rowid' in " +
830             "an FTS entity must be annotated with @PrimaryKey."
831 
832     const val TOO_MANY_PRIMARY_KEYS_IN_FTS_ENTITY =
833         "An FTS entity can only have a single primary key."
834 
835     const val INVALID_FTS_ENTITY_PRIMARY_KEY_NAME =
836         "The single primary key property in an FTS entity " +
837             "must either be named 'rowid' or must be annotated with @ColumnInfo(name = \"rowid\")"
838 
839     const val INVALID_FTS_ENTITY_PRIMARY_KEY_AFFINITY =
840         "The single @PrimaryKey annotated property in an " +
841             "FTS entity must be of INTEGER affinity."
842 
843     fun missingLanguageIdProperty(columnName: String) =
844         "The specified 'languageid' column: \"$columnName\", was not found."
845 
846     const val INVALID_FTS_ENTITY_LANGUAGE_ID_AFFINITY =
847         "The 'languageid' property must be of INTEGER " + "affinity."
848 
849     fun missingNotIndexedProperty(missingNotIndexedColumns: List<String>) =
850         "Non-existent columns are specified to be not indexed in notIndexed: " +
851             missingNotIndexedColumns.joinToString(",")
852 
853     const val INVALID_FTS_ENTITY_PREFIX_SIZES =
854         "Prefix sizes to index must non-zero positive values."
855 
856     const val FTS_EXTERNAL_CONTENT_CANNOT_FIND_ENTITY = "Cannot find external content entity class."
857 
858     fun externalContentNotAnEntity(className: String) =
859         "External content entity referenced in " +
860             "a Fts4 annotation must be a @Entity class. $className is not an entity"
861 
862     fun missingFtsContentProperty(
863         ftsClassName: String,
864         columnName: String,
865         contentClassName: String
866     ) =
867         "External Content FTS Entity '$ftsClassName' has declared property with column name " +
868             "'$columnName' that was not found in the external content entity " +
869             "'$contentClassName'."
870 
871     fun missingExternalContentEntity(ftsClassName: String, contentClassName: String) =
872         "External Content FTS Entity '$ftsClassName' has a declared content entity " +
873             "'$contentClassName' that is not present in the same @Database. Maybe you " +
874             "forgot to add it to the entities section of the @Database?"
875 
876     fun cannotFindAsEntityProperty(entityName: String) =
877         "Cannot find a column in the entity " +
878             "$entityName that matches with this partial entity property. If you don't wish to use " +
879             "the property then you can annotate it with @Ignore."
880 
881     const val INVALID_TARGET_ENTITY_IN_SHORTCUT_FUNCTION =
882         "Target entity declared in @Insert, @Update " + "or @Delete must be annotated with @Entity."
883 
884     const val INVALID_RELATION_IN_PARTIAL_ENTITY = "Partial entities cannot have relations."
885 
886     fun invalidQueryForSingleColumnArray(returnType: String) =
887         "If a DAO function has a " +
888             "primitive array or an array of String return type, a single column must be returned. " +
889             "Please check the query of the DAO function with the `$returnType` return type."
890 
891     val EXPAND_PROJECTION_ALONG_WITH_REMOVE_UNUSED =
892         """
893         Using @${RewriteQueriesToDropUnusedColumns::class.simpleName} annotation when
894         room.expandProjection compiler flag is enabled will disable expandProjection for queries
895         covered with @${RewriteQueriesToDropUnusedColumns::class.simpleName}.
896     """
897             .trim()
898 
899     fun missingPrimaryKeysInPartialEntityForInsert(
900         partialEntityName: String,
901         primaryKeyNames: List<String>
902     ) =
903         "The partial entity $partialEntityName is missing the primary key properties " +
904             "(${primaryKeyNames.joinToString()}) needed to perform an INSERT. If your single " +
905             "primary key is auto generated then the properties are optional."
906 
907     fun missingPrimaryKeysInPartialEntityForUpsert(
908         partialEntityName: String,
909         primaryKeyNames: List<String>
910     ) =
911         "The partial entity $partialEntityName is missing the primary key properties " +
912             "(${primaryKeyNames.joinToString()}) needed to perform an UPSERT. If your single " +
913             "primary key is auto generated then the properties are optional."
914 
915     fun missingRequiredColumnsInPartialEntity(
916         partialEntityName: String,
917         missingColumnNames: List<String>
918     ) =
919         "The partial entity $partialEntityName is missing required columns " +
920             "(${missingColumnNames.joinToString()}) needed to perform an INSERT. These are " +
921             "NOT NULL columns without default values."
922 
923     fun missingPrimaryKeysInPartialEntityForUpdate(
924         partialEntityName: String,
925         primaryKeyNames: List<String>
926     ) =
927         "The partial entity $partialEntityName is missing the primary key properties " +
928             "(${primaryKeyNames.joinToString()}) needed to perform an UPDATE."
929 
930     fun noColumnsInPartialEntity(partialEntityName: String) =
931         "The partial entity $partialEntityName does not have any columns that can be used to " +
932             "perform the query."
933 
934     fun cannotFindPreparedQueryResultAdapter(returnType: String, type: QueryType) =
935         StringBuilder()
936             .apply {
937                 append("Not sure how to handle query function's return type ($returnType). ")
938                 if (type == QueryType.INSERT) {
939                     append(
940                         "INSERT query functions must either return void " +
941                             "or long (the rowid of the inserted row)."
942                     )
943                 } else if (type == QueryType.UPDATE) {
944                     append(
945                         "UPDATE query functions must either return void " +
946                             "or int (the number of updated rows)."
947                     )
948                 } else if (type == QueryType.DELETE) {
949                     append(
950                         "DELETE query functions must either return void " +
951                             "or int (the number of deleted rows)."
952                     )
953                 }
954             }
955             .toString()
956 
957     val JDK_VERSION_HAS_BUG =
958         "Current JDK version ${System.getProperty("java.runtime.version") ?: ""} has a bug" +
959             " (https://bugs.openjdk.java.net/browse/JDK-8007720)" +
960             " that prevents Room from being incremental." +
961             " Consider using JDK 11+ or the embedded JDK shipped with Android Studio 3.5+."
962 
963     fun invalidChannelType(typeName: String) =
964         "'$typeName' is not supported as a return type. " +
965             "Instead declare return type as ${KotlinTypeNames.FLOW} and use Flow transforming " +
966             "functions that converts the Flow into a Channel."
967 
968     fun mismatchedGetter(
969         propertyName: String,
970         ownerType: String,
971         getterType: String,
972         propertyType: String
973     ) =
974         """
975             $ownerType's $propertyName property has type $propertyType but its getter returns $getterType.
976             This mismatch might cause unexpected $propertyName values in the database when $ownerType
977             is inserted into database.
978         """
979             .trim()
980 
981     fun mismatchedSetter(
982         propertyName: String,
983         ownerType: String,
984         setterType: String,
985         propertyType: String
986     ) =
987         """
988             $ownerType's $propertyName property has type $propertyType but its setter accepts $setterType.
989             This mismatch might cause unexpected $propertyName values when $ownerType is read from the
990             database.
991         """
992             .trim()
993 
994     const val DATABASE_INVALID_DAO_FUNCTION_RETURN_TYPE =
995         "Abstract database functions must return a @Dao " + "annotated class or interface."
996 
997     fun invalidEntityTypeInDatabaseAnnotation(typeName: String): String {
998         return "Invalid Entity type: $typeName. An entity in the database must be a class."
999     }
1000 
1001     fun invalidViewTypeInDatabaseAnnotation(typeName: String): String {
1002         return "Invalid View type: $typeName. Views in a database must be a class or an " +
1003             "interface."
1004     }
1005 
1006     fun invalidAutoMigrationTypeInDatabaseAnnotation(): String {
1007         return "Invalid AutoMigration type: An auto migration in the database must be " +
1008             "an @AutoMigration annotation."
1009     }
1010 
1011     const val EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE =
1012         "The type of an Embedded property must be a " + "class or an interface."
1013     const val RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE =
1014         "Entity type in a Relation must be a class " + "or an interface."
1015 
1016     fun shortcutFunctionArgumentMustBeAClass(typeName: String): String {
1017         return "Invalid query argument: $typeName. It must be a class or an interface."
1018     }
1019 
1020     const val AUTOMIGRATION_SPEC_MUST_BE_CLASS = "The AutoMigration spec " + "type must be a class."
1021 
1022     fun autoMigrationElementMustImplementSpec(spec: String): String {
1023         return "The AutoMigration spec $spec must implement the AutoMigrationSpec interface."
1024     }
1025 
1026     // TODO: (b/180389433) If the files don't exist the getSchemaFile() function should return
1027     //  null and before calling process
1028     fun autoMigrationToVersionMustBeGreaterThanFrom(to: Int, from: Int) =
1029         if (from > to) {
1030             "Downgrades are not supported in AutoMigration."
1031         } else {
1032             "The versions provided (to: $to, from: $from) are invalid. The To version must" +
1033                 " be greater than the From version."
1034         }
1035 
1036     fun autoMigrationSchemasNotFound(schemaVersion: Int, schemaOutFolderPath: String): String {
1037         return "Schema '$schemaVersion.json' required for migration was not found at the schema " +
1038             "out folder: $schemaOutFolderPath. Cannot generate auto migrations."
1039     }
1040 
1041     fun invalidAutoMigrationSchema(schemaVersion: Int, schemaOutFolderPath: String): String {
1042         return "Found invalid schema file '$schemaVersion.json' at the schema out " +
1043             "folder: $schemaOutFolderPath.\nIf you've modified the file, you might've broken the " +
1044             "JSON format, try deleting the file and re-running the compiler.\n" +
1045             "If you've not modified the file, please file a bug at " +
1046             "https://issuetracker.google.com/issues/new?component=413107&template=1096568 " +
1047             "with a sample app to reproduce the issue."
1048     }
1049 
1050     fun newNotNullColumnMustHaveDefaultValue(columnName: String): String {
1051         return "New NOT NULL " +
1052             "column'$columnName' " +
1053             "added with no default value specified. Please specify the default value using " +
1054             "@ColumnInfo."
1055     }
1056 
1057     fun columnWithChangedSchemaFound(columnName: String): String {
1058         return "Encountered column '$columnName' with an unsupported schema change at the column " +
1059             "level (e.g. affinity change). These changes are not yet " +
1060             "supported by AutoMigration."
1061     }
1062 
1063     fun deletedOrRenamedColumnFound(
1064         className: String?,
1065         columnName: String,
1066         tableName: String
1067     ): String {
1068         return if (className != null) {
1069             """
1070             AutoMigration Failure in ‘$className’: Column ‘$columnName’ in table ‘$tableName’ has
1071             been either removed or renamed. Please annotate ‘$className’ with the @RenameColumn
1072             or @DeleteColumn annotation to specify the change to be performed:
1073             1) RENAME:
1074                 @RenameColumn.Entries(
1075                     @RenameColumn(
1076                         tableName = "$tableName",
1077                         fromColumnName = "$columnName",
1078                         toColumnName = <NEW_COLUMN_NAME>
1079                     )
1080                 )
1081             2) DELETE:
1082                 @DeleteColumn.Entries(
1083                     @DeleteColumn(
1084                         tableName = "$tableName",
1085                         columnName = "$columnName"
1086                     )
1087                 )
1088             """
1089         } else {
1090             """
1091             AutoMigration Failure: Please declare an interface extending 'AutoMigrationSpec',
1092             and annotate with the @RenameColumn or @DeleteColumn annotation to specify the
1093             change to be performed:
1094             1) RENAME:
1095                 @RenameColumn.Entries(
1096                     @RenameColumn(
1097                         tableName = "$tableName",
1098                         fromColumnName = "$columnName",
1099                         toColumnName = <NEW_COLUMN_NAME>
1100                     )
1101                 )
1102             2) DELETE:
1103                 @DeleteColumn.Entries(
1104                     @DeleteColumn(
1105                         tableName = "$tableName",
1106                         columnName = "$columnName"
1107                     )
1108                 )
1109             """
1110         }
1111     }
1112 
1113     fun deletedOrRenamedTableFound(className: String?, tableName: String): String {
1114         return if (className != null) {
1115             """
1116             AutoMigration Failure in '$className': Table '$tableName' has been either removed or
1117             renamed. Please annotate '$className' with the @RenameTable or @DeleteTable
1118             annotation to specify the change to be performed:
1119             1) RENAME:
1120                 @RenameTable.Entries(
1121                     @RenameTable(
1122                         fromTableName = "$tableName",
1123                         toTableName = <NEW_TABLE_NAME>
1124                     )
1125                 )
1126             2) DELETE:
1127                 @DeleteTable.Entries(
1128                     @DeleteTable(
1129                         tableName = "$tableName"
1130                     )
1131                 )
1132             """
1133         } else {
1134             """
1135             AutoMigration Failure: Please declare an interface extending 'AutoMigrationSpec',
1136             and annotate with the @RenameTable or @DeleteTable annotation to specify the change
1137             to be performed:
1138             1) RENAME:
1139                 @RenameTable.Entries(
1140                     @RenameTable(
1141                         fromTableName = "$tableName",
1142                         toTableName = <NEW_TABLE_NAME>
1143                     )
1144                 )
1145             2) DELETE:
1146                 @DeleteTable.Entries(
1147                     @DeleteTable(
1148                         tableName = "$tableName"
1149                     )
1150                 )
1151             """
1152         }
1153     }
1154 
1155     fun tableRenameError(
1156         className: String,
1157         originalTableName: String,
1158         newTableName: String
1159     ): String {
1160         return "AutoMigration Failure in '$className': The table renamed from " +
1161             "'$originalTableName' to '$newTableName' is " +
1162             "not found in the new version of the database."
1163     }
1164 
1165     fun conflictingRenameTableAnnotationsFound(annotations: String): String {
1166         return "Conflicting @RenameTable annotations found: [$annotations]"
1167     }
1168 
1169     fun conflictingRenameColumnAnnotationsFound(annotations: String): String {
1170         return "Conflicting @RenameColumn annotations found: [$annotations]"
1171     }
1172 
1173     const val AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF =
1174         "Cannot create auto migrations when " + "exportSchema is false."
1175 
1176     const val AUTO_MIGRATION_SCHEMA_IN_FOLDER_NULL =
1177         "Schema import directory was not provided to the" +
1178             " annotation processor so Room cannot read older schemas. To generate auto migrations," +
1179             " you must provide `room.schemaLocation` annotation processor arguments by applying the" +
1180             " Room Gradle plugin (id 'androidx.room') AND set exportSchema to true."
1181 
1182     fun tableWithConflictingPrefixFound(tableName: String): String {
1183         return "The new version of the schema contains '$tableName' a table name" +
1184             " with the prefix '_new_', which will cause conflicts for auto migrations. Please use" +
1185             " a different name."
1186     }
1187 
1188     const val INNER_CLASS_AUTOMIGRATION_SPEC_MUST_BE_STATIC =
1189         "An inner class AutoMigrationSpec must be" + " static."
1190 
1191     const val AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR =
1192         "Classes that are used as " +
1193             "AutoMigrationSpec " +
1194             "implementations must have no-argument public constructors."
1195 
1196     const val JVM_NAME_ON_OVERRIDDEN_FUNCTION =
1197         "Using @JvmName annotation on a function or accessor " +
1198             "that will be overridden by Room is not supported. If this is important for your use " +
1199             "case, please file a bug at $ISSUE_TRACKER_LINK with details."
1200 
1201     fun ambiguousColumn(
1202         columnName: String,
1203         location: AmbiguousColumnLocation,
1204         typeName: String?
1205     ): String {
1206         val (locationDesc, recommendation) =
1207             when (location) {
1208                 AmbiguousColumnLocation.MAP_INFO -> {
1209                     "in the @MapInfo" to "update @MapInfo"
1210                 }
1211                 AmbiguousColumnLocation.DATA_CLASS -> {
1212                     checkNotNull(typeName)
1213                     "in the object '$typeName'" to "use @ColumnInfo"
1214                 }
1215                 AmbiguousColumnLocation.ENTITY -> {
1216                     checkNotNull(typeName)
1217                     "in the entity '$typeName'" to
1218                         "use a new data class / data class with " + "@ColumnInfo'"
1219                 }
1220             }
1221         return "The column '$columnName' $locationDesc is ambiguous and cannot be properly " +
1222             "resolved. Please alias the column and $recommendation. Otherwise there is a risk of " +
1223             "the query returning invalid values. You can suppress this warning by annotating " +
1224             "the function with @SuppressWarnings(RoomWarnings.AMBIGUOUS_COLUMN_IN_RESULT)."
1225     }
1226 
1227     enum class AmbiguousColumnLocation {
1228         MAP_INFO,
1229         DATA_CLASS,
1230         ENTITY,
1231     }
1232 
1233     const val NONNULL_VOID =
1234         "Invalid non-null declaration of 'Void', should be nullable. The 'Void' " +
1235             "class represents a placeholder type that is uninstantiable and 'null' is always returned."
1236 
1237     fun nullableCollectionOrArrayReturnTypeInDaoFunction(
1238         typeName: String,
1239         returnType: String
1240     ): String {
1241         return "The nullable `$returnType` ($typeName) return type in a DAO function is " +
1242             "meaningless because Room will instead return an empty `$returnType` if no rows are " +
1243             "returned from the query."
1244     }
1245 
1246     fun nullableComponentInDaoFunctionReturnType(typeName: String): String {
1247         return "The DAO function return type ($typeName) with the nullable type argument " +
1248             "is meaningless because for now Room will never put a null value in a result."
1249     }
1250 
1251     const val EXPORTING_SCHEMA_TO_RESOURCES =
1252         "Schema export is set to be outputted as a resource" +
1253             " (i.e. room.exportSchemaResource is set to true), this means Room will write the current" +
1254             " schema version file into the produced JAR. Such flag must only be used for generating" +
1255             " the schema file and extracting it from the JAR but not for production builds, otherwise" +
1256             " the schema file will end up in the final artifact which is typically not desired. This" +
1257             " warning serves as a reminder to use room.exportSchemaResource cautiously."
1258 
1259     const val INVALID_GRADLE_PLUGIN_AND_SCHEMA_LOCATION_OPTION =
1260         "The Room Gradle plugin " +
1261             "(id 'androidx.room') cannot be used with an explicit use of the annotation processor" +
1262             "option `room.schemaLocation`, please remove the configuration of the option and " +
1263             "configure the schema location via the plugin project extension: " +
1264             "`room { schemaDirectory(...) }`."
1265 
1266     const val INVALID_DATABASE_VERSION = "Database version must be greater than 0"
1267 
1268     const val JAVA_CODEGEN_ON_NON_ANDROID_TARGET =
1269         "Cannot generate Java targeting a non-Android " +
1270             "platform. To generate Java, you must only have Android as a target platform. " +
1271             "To process a non-Android target platform, you must enable Kotlin code " +
1272             "generation in KSP."
1273 
1274     const val INVALID_BLOCKING_DAO_FUNCTION_NON_ANDROID =
1275         "Only suspend functions are allowed in DAOs" +
1276             " declared in source sets targeting non-Android platforms."
1277 
1278     val INVALID_KOTLIN_CODE_GEN_IN_JAVAC =
1279         "${Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName} can only be enabled in KSP."
1280 
1281     const val RAW_QUERY_NOT_SUPPORTED_ON_NON_ANDROID =
1282         "@RawQuery annotated DAO functions are currently not supported in source sets targeting " +
1283             "non-Android platforms."
1284 
1285     const val MISSING_CONSTRUCTED_BY_ANNOTATION =
1286         "The @Database class must be annotated with @ConstructedBy since the source is targeting " +
1287             "non-Android platforms."
1288 
1289     const val INVALID_CONSTRUCTED_BY_CLASS = "The @ConstructedBy 'value' must be a valid class."
1290 
1291     const val INVALID_CONSTRUCTED_BY_NOT_OBJECT =
1292         "The @ConstructedBy definition must be an 'object' declaration."
1293 
1294     const val INVALID_CONSTRUCTED_BY_NOT_EXPECT =
1295         "The @ConstructedBy definition must be an 'expect' declaration."
1296 
1297     fun invalidConstructedBySuperInterface(expected: String) =
1298         "The @ConstructedBy definition must implement a single interface of type '$expected'."
1299 }
1300