Skip to content

Commit 5a1cd32

Browse files
authored
Set data type (#7442)
1 parent 57d24b4 commit 5a1cd32

File tree

124 files changed

+12954
-567
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+12954
-567
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* Added support for `java.util.UUID` as a primary key.
1313
* Added support for `RealmAny` as supported field in model classes. A `RealmAny` is used to represent a polymorphic Realm value or Realm Object, is indexable but cannot be used as a primary key. See [Javadoc for RealmAny](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmAny.html).
1414
* Added support for `RealmDictionary` as supported field in model classes. A `RealmDictionary` is a `Map` of strings to values - all types under the `RealmAny` umbrella can be used as values. See [Javadoc for RealmDictionary](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmDictionary.html) and [Javadoc for RealmMap](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmMap.html). `RealmDictionary` is not yet supported by any of the `Realm.insert` and `Realm.createFromJson` methods - This support will be added in a future release.
15+
* Added support for `RealmSet` as supported field in model classes. A `RealmSet` is a collection that implements the Java `Set` interface and contains no duplicate values - all types under the `RealmAny` umbrella can be used as values. See [Javadoc for RealmSet](https://docs.mongodb.com/realm-sdks/java/latest/io/realm/RealmSet.html). `RealmSet` is not yet supported by any of the `Realm.insert` and `Realm.createFromJson` methods - This support will be added in a future release.
1516
* Allow UTF8 encoded characters in property names in string-based queries ([#4467](https://github.com/realm/realm-core/issues/4467))
1617
* The error message when the initial steps of opening a Realm file fails is now more descriptive.
1718
* Make conversion of Decimal128 to/from string work for numbers with more than 19 significant digits. ([#4548](https://github.com/realm/realm-core/issues/4548))

dependencies.list

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ REALM_CORE=10.3.3
44

55
# Version of MongoDB Realm used by integration tests
66
# See https://github.com/realm/ci/packages/147854 for available versions
7-
MONGODB_REALM_SERVER=2021-04-19
7+
MONGODB_REALM_SERVER=2021-04-22
88

99
# Common Android settings across projects
1010
GRADLE_BUILD_TOOLS=4.0.0

realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
4141
private val nullableFields = LinkedHashSet<RealmFieldElement>() // Set of fields which can be nullable
4242
private val nullableValueListFields = LinkedHashSet<RealmFieldElement>() // Set of fields whose elements can be nullable
4343
private val nullableValueMapFields = LinkedHashSet<RealmFieldElement>() // Set of fields whose elements can be nullable
44+
private val nullableValueSetFields = LinkedHashSet<RealmFieldElement>() // Set of fields whose elements can be nullable
45+
private val realmModelSets = LinkedHashSet<RealmFieldElement>()
4446

4547
// package name for model class.
4648
private lateinit var packageName: String
@@ -85,10 +87,25 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
8587
typeMirrors.UUID_MIRROR,
8688
typeMirrors.MIXED_MIRROR
8789
)
88-
private val validDictionaryTypes: List<TypeMirror> = listOf(
90+
private val validDictionaryTypes: List<TypeMirror> = listOf(
91+
typeMirrors.STRING_MIRROR,
92+
typeMirrors.BINARY_MIRROR,
93+
typeMirrors.BOOLEAN_MIRROR,
94+
typeMirrors.LONG_MIRROR,
95+
typeMirrors.INTEGER_MIRROR,
96+
typeMirrors.SHORT_MIRROR,
97+
typeMirrors.BYTE_MIRROR,
98+
typeMirrors.DOUBLE_MIRROR,
99+
typeMirrors.FLOAT_MIRROR,
100+
typeMirrors.DATE_MIRROR,
101+
typeMirrors.DECIMAL128_MIRROR,
102+
typeMirrors.OBJECT_ID_MIRROR,
103+
typeMirrors.UUID_MIRROR,
104+
typeMirrors.MIXED_MIRROR
105+
)
106+
private val validSetTypes: List<TypeMirror> = listOf(
89107
typeMirrors.STRING_MIRROR,
90108
typeMirrors.BINARY_MIRROR,
91-
typeMirrors.BINARY_NON_PRIMITIVE_MIRROR,
92109
typeMirrors.BOOLEAN_MIRROR,
93110
typeMirrors.LONG_MIRROR,
94111
typeMirrors.INTEGER_MIRROR,
@@ -119,6 +136,9 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
119136
val primaryKeyGetter: String
120137
get() = getInternalGetter(primaryKey!!.simpleName.toString())
121138

139+
val realmModelSetFields: Set<RealmFieldElement>
140+
get() = realmModelSets.toSet()
141+
122142
/**
123143
* Returns `true if the class is considered to be a valid RealmObject class.
124144
* RealmObject and Proxy classes also have the @RealmClass annotation but are not considered valid
@@ -232,6 +252,15 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
232252
return nullableValueMapFields.contains(realmDictionaryVariableElement)
233253
}
234254

255+
/**
256+
* Checks if the value of a `RealmDictionary` entry designated by `realmDictionaryVariableElement` is nullable.
257+
*
258+
* @return `true` if the element is nullable type, `false` otherwise.
259+
*/
260+
fun isSetValueNullable(realmSetVariableElement: VariableElement): Boolean {
261+
return nullableValueSetFields.contains(realmSetVariableElement)
262+
}
263+
235264
/**
236265
* Checks if a VariableElement is indexed.
237266
*
@@ -333,6 +362,9 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
333362
if (!checkDictionaryTypes()) {
334363
return false
335364
}
365+
if (!checkSetTypes()) {
366+
return false
367+
}
336368
if (!checkReferenceTypes()) {
337369
return false
338370
}
@@ -403,6 +435,34 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
403435
return true
404436
}
405437

438+
private fun checkSetTypes(): Boolean {
439+
for (field in fields) {
440+
if (Utils.isRealmSet(field)) {
441+
if (!checkSetValuesType(field)) {
442+
return false
443+
}
444+
}
445+
}
446+
447+
return true
448+
}
449+
450+
private fun checkSetValuesType(field: VariableElement): Boolean {
451+
// Check for missing generic (default back to Object)
452+
if (Utils.getGenericTypeQualifiedName(field) == null) {
453+
Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field)
454+
return false
455+
}
456+
457+
val elementTypeMirror = checkReferenceIsNotInterface(field) ?: return false
458+
return checkAcceptableClass(
459+
field,
460+
elementTypeMirror,
461+
validSetTypes,
462+
"Element type RealmSet must be of type 'RealmAny' or any type that can be boxed inside 'RealmAny': "
463+
)
464+
}
465+
406466
private fun checkDictionaryValuesType(field: VariableElement): Boolean {
407467
// Check for missing generic (default back to Object)
408468
if (Utils.getGenericTypeQualifiedName(field) == null) {
@@ -415,7 +475,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
415475
field,
416476
elementTypeMirror,
417477
validDictionaryTypes,
418-
"Element type of RealmDictionary must be of type 'RealmAny' or any type that can be boxed inside 'RealmAny': "
478+
"Element type RealmDictionary must be of type 'RealmAny' or any type that can be boxed inside 'RealmAny': "
419479
)
420480
}
421481

@@ -541,8 +601,10 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
541601
if (!field.modifiers.contains(Modifier.FINAL)) {
542602
continue
543603
}
544-
if (Utils.isRealmList(field) || Utils.isMutableRealmInteger(field) ||
545-
Utils.isRealmDictionary(field)) {
604+
if (Utils.isRealmList(field) ||
605+
Utils.isMutableRealmInteger(field) ||
606+
Utils.isRealmDictionary(field) ||
607+
Utils.isRealmSet(field)) {
546608
continue
547609
}
548610

@@ -617,7 +679,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
617679
val containsRealmModelClasses = (listGenericType.isNotEmpty() && Utils.isRealmModel(listGenericType[0]))
618680
val containsRealmAny = (listGenericType.isNotEmpty() && Utils.isRealmAny(listGenericType[0]))
619681

620-
// @Required not allowed if the dictionary contains Realm model classes
682+
// @Required not allowed if the dictionary contains Realm model classes or RealmAny
621683
if (hasRequiredAnnotation && (containsRealmModelClasses || containsRealmAny)) {
622684
Utils.error("@Required not allowed on RealmDictionaries that contain other Realm model classes and RealmAny.")
623685
return false
@@ -630,6 +692,30 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
630692
nullableValueMapFields.add(field)
631693
}
632694
}
695+
} else if (Utils.isRealmSet(field)) {
696+
// Same as RealmList
697+
val hasRequiredAnnotation = hasRequiredAnnotation(field)
698+
val setGenericType = (field.asType() as DeclaredType).typeArguments
699+
val containsRealmModelClasses = (setGenericType.isNotEmpty() && Utils.isRealmModel(setGenericType[0]))
700+
val containsRealmAny = (setGenericType.isNotEmpty() && Utils.isRealmAny(setGenericType[0]))
701+
702+
// @Required not allowed if the set contains Realm model classes or RealmAny
703+
if (hasRequiredAnnotation && (containsRealmModelClasses || containsRealmAny)) {
704+
Utils.error("@Required not allowed on RealmSets that contain other Realm model classes and RealmAny.")
705+
return false
706+
}
707+
708+
// @Required thus only makes sense for RealmSets with primitive types
709+
// We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values.
710+
if (!hasRequiredAnnotation) {
711+
if (!containsRealmModelClasses) {
712+
nullableValueSetFields.add(field)
713+
}
714+
}
715+
716+
if (containsRealmModelClasses) {
717+
realmModelSets.add(field)
718+
}
633719
} else if (isRequiredField(field)) {
634720
if (!checkBasicRequiredAnnotationUsage(field)) {
635721
return false
@@ -670,7 +756,9 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat
670756
Utils.isRealmAnyList(field) ||
671757
Utils.isRealmAny(field) ||
672758
Utils.isRealmModelDictionary(field) ||
673-
Utils.isRealmAnyDictionary(field)) {
759+
Utils.isRealmModelSet(field) ||
760+
Utils.isRealmAnyDictionary(field)||
761+
Utils.isRealmAnySet(field)) {
674762
_objectReferenceFields.add(field)
675763
} else {
676764
basicTypeFields.add(field)

realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@ object Constants {
8787
"io.realm.RealmAny" to RealmFieldType.STRING_TO_MIXED_MAP
8888
)
8989

90+
val SET_ELEMENT_TYPE_TO_REALM_TYPES = mapOf(
91+
"java.lang.Byte" to RealmFieldType.INTEGER_SET,
92+
"java.lang.Short" to RealmFieldType.INTEGER_SET,
93+
"java.lang.Integer" to RealmFieldType.INTEGER_SET,
94+
"java.lang.Long" to RealmFieldType.INTEGER_SET,
95+
"java.lang.Float" to RealmFieldType.FLOAT_SET,
96+
"java.lang.Double" to RealmFieldType.DOUBLE_SET,
97+
"java.lang.Boolean" to RealmFieldType.BOOLEAN_SET,
98+
"java.lang.String" to RealmFieldType.STRING_SET,
99+
"java.util.Date" to RealmFieldType.DATE_SET,
100+
"byte[]" to RealmFieldType.BINARY_SET,
101+
"org.bson.types.Decimal128" to RealmFieldType.DECIMAL128_SET,
102+
"org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID_SET,
103+
"java.util.UUID" to RealmFieldType.UUID_SET,
104+
"io.realm.RealmAny" to RealmFieldType.MIXED_SET
105+
)
106+
90107
/**
91108
* Realm types and their corresponding Java types.
92109
*
@@ -135,8 +152,20 @@ object Constants {
135152
STRING_TO_DECIMAL128_MAP("STRING_TO_DECIMAL128_MAP", "Map"),
136153
STRING_TO_OBJECT_ID_MAP("STRING_TO_OBJECT_ID_MAP", "Map"),
137154
STRING_TO_UUID_MAP("STRING_TO_UUID_MAP", "Map"),
138-
STRING_TO_MIXED_MAP("STRING_TO_MIXED_MAP", "Map");
155+
STRING_TO_MIXED_MAP("STRING_TO_MIXED_MAP", "Map"),
139156

157+
INTEGER_SET("INTEGER_SET", "Set"),
158+
BOOLEAN_SET("BOOLEAN_SET", "Set"),
159+
STRING_SET("STRING_SET", "Set"),
160+
BINARY_SET("BINARY_SET", "Set"),
161+
DATE_SET("DATE_SET", "Set"),
162+
FLOAT_SET("FLOAT_SET", "Set"),
163+
DOUBLE_SET("DOUBLE_SET", "Set"),
164+
DECIMAL128_SET("DECIMAL128_SET", "Set"),
165+
OBJECT_ID_SET("OBJECT_ID_SET", "Set"),
166+
UUID_SET("UUID_SET", "Set"),
167+
LINK_SET("LINK_SET", "Set"),
168+
MIXED_SET("MIXED_SET", "Set");
140169

141170
/**
142171
* The name of the enum, used in the Java bindings, used to represent the corresponding type.

realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ object OsObjectBuilderTypeHelper {
4242
QualifiedClassName("java.util.UUID") to "UUIDValueDictionary"
4343
)
4444

45+
private val QUALIFIED_SET_VALUES: Map<QualifiedClassName, String> = mapOf(
46+
QualifiedClassName("io.realm.RealmAny") to "RealmAnySet",
47+
QualifiedClassName("java.lang.Boolean") to "BooleanSet",
48+
QualifiedClassName("java.lang.String") to "StringSet",
49+
QualifiedClassName("java.lang.Integer") to "IntegerSet",
50+
QualifiedClassName("java.lang.Float") to "FloatSet",
51+
QualifiedClassName("java.lang.Long") to "LongSet",
52+
QualifiedClassName("java.lang.Short") to "ShortSet",
53+
QualifiedClassName("java.lang.Byte") to "ByteSet",
54+
QualifiedClassName("java.lang.Double") to "DoubleSet",
55+
QualifiedClassName("java.util.Date") to "DateSet",
56+
QualifiedClassName("byte[]") to "BinarySet",
57+
QualifiedClassName("org.bson.types.ObjectId") to "ObjectIdSet",
58+
QualifiedClassName("org.bson.types.Decimal128") to "Decimal128Set",
59+
QualifiedClassName("java.util.UUID") to "UUIDSet"
60+
)
61+
4562
init {
4663
// Map of qualified types to their OsObjectBuilder Type
4764
val fieldTypes = HashMap<QualifiedClassName, String>()
@@ -107,6 +124,8 @@ object OsObjectBuilderTypeHelper {
107124
"add" + getListTypeName(Utils.getRealmListType(field))
108125
} else if (Utils.isRealmDictionary(field)) {
109126
"add" + getDictionaryValueTypeName(Utils.getDictionaryType(field))
127+
} else if (Utils.isRealmSet(field)) {
128+
"add" + getSetValueTypeName(Utils.getSetType(field))
110129
} else if (Utils.isRealmResults(field)) {
111130
throw IllegalStateException("RealmResults are not supported by OsObjectBuilder: $field")
112131
} else {
@@ -135,4 +154,10 @@ object OsObjectBuilderTypeHelper {
135154
"Unsupported dictionary value type: '$typeName'"
136155
}
137156
}
157+
158+
private fun getSetValueTypeName(typeName: QualifiedClassName?): String {
159+
return requireNotNull(QUALIFIED_SET_VALUES[typeName]) {
160+
"Unsupported set value type: '$typeName'"
161+
}
162+
}
138163
}

realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@
1616

1717
package io.realm.processor
1818

19+
import io.realm.annotations.RealmClass
20+
import io.realm.annotations.RealmModule
1921
import java.io.IOException
20-
import java.util.HashSet
21-
22+
import java.util.*
2223
import javax.annotation.processing.AbstractProcessor
2324
import javax.annotation.processing.RoundEnvironment
2425
import javax.annotation.processing.SupportedAnnotationTypes
2526
import javax.annotation.processing.SupportedOptions
2627
import javax.lang.model.SourceVersion
2728
import javax.lang.model.element.ElementKind
28-
import javax.lang.model.element.TypeElement
29-
30-
import io.realm.annotations.RealmClass
31-
import io.realm.annotations.RealmModule
3229
import javax.lang.model.element.Name
30+
import javax.lang.model.element.TypeElement
31+
import javax.lang.model.type.DeclaredType
3332
import javax.lang.model.type.TypeMirror
3433

3534

@@ -152,6 +151,9 @@ class RealmProcessor : AbstractProcessor() {
152151
// List of backlinks
153152
private val backlinksToValidate = HashSet<Backlink>()
154153

154+
// List of realm model sets
155+
private val realmModelSetsToValidate = HashSet<RealmFieldElement>()
156+
155157
private var hasProcessedModules = false
156158
private var round = -1
157159

@@ -187,6 +189,9 @@ class RealmProcessor : AbstractProcessor() {
187189
if (!validateBacklinks()) {
188190
return ABORT
189191
}
192+
if (!validateRealmModelSets()) {
193+
return ABORT
194+
}
190195
hasProcessedModules = true
191196

192197
// Create all files
@@ -230,6 +235,7 @@ class RealmProcessor : AbstractProcessor() {
230235

231236
classCollection.addClass(metadata)
232237
backlinksToValidate.addAll(metadata.backlinkFields)
238+
realmModelSetsToValidate.addAll(metadata.realmModelSetFields)
233239
}
234240

235241
return true
@@ -338,4 +344,26 @@ class RealmProcessor : AbstractProcessor() {
338344

339345
return allValid
340346
}
347+
348+
// Because library classes are processed separately, there is no guarantee that this method can
349+
// see all of the classes necessary to completely validate all of the realm model sets. If it can find
350+
// the fully-qualified class, though, and prove that the generic type is an embedded object,
351+
// it can catch the error at compile time. Otherwise it is caught at runtime, when validating the
352+
// schema.
353+
private fun validateRealmModelSets(): Boolean {
354+
var allValid = true
355+
356+
for (field in realmModelSetsToValidate) {
357+
val genericClassName = (field.asType() as DeclaredType).typeArguments.toString()
358+
val genericType = processingEnv.elementUtils.getTypeElement(genericClassName).asType()
359+
360+
val embedded = Utils.isFieldTypeEmbedded(genericType, classCollection)
361+
if(embedded && allValid){
362+
Utils.error("RealmSets field ${field.javaName} at ${field.fieldReference} do not support embedded objects.")
363+
allValid = false
364+
}
365+
}
366+
367+
return allValid
368+
}
341369
}

0 commit comments

Comments
 (0)