Skip to content

Commit e5ae3aa

Browse files
authored
Sets - Insert or update (#7464)
1 parent 6b060ff commit e5ae3aa

File tree

12 files changed

+195
-38
lines changed

12 files changed

+195
-38
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
* None.
55

66
### Enhancements
7-
* Allow `insert` and `insertOrUpdate` RealmObject's or collections of RealmObject's containing RealmDictionaries.
7+
* Allow `insert` and `insertOrUpdate` operations on `RealmObject` or `RealmObject` collections containing `RealmDictionary` or `RealmSet` fields.
88

99
### Fixed
1010
* Removed wrong `@Nullable` annotation on `RealmQuery.maxRealmAny()`.
11+
* Fixed `RealmAny.getValueClass()` returning the `RealmObject` proxy class instead of the model class on a `RealmAny` referencing a managed `RealmObject`.
1112

1213
### Compatibility
1314
* File format: Generates Realms with format v21. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1.

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

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,9 +1921,86 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi
19211921
endControlFlow()
19221922
emitEmptyLine()
19231923
}
1924-
Utils.isRealmSet(field) -> {
1925-
// TODO: Set
1926-
emitSingleLineComment("TODO: Set")
1924+
Utils.isRealmModelSet(field) -> {
1925+
val genericType: TypeMirror = Utils.getGenericType(field)!!
1926+
val isEmbedded = Utils.isFieldTypeEmbedded(genericType, classCollection)
1927+
1928+
emitEmptyLine()
1929+
emitStatement("OsSet %1\$sOsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
1930+
emitStatement("RealmSet<%s> %sSet = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
1931+
if (isEmbedded) {
1932+
// throw not supported
1933+
throw UnsupportedOperationException("Field $fieldName of type RealmSet<${genericType}>, RealmSet does not support embedded objects.")
1934+
} else {
1935+
beginControlFlow("if (%1\$sSet != null && %1\$sSet.size() == %1\$sOsSet.size())", fieldName)
1936+
emitSingleLineComment("For Sets of equal lengths, we need to set each element directly as clearing the receiver Set can be wrong if the input and target Set are the same.")
1937+
emitStatement("int objectCount = %1\$sSet.size()", fieldName)
1938+
beginControlFlow("for (${genericType} ${fieldName}Item: ${fieldName}Set)")
1939+
emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
1940+
beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
1941+
emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getSetGenericProxyClassSimpleName(field))
1942+
endControlFlow()
1943+
emitStatement("%1\$sOsSet.addRow(cacheItemIndex%1\$s)", fieldName)
1944+
endControlFlow()
1945+
nextControlFlow("else")
1946+
emitStatement("%1\$sOsSet.clear()", fieldName)
1947+
beginControlFlow("if (%sSet != null)", fieldName)
1948+
beginControlFlow("for (%1\$s %2\$sItem : %2\$sSet)", genericType, fieldName)
1949+
emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
1950+
beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
1951+
emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getSetGenericProxyClassSimpleName(field))
1952+
endControlFlow()
1953+
emitStatement("%1\$sOsSet.addRow(cacheItemIndex%1\$s)", fieldName)
1954+
endControlFlow()
1955+
endControlFlow()
1956+
endControlFlow()
1957+
}
1958+
emitEmptyLine()
1959+
}
1960+
Utils.isRealmValueSet(field) -> {
1961+
val genericType = Utils.getGenericTypeQualifiedName(field)
1962+
emitEmptyLine()
1963+
emitStatement("OsSet %1\$sOsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
1964+
emitStatement("%1\$sOsSet.clear()", fieldName)
1965+
emitStatement("RealmSet<%s> %sSet = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
1966+
beginControlFlow("if (%sSet != null)", fieldName)
1967+
beginControlFlow("for (%1\$s %2\$sItem : %2\$sSet)", genericType, fieldName)
1968+
beginControlFlow("if (%1\$sItem == null)", fieldName)
1969+
emitStatement("%1\$sOsSet.add(($genericType) null)", fieldName)
1970+
nextControlFlow("else")
1971+
emitStatement("${fieldName}OsSet.add(${fieldName}Item)")
1972+
endControlFlow()
1973+
endControlFlow()
1974+
endControlFlow()
1975+
emitEmptyLine()
1976+
}
1977+
Utils.isRealmAnySet(field) -> {
1978+
emitEmptyLine()
1979+
emitStatement("OsSet ${fieldName}OsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
1980+
emitStatement("RealmSet<RealmAny> ${fieldName}Set = ((${interfaceName}) object).${getter}()")
1981+
1982+
beginControlFlow("if (${fieldName}Set != null && ${fieldName}Set.size() == ${fieldName}OsSet.size())")
1983+
emitSingleLineComment("For Sets of equal lengths, we need to set each element directly as clearing the receiver Set can be wrong if the input and target Set are the same.")
1984+
emitStatement("int objectCount = ${fieldName}Set.size()")
1985+
beginControlFlow("for (RealmAny ${fieldName}Item: ${fieldName}Set)")
1986+
emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
1987+
beginControlFlow("if (cacheItemIndex${fieldName} == null)")
1988+
emitStatement("${fieldName}Item = ProxyUtils.insertOrUpdate(${fieldName}Item, realm, cache)")
1989+
endControlFlow()
1990+
emitStatement("${fieldName}OsSet.addRealmAny(${fieldName}Item.getNativePtr())")
1991+
endControlFlow()
1992+
nextControlFlow("else")
1993+
emitStatement("${fieldName}OsSet.clear()")
1994+
beginControlFlow("if (${fieldName}Set != null)")
1995+
beginControlFlow("for (RealmAny ${fieldName}Item : ${fieldName}Set)")
1996+
emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
1997+
beginControlFlow("if (cacheItemIndex${fieldName} == null)")
1998+
emitStatement("${fieldName}Item = ProxyUtils.insertOrUpdate(${fieldName}Item, realm, cache)")
1999+
endControlFlow()
2000+
emitStatement("${fieldName}OsSet.addRealmAny(${fieldName}Item.getNativePtr())")
2001+
endControlFlow()
2002+
endControlFlow()
2003+
endControlFlow()
19272004
}
19282005
else -> {
19292006
if (metadata.primaryKey !== field) {
@@ -1950,14 +2027,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi
19502027
val args = if (metadata.embedded) embeddedArgs else topLevelArgs
19512028
beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args)
19522029

1953-
// Throw if model contains a set field until we add support for it
1954-
if (containsSet(metadata.fields)) {
1955-
emitStatement("throw new UnsupportedOperationException(\"Calls to 'insertOrUpdate' with RealmModels containing RealmSet properties are not supported yet.\")")
1956-
endMethod()
1957-
emitEmptyLine()
1958-
return@apply
1959-
}
1960-
19612030
// If object is already in the Realm there is nothing to update
19622031
beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))")
19632032
emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()")
@@ -1992,14 +2061,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi
19922061

19932062
beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args)
19942063

1995-
// Throw if model contains a set field until we add support for it
1996-
if (containsSet(metadata.fields)) {
1997-
emitStatement("throw new UnsupportedOperationException(\"Calls to 'insertOrUpdate' with RealmModels containing RealmSet properties are not supported yet.\")")
1998-
endMethod()
1999-
emitEmptyLine()
2000-
return@apply
2001-
}
2002-
20032064
emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
20042065
emitStatement("long tableNativePtr = table.getNativePtr()")
20052066
emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName)

realm/realm-library/src/androidTest/kotlin/io/realm/ManagedSetTester.kt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,75 @@ class ManagedSetTester<T : Any>(
406406
assertSetContainsSet(expectedSet, set)
407407
}
408408

409+
override fun insertOrUpdate() {
410+
// Instantiate container and set Set on container
411+
val manualInstance = SetAllTypesPrimaryKey().apply {
412+
primaryKeyAllTypesSetProperty.setter(this, RealmSet<T>().init(initializedSet))
413+
}
414+
415+
// Insert to Realm
416+
realm.executeTransaction {
417+
realm.insertOrUpdate(manualInstance)
418+
}
419+
420+
// Get Set from container from Realm
421+
val allTypesPrimaryKey = realm.where<SetAllTypesPrimaryKey>().findFirst()!!
422+
val set = primaryKeyAllTypesSetProperty.get(allTypesPrimaryKey)
423+
assertFalse(set.isEmpty())
424+
425+
assertSetContainsSet(initializedSet, set)
426+
427+
primaryKeyAllTypesSetProperty.getter(manualInstance).add(notPresentValue)
428+
429+
// Insert to Realm with non managed updated model
430+
realm.executeTransaction {
431+
realm.insertOrUpdate(manualInstance)
432+
}
433+
434+
val updatedContainer = realm.where<SetAllTypesPrimaryKey>().findFirst()!!
435+
val updatedSet = primaryKeyAllTypesSetProperty.get(updatedContainer)
436+
assertEquals(initializedSet.size + 1, updatedSet.size)
437+
438+
assertSetContainsSet(initializedSet.plus(notPresentValue), set)
439+
}
440+
441+
override fun insertOrUpdateList() {
442+
// Instantiate container and set Set on container
443+
val manualInstance = SetAllTypesPrimaryKey().apply {
444+
columnLong = 0
445+
primaryKeyAllTypesSetProperty.setter(this, RealmSet<T>().init(initializedSet))
446+
}
447+
448+
val emptyInstance = SetAllTypesPrimaryKey().apply {
449+
columnLong = 1
450+
}
451+
452+
// Insert to Realm
453+
realm.executeTransaction {
454+
realm.insertOrUpdate(listOf(emptyInstance, manualInstance))
455+
}
456+
457+
// Get Set from container from Realm
458+
val allTypesPrimaryKey = realm.where<SetAllTypesPrimaryKey>().equalTo("columnLong", 0.toLong()).findFirst()!!
459+
val set = primaryKeyAllTypesSetProperty.get(allTypesPrimaryKey)
460+
assertFalse(set.isEmpty())
461+
462+
assertSetContainsSet(initializedSet, set)
463+
464+
primaryKeyAllTypesSetProperty.getter(manualInstance).add(notPresentValue)
465+
466+
// Insert to Realm with non managed updated model
467+
realm.executeTransaction {
468+
realm.insertOrUpdate(listOf(emptyInstance, manualInstance))
469+
}
470+
471+
val updatedContainer = realm.where<SetAllTypesPrimaryKey>().findFirst()!!
472+
val updatedSet = primaryKeyAllTypesSetProperty.get(updatedContainer)
473+
assertEquals(initializedSet.size + 1, updatedSet.size)
474+
475+
assertSetContainsSet(initializedSet.plus(notPresentValue), set)
476+
}
477+
409478
override fun copyToRealm() {
410479
doCopyToRealmTest(initializedSet)
411480
}

realm/realm-library/src/androidTest/kotlin/io/realm/NoPKRealmModelSetTester.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ class NoPKRealmModelSetTester<T : RealmModel>(
130130

131131
override fun insertList() = Unit // Not tested
132132

133+
override fun insertOrUpdate() = Unit // Not tested
134+
135+
override fun insertOrUpdateList() = Unit // Not tested
136+
133137
override fun copyToRealm() = Unit // Not tested
134138

135139
override fun copyToRealmOrUpdate() = Unit // Not tested

realm/realm-library/src/androidTest/kotlin/io/realm/NullMixedSetTester.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class NullRealmAnySetTester(
5454

5555
override fun insertList() = Unit // Not applicable
5656

57+
override fun insertOrUpdate() = Unit // Not applicable
58+
59+
override fun insertOrUpdateList() = Unit // Not applicable
60+
5761
override fun copyToRealm() = Unit // Not applicable
5862

5963
override fun copyToRealmOrUpdate() = Unit // Not applicable

realm/realm-library/src/androidTest/kotlin/io/realm/ParameterizedSetTests.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ class ParameterizedSetTests(
123123
tester.insertList()
124124
}
125125

126+
@Test
127+
fun insertOrUpdate() {
128+
tester.insertOrUpdate()
129+
}
130+
131+
@Test
132+
fun insertOrUpdateList() {
133+
tester.insertOrUpdateList()
134+
}
135+
126136
@Test
127137
fun copyToRealm() {
128138
tester.copyToRealm()
@@ -231,6 +241,8 @@ interface SetTester : GenericTester {
231241
fun containsAll()
232242
fun insert()
233243
fun insertList()
244+
fun insertOrUpdate()
245+
fun insertOrUpdateList()
234246
fun copyToRealm()
235247
fun copyToRealmOrUpdate()
236248
fun addAll()

realm/realm-library/src/androidTest/kotlin/io/realm/RealmModelManagedSetTester.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ class RealmModelManagedSetTester<T : Any>(
219219
managedTester.doInsertListTest(unmanagedInitializedSet)
220220
}
221221

222+
override fun insertOrUpdate() {
223+
managedTester.insertOrUpdate()
224+
}
225+
226+
override fun insertOrUpdateList() {
227+
managedTester.insertOrUpdate()
228+
}
229+
222230
override fun copyToRealm() {
223231
// This specific test case needs unmanaged objects on PK models
224232
realm.executeTransaction {

realm/realm-library/src/androidTest/kotlin/io/realm/SetMiscTests.kt

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -239,17 +239,7 @@ class SetMiscTests {
239239

240240
realm.close()
241241
}
242-
243-
@Test
244-
fun insertOrUpdate_unsupportedOperation() {
245-
realm = Realm.getInstance(configFactory.createConfiguration())
246-
realm.executeTransaction {
247-
assertFailsWith<UnsupportedOperationException> {
248-
realm.insertOrUpdate(SetContainerMigrationClass())
249-
}
250-
}
251-
}
252-
242+
253243
@Test
254244
fun createAllFromJson_unsupportedOperation() {
255245
realm = Realm.getInstance(configFactory.createConfiguration())

realm/realm-library/src/androidTest/kotlin/io/realm/UnmanagedSetTester.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ class UnmanagedSetTester<T : Any>(
148148

149149
override fun insertList() = Unit // Not applicable
150150

151+
override fun insertOrUpdate() = Unit // Not applicable
152+
153+
override fun insertOrUpdateList() = Unit // Not applicable
154+
151155
override fun copyToRealm() = Unit // Not applicable
152156

153157
override fun copyToRealmOrUpdate() = Unit // Not applicable

realm/realm-library/src/androidTest/kotlin/io/realm/realmany/RealmAnyTests.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,16 @@ class RealmAnyTests {
503503
assertEquals(PrimaryKeyAsString::class.simpleName, realmAnyObject.realmAny!!.valueClass!!.simpleName)
504504
}
505505

506+
@Test
507+
fun managed_realmModel() {
508+
realm.executeTransaction {
509+
val realmAnyObject = realm.createObject<RealmAnyNotIndexed>()
510+
val realmAny = RealmAny.valueOf(realmAnyObject)
511+
512+
assertEquals(RealmAnyNotIndexed::class.java, realmAny.valueClass)
513+
}
514+
}
515+
506516
@Test
507517
fun managed_nullRealmAny() {
508518
realm.executeTransaction {

0 commit comments

Comments
 (0)