Adding additional error coverage for MapInfo.
Adding checks for scenarios in which a Dao method with a map or multimap return type may have a key or value argument that either does not implement equals() and hashcode(), or is one of the built-in types that require further information (e.g String, primitive...).
Test: QueryMethodProcessorTest.kt
Change-Id: I5f908a596ad08d7181fe0547da23379cc5f5aaf0
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
index a8d63c4..76e5981 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
@@ -246,7 +246,6 @@
ImmutableMap<Artist, ByteBuffer> getAllArtistsWithAlbumCoversRawQuery(SupportSQLiteQuery query);
@RewriteQueriesToDropUnusedColumns
-
@MapInfo(valueColumn = "mImageYear")
@Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
ImmutableMap<Artist, Long> getAllArtistsWithAlbumCoverYear();
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
index f8fcf20..e97af36a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
@@ -66,15 +66,9 @@
* super class level and continues to look for these declared methods.
*/
fun XType.implementsEqualsAndHashcode(): Boolean {
- if (this.typeName.isPrimitive) return true
- if (this.typeName.isBoxedPrimitive) return true
- if (this.typeName == CommonTypeNames.STRING) return true
- if (this.isTypeOf(ByteArray::class)) return true
- if (this.isArray() && this.isByte()) return true
+ if (this.isSupportedMapTypeArg()) return true
val typeElement = this.typeElement ?: return false
-
- if (this.typeElement!!.isEnum()) return true
if (typeElement.className == ClassName.OBJECT) {
return false
}
@@ -91,8 +85,22 @@
it.parameters.count() == 0
}
- if (hasEquals && hasHashCode) {
- return true
- }
+ if (hasEquals && hasHashCode) return true
+
return typeElement.superType?.let { it.implementsEqualsAndHashcode() } ?: false
}
+
+/**
+ * Checks if the class of the provided type is one of the types supported in Dao functions with a
+ * Map or Multimap return type.
+ */
+fun XType.isSupportedMapTypeArg(): Boolean {
+ if (this.typeName.isPrimitive) return true
+ if (this.typeName.isBoxedPrimitive) return true
+ if (this.typeName == CommonTypeNames.STRING) return true
+ if (this.isTypeOf(ByteArray::class)) return true
+ if (this.isArray() && this.isByte()) return true
+ val typeElement = this.typeElement ?: return false
+ if (typeElement.isEnum()) return true
+ return false
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 543b2fe9..d884c99 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -134,8 +134,8 @@
fun cannotFindQueryResultAdapter(returnTypeName: TypeName) = "Not sure how to convert a " +
"Cursor to this method's return type ($returnTypeName)."
- fun classMustImplementEqualsAndHashCode(mapType: String, keyType: String) = "The key" +
- " of the provided method's multimap return type ($mapType) must implement equals() and " +
+ fun classMustImplementEqualsAndHashCode(keyType: String) = "The key" +
+ " of the provided method's multimap return type must implement equals() and " +
"hashCode(). Key type is: $keyType."
val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
@@ -151,6 +151,22 @@
val MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED = "To use the @MapInfo annotation, you " +
"must provide either the key column name, value column name, or both."
+ fun keyMayNeedMapInfo(keyArg: TypeName): String {
+ return """
+ Looks like you may need to use @MapInfo to clarify the 'keyColumnName' needed for
+ the return type of a method. Type argument that needs
+ @MapInfo: $keyArg
+ """.trim()
+ }
+
+ fun valueMayNeedMapInfo(valueArg: TypeName): String {
+ return """
+ Looks like you may need to use @MapInfo to clarify the 'valueColumnName' needed for
+ the return type of a method. Type argument that needs
+ @MapInfo: $valueArg
+ """.trim()
+ }
+
val CANNOT_FIND_DELETE_RESULT_ADAPTER = "Not sure how to handle delete method's " +
"return type. Currently the supported return types are void, int or Int."
@@ -225,6 +241,10 @@
return MISSING_PARAMETER_FOR_BIND.format(bindVarName.joinToString(", "))
}
+ fun valueCollectionMustBeListOrSet(mapValueTypeName: TypeName): String {
+ return "Multimap 'value' collection type must be a List or Set. Found $mapValueTypeName."
+ }
+
private val UNUSED_QUERY_METHOD_PARAMETER = "Unused parameter%s: %s"
fun unusedQueryMethodParameter(unusedParams: List<String>): String {
return UNUSED_QUERY_METHOD_PARAMETER.format(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 8816693..3a55bb7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -21,7 +21,6 @@
import androidx.room.compiler.processing.isEnum
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaBaseTypeNames
-import androidx.room.ext.implementsEqualsAndHashcode
import androidx.room.ext.isEntityElement
import androidx.room.ext.isNotByte
import androidx.room.ext.isNotKotlinUnit
@@ -34,7 +33,7 @@
import androidx.room.processor.FieldProcessor
import androidx.room.processor.PojoProcessor
import androidx.room.processor.ProcessorErrors.DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP
-import androidx.room.processor.ProcessorErrors.classMustImplementEqualsAndHashCode
+import androidx.room.processor.ProcessorErrors.valueCollectionMustBeListOrSet
import androidx.room.solver.binderprovider.CoroutineFlowResultBinderProvider
import androidx.room.solver.binderprovider.CursorQueryResultBinderProvider
import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
@@ -63,6 +62,7 @@
import androidx.room.solver.query.result.ImmutableMapQueryResultAdapter
import androidx.room.solver.query.result.ListQueryResultAdapter
import androidx.room.solver.query.result.MapQueryResultAdapter
+import androidx.room.solver.query.result.MultimapQueryResultAdapter.Companion.validateMapTypeArgs
import androidx.room.solver.query.result.OptionalQueryResultAdapter
import androidx.room.solver.query.result.PojoRowAdapter
import androidx.room.solver.query.result.QueryResultAdapter
@@ -100,7 +100,6 @@
import androidx.room.solver.types.TypeConverter
import androidx.room.vo.MapInfo
import androidx.room.vo.ShortcutQueryParameter
-import androidx.room.vo.Warning
import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableListMultimap
@@ -513,17 +512,6 @@
valueTypeArg
)
- if (!keyTypeArg.implementsEqualsAndHashcode()) {
- context.logger.w(
- Warning.DOES_NOT_IMPLEMENT_EQUALS_HASHCODE,
- keyTypeArg.typeElement,
- classMustImplementEqualsAndHashCode(
- typeMirror.typeName.toString(),
- keyTypeArg.typeName.toString()
- )
- )
- }
-
val resultAdapter = findQueryResultAdapter(mapType, query, extras) ?: return null
return ImmutableMapQueryResultAdapter(
keyTypeArg = keyTypeArg,
@@ -555,49 +543,39 @@
return null
}
- if (!keyTypeArg.implementsEqualsAndHashcode()) {
- context.logger.w(
- Warning.DOES_NOT_IMPLEMENT_EQUALS_HASHCODE,
- keyTypeArg.typeElement,
- classMustImplementEqualsAndHashCode(
- typeMirror.typeName.toString(),
- keyTypeArg.typeName.toString()
- )
- )
- }
-
// Get @MapInfo info if any (this might be null)
val mapInfo = extras.getData(MapInfo::class)
+ val keyRowAdapter = findRowAdapter(
+ typeMirror = keyTypeArg,
+ query = query,
+ columnName = mapInfo?.keyColumnName
+ ) ?: return null
+
+ val valueRowAdapter = findRowAdapter(
+ typeMirror = valueTypeArg,
+ query = query,
+ columnName = mapInfo?.valueColumnName
+ ) ?: return null
+
+ validateMapTypeArgs(
+ keyTypeArg = keyTypeArg,
+ valueTypeArg = valueTypeArg,
+ keyReader = findCursorValueReader(keyTypeArg, null),
+ valueReader = findCursorValueReader(valueTypeArg, null),
+ mapInfo = mapInfo,
+ logger = context.logger
+ )
return GuavaImmutableMultimapQueryResultAdapter(
keyTypeArg = keyTypeArg,
valueTypeArg = valueTypeArg,
- keyRowAdapter = findRowAdapter(
- typeMirror = keyTypeArg,
- query = query,
- columnName = mapInfo?.keyColumnName
- ) ?: return null,
- valueRowAdapter = findRowAdapter(
- typeMirror = valueTypeArg,
- query = query,
- columnName = mapInfo?.valueColumnName
- ) ?: return null,
+ keyRowAdapter = keyRowAdapter,
+ valueRowAdapter = valueRowAdapter,
immutableClassName = immutableClassName
)
} else if (typeMirror.isTypeOf(java.util.Map::class)) {
val keyTypeArg = typeMirror.typeArguments[0].extendsBoundOrSelf()
val mapValueTypeArg = typeMirror.typeArguments[1].extendsBoundOrSelf()
- if (!keyTypeArg.implementsEqualsAndHashcode()) {
- context.logger.w(
- Warning.DOES_NOT_IMPLEMENT_EQUALS_HASHCODE,
- keyTypeArg.typeElement,
- classMustImplementEqualsAndHashCode(
- typeMirror.typeName.toString(),
- keyTypeArg.typeName.toString()
- )
- )
- }
-
if (mapValueTypeArg.typeElement == null) {
context.logger.e(
"Multimap 'value' collection type argument does not represent a class. " +
@@ -618,41 +596,65 @@
mapValueTypeArg.isTypeOf(java.util.Set::class)
) {
val valueTypeArg = mapValueTypeArg.typeArguments.single().extendsBoundOrSelf()
+
+ val keyRowAdapter = findRowAdapter(
+ typeMirror = keyTypeArg,
+ query = query,
+ columnName = mapInfo?.keyColumnName
+ ) ?: return null
+
+ val valueRowAdapter = findRowAdapter(
+ typeMirror = valueTypeArg,
+ query = query,
+ columnName = mapInfo?.valueColumnName
+ ) ?: return null
+
+ validateMapTypeArgs(
+ keyTypeArg = keyTypeArg,
+ valueTypeArg = valueTypeArg,
+ keyReader = findCursorValueReader(keyTypeArg, null),
+ valueReader = findCursorValueReader(valueTypeArg, null),
+ mapInfo = mapInfo,
+ logger = context.logger
+ )
+
return MapQueryResultAdapter(
keyTypeArg = keyTypeArg,
valueTypeArg = valueTypeArg,
- keyRowAdapter = findRowAdapter(
- typeMirror = keyTypeArg,
- query = query,
- columnName = mapInfo?.keyColumnName
- ) ?: return null,
- valueRowAdapter = findRowAdapter(
- typeMirror = valueTypeArg,
- query = query,
- columnName = mapInfo?.valueColumnName
- ) ?: return null,
+ keyRowAdapter = keyRowAdapter,
+ valueRowAdapter = valueRowAdapter,
valueCollectionType = mapValueTypeArg
)
} else {
context.logger.e(
- "Multimap 'value' collection type must be a List or Set. Found " +
- "${mapValueTypeArg.typeName}."
+ valueCollectionMustBeListOrSet(mapValueTypeArg.typeName)
)
}
} else {
+ val keyRowAdapter = findRowAdapter(
+ typeMirror = keyTypeArg,
+ query = query,
+ columnName = mapInfo?.keyColumnName
+ ) ?: return null
+ val valueRowAdapter = findRowAdapter(
+ typeMirror = mapValueTypeArg,
+ query = query,
+ columnName = mapInfo?.valueColumnName
+ ) ?: return null
+
+ validateMapTypeArgs(
+ keyTypeArg = keyTypeArg,
+ valueTypeArg = mapValueTypeArg,
+ keyReader = findCursorValueReader(keyTypeArg, null),
+ valueReader = findCursorValueReader(mapValueTypeArg, null),
+ mapInfo = mapInfo,
+ logger = context.logger
+ )
return MapQueryResultAdapter(
keyTypeArg = keyTypeArg,
valueTypeArg = mapValueTypeArg,
- keyRowAdapter = findRowAdapter(
- typeMirror = keyTypeArg,
- query = query,
- columnName = mapInfo?.keyColumnName
- ) ?: return null,
- valueRowAdapter = findRowAdapter(
- typeMirror = mapValueTypeArg,
- query = query,
- columnName = mapInfo?.valueColumnName
- ) ?: return null,
+ keyRowAdapter = keyRowAdapter,
+ valueRowAdapter = valueRowAdapter,
valueCollectionType = null
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt
index 2acfb6d2..5e44a0e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt
@@ -17,6 +17,12 @@
package androidx.room.solver.query.result
import androidx.room.compiler.processing.XType
+import androidx.room.ext.implementsEqualsAndHashcode
+import androidx.room.log.RLog
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.types.CursorValueReader
+import androidx.room.vo.MapInfo
+import androidx.room.vo.Warning
/**
* Common interface for Map and Multimap result adapters.
@@ -24,4 +30,47 @@
interface MultimapQueryResultAdapter {
val keyTypeArg: XType
val valueTypeArg: XType
-}
\ No newline at end of file
+
+ companion object {
+ /**
+ * Checks if the @MapInfo annotation is needed for clarification regarding the return type
+ * of a Dao method.
+ */
+ fun validateMapTypeArgs(
+ keyTypeArg: XType,
+ valueTypeArg: XType,
+ keyReader: CursorValueReader?,
+ valueReader: CursorValueReader?,
+ mapInfo: MapInfo?,
+ logger: RLog
+ ) {
+
+ if (!keyTypeArg.implementsEqualsAndHashcode()) {
+ logger.w(
+ Warning.DOES_NOT_IMPLEMENT_EQUALS_HASHCODE,
+ ProcessorErrors.classMustImplementEqualsAndHashCode(
+ keyTypeArg.typeName.toString()
+ )
+ )
+ }
+
+ val hasKeyColumnName = mapInfo?.keyColumnName?.isNotEmpty() ?: false
+ if (!hasKeyColumnName && keyReader != null) {
+ logger.e(
+ ProcessorErrors.keyMayNeedMapInfo(
+ keyTypeArg.typeName
+ )
+ )
+ }
+
+ val hasValueColumnName = mapInfo?.valueColumnName?.isNotEmpty() ?: false
+ if (!hasValueColumnName && valueReader != null) {
+ logger.e(
+ ProcessorErrors.valueMayNeedMapInfo(
+ valueTypeArg.typeName
+ )
+ )
+ }
+ }
+ }
+}
diff --git a/room/room-compiler/src/test/data/common/input/DateConverter.java b/room/room-compiler/src/test/data/common/input/DateConverter.java
new file mode 100644
index 0000000..cc05818
--- /dev/null
+++ b/room/room-compiler/src/test/data/common/input/DateConverter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import androidx.room.TypeConverter;
+
+import java.util.Date;
+
+public class DateConverter {
+ @TypeConverter
+ public static Date toDate(Long input) {return null;}
+
+ @TypeConverter
+ public static Long fromDate(Date input) {return null;}
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/data/common/input/Image.java b/room/room-compiler/src/test/data/common/input/Image.java
new file mode 100644
index 0000000..d40cee9
--- /dev/null
+++ b/room/room-compiler/src/test/data/common/input/Image.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+import androidx.room.TypeConverters;
+
+import java.util.Date;
+
+@Entity
+@TypeConverters(DateConverter.class)
+public class Image {
+ @PrimaryKey
+ public final int mImageId;
+ public final Long mImageYear;
+ public final String mArtistInImage;
+ public final byte[] mAlbumCover;
+ public final Date mDateReleased;
+ public final ImageFormat mFormat;
+
+ public Image(int imageId, Long imageYear, String artistInImage, byte[] albumCover,
+ Date dateReleased, ImageFormat format) {
+ mImageId = imageId;
+ mImageYear = imageYear;
+ mArtistInImage = artistInImage;
+ mAlbumCover = albumCover;
+ mDateReleased = dateReleased;
+ mFormat = format;
+ }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/data/common/input/ImageFormat.java b/room/room-compiler/src/test/data/common/input/ImageFormat.java
new file mode 100644
index 0000000..8150b4d
--- /dev/null
+++ b/room/room-compiler/src/test/data/common/input/ImageFormat.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package foo.bar;
+public enum ImageFormat {
+ JPG,
+ GIF,
+ MPEG
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index f0e05bf..98f36d2 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -34,6 +34,8 @@
import androidx.room.processor.ProcessorErrors.DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP
import androidx.room.processor.ProcessorErrors.MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED
import androidx.room.processor.ProcessorErrors.cannotFindQueryResultAdapter
+import androidx.room.processor.ProcessorErrors.keyMayNeedMapInfo
+import androidx.room.processor.ProcessorErrors.valueMayNeedMapInfo
import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
import androidx.room.solver.query.result.ListQueryResultAdapter
import androidx.room.solver.query.result.LiveDataQueryResultBinder
@@ -76,6 +78,7 @@
import androidx.annotation.NonNull;
import androidx.room.*;
import java.util.*;
+ import com.google.common.collect.*;
@Dao
abstract class MyClass {
"""
@@ -1304,7 +1307,8 @@
)
val commonSources = listOf(
COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER, COMMON.BOOK,
- COMMON.NOT_AN_ENTITY, COMMON.ARTIST, COMMON.SONG
+ COMMON.NOT_AN_ENTITY, COMMON.ARTIST, COMMON.SONG, COMMON.IMAGE, COMMON.IMAGE_FORMAT,
+ COMMON.CONVERTER
)
runProcessorTest(
sources = additionalSources + commonSources + inputSource,
@@ -1404,7 +1408,6 @@
hasWarningCount(1)
hasWarningContaining(
ProcessorErrors.classMustImplementEqualsAndHashCode(
- "java.util.Map<foo.bar.User, foo.bar.Book>",
"foo.bar.User"
)
)
@@ -1413,6 +1416,171 @@
}
@Test
+ fun testMissingMapInfoOneToOneString() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @Query("select * from Artist JOIN Song ON Artist.mArtistName == Song.mArtist")
+ abstract Map<Artist, String> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testOneToOneStringMapInfoForKeyInsteadOfColumn() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @MapInfo(keyColumn = "mArtistName")
+ @Query("select * from Artist JOIN Song ON Artist.mArtistName == Song.mArtist")
+ abstract Map<Artist, String> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoOneToManyString() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @Query("select * from Artist JOIN Song ON Artist.mArtistName == Song.mArtist")
+ abstract Map<Artist, List<String>> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneString() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @Query("select * from Artist JOIN Song ON Artist.mArtistName == Song.mArtist")
+ abstract ImmutableListMultimap<Artist, String> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoOneToOneLong() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
+ Map<Artist, Long> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "Long")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoOneToManyLong() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
+ Map<Artist, Set<Long>> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "Long")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneLong() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
+ ImmutableListMultimap<Artist, Long> getAllArtistsWithAlbumCoverYear();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.lang", "Long")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneTypeConverterKey() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @TypeConverters(DateConverter.class)
+ @Query("SELECT * FROM Image JOIN Artist ON Artist.mArtistName = Image.mArtistInImage")
+ ImmutableMap<java.util.Date, Artist> getAlbumDateWithBandActivity();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ keyMayNeedMapInfo(
+ ClassName.get("java.util", "Date")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneTypeConverterValue() {
+ singleQueryMethod<ReadQueryMethod>(
+ """
+ @TypeConverters(DateConverter.class)
+ @Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
+ ImmutableMap<Artist, java.util.Date> getAlbumDateWithBandActivity();
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ valueMayNeedMapInfo(
+ ClassName.get("java.util", "Date")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
fun testUseMapInfoWithColumnsNotInQuery() {
if (!enableVerification) {
return
@@ -1428,7 +1596,6 @@
hasWarningCount(1)
hasWarningContaining(
ProcessorErrors.classMustImplementEqualsAndHashCode(
- "java.util.Map<foo.bar.User, foo.bar.Book>",
"foo.bar.User"
)
)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
index 721c703..9acb444 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -400,7 +400,6 @@
hasWarningCount(1)
hasWarningContaining(
ProcessorErrors.classMustImplementEqualsAndHashCode(
- "java.util.Map<foo.bar.User, foo.bar.Book>",
"foo.bar.User"
)
)
@@ -408,6 +407,171 @@
}
}
+ @Test
+ fun testMissingMapInfoOneToOneString() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ abstract Map<Artist, String> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoOneToManyString() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ abstract Map<Artist, List<String>> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneString() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ abstract ImmutableListMultimap<Artist, String> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoOneToOneLong() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ Map<Artist, Long> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "Long")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoOneToManyLong() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ Map<Artist, Set<Long>> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "Long")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneLong() {
+ singleQueryMethod(
+ """
+ @RawQuery
+ ImmutableListMultimap<Artist, Long> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "Long")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneTypeConverterKey() {
+ singleQueryMethod(
+ """
+ @TypeConverters(DateConverter.class)
+ @RawQuery
+ ImmutableMap<java.util.Date, Artist> getAlbumDateWithBandActivity(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.keyMayNeedMapInfo(
+ ClassName.get("java.util", "Date")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testMissingMapInfoImmutableListMultimapOneToOneTypeConverterValue() {
+ singleQueryMethod(
+ """
+ @TypeConverters(DateConverter.class)
+ @RawQuery
+ ImmutableMap<Artist, java.util.Date> getAlbumDateWithBandActivity(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.util", "Date")
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testOneToOneStringMapInfoForKeyInsteadOfColumn() {
+ singleQueryMethod(
+ """
+ @MapInfo(keyColumn = "mArtistName")
+ @RawQuery
+ abstract Map<Artist, String> getAllArtistsWithAlbumCoverYear(SupportSQLiteQuery query);
+ """
+ ) { _, invocation ->
+ invocation.assertCompilationResult {
+ hasErrorContaining(
+ ProcessorErrors.valueMayNeedMapInfo(
+ ClassName.get("java.lang", "String")
+ )
+ )
+ }
+ }
+ }
+
private fun singleQueryMethod(
vararg input: String,
handler: (RawQueryMethod, XTestInvocation) -> Unit
@@ -421,7 +585,8 @@
val commonSources = listOf(
COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE,
- COMMON.NOT_AN_ENTITY, COMMON.BOOK, COMMON.ARTIST, COMMON.SONG
+ COMMON.NOT_AN_ENTITY, COMMON.BOOK, COMMON.ARTIST, COMMON.SONG, COMMON.IMAGE,
+ COMMON.IMAGE_FORMAT, COMMON.CONVERTER
)
runProcessorTest(
sources = commonSources + inputSource
@@ -455,6 +620,7 @@
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.lifecycle.LiveData;
import java.util.*;
+ import com.google.common.collect.*;
@Dao
abstract class MyClass {
"""
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index be1bdc8..31e60b3 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -49,6 +49,18 @@
loadJavaCode("common/input/Artist.java", "foo.bar.Artist")
}
+ val CONVERTER by lazy {
+ loadJavaCode("common/input/DateConverter.java", "foo.bar.DateConverter")
+ }
+
+ val IMAGE_FORMAT by lazy {
+ loadJavaCode("common/input/ImageFormat.java", "foo.bar.ImageFormat")
+ }
+
+ val IMAGE by lazy {
+ loadJavaCode("common/input/Image.java", "foo.bar.Image")
+ }
+
val SONG by lazy {
loadJavaCode("common/input/Song.java", "foo.bar.Song")
}