-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Embedded Objects #6730
Conversation
Any thoughts on calling the one link version |
Yes, I did consider it. Arguably it would be more correct, but it does introduce one more concept to learn and it makes it more difficult for us to document how to use backlinks in general. Also, the names would be so close together that I could see some having doubts about which one to use or hit the wrong one by accident when autocompleting. Also, since the syntax is basically just a shorthand for calling It was sort of the same reasoning for choosing |
# Conflicts: # CHANGELOG.md
@@ -5,7 +5,7 @@ | |||
* Removed Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partionKey()` has been added as a replacement. Read more [here](XXX). | |||
|
|||
### Enhancements | |||
* None. | |||
* Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://realm.io/docs/java/latest/#embedded-objects). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the the
@@ -43,7 +43,7 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible | |||
* `RealmResults.asJSON()` is no longer `@Beta`. | |||
* Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. | |||
* Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. | |||
* Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://realm.io/docs/java/latest/#embedded-objects). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) | |||
* `RealmResults.asJSON()` is no longer `@Beta` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's already there 3 lines above I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, there are some merge mistakes that needs to be cleaned up
# Conflicts: # realm/build.gradle
# Conflicts: # CHANGELOG.md # realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java # realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java
# Conflicts: # CHANGELOG.md # realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave it a try. Quite a big lump with lots of areas I have not touched yet. Besides the comments, maybe put a note on the 'Dynamic realm constraints' in some more obvious place.
realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt
Outdated
Show resolved
Hide resolved
realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt
Outdated
Show resolved
Hide resolved
realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt
Outdated
Show resolved
Hide resolved
realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt
Show resolved
Hide resolved
realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt
Outdated
Show resolved
Hide resolved
realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
Outdated
Show resolved
Hide resolved
realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
Outdated
Show resolved
Hide resolved
realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
Outdated
Show resolved
Hide resolved
realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
Outdated
Show resolved
Hide resolved
@@ -0,0 +1,871 @@ | |||
package io.realm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also add the generated proxy for the EmbeddedClass
for reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks massive but overall good. I tried not to get too caught into the business details, so mostly nitpicking from my part.
realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt
Outdated
Show resolved
Hide resolved
@@ -128,12 +128,13 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat | |||
return type != "io.realm.DynamicRealmObject" && !type.endsWith(".RealmObject") && !type.endsWith("RealmProxy") | |||
} | |||
|
|||
var embedded: Boolean = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a lot immutable data coming out of classType
. I'm wondering if we could somehow avoid using var
s for things like this and rely on initialisation for assigning values instead. It feels like we are adding state to this class unnecessarily. Another example from this class is the contains*
booleans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably an architectural problem. Right now ClassMetaData
is responsible for both parsing AND storing state. It would probably help if we could separate this a bit. The issue is that there is a lot of state and it is interdependent, so not sure how much it would help if we split it. We would still need to track a lot of intermediate state somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like it could escalate quickly if these changes are done here and now. I don't think it's super urgent so we can push it for a later time. I'm very wary when it comes to using mutable properties in Kotlin nonetheless. If not managed correctly they become a timebomb.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at the rest of this class there are tons of var as well. That doesn't mean your point is invalid, but I wonder how you would handle the case of gathering ~10+ variables across 100's of line of code before you send them to a constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see e.g. internalClassName
is a latent var. It takes its value from realmClassAnnotation
or moduleClassNameFormatter
(it's a bit more cumbersome to reason in case the value comes from the latter, but still, it's the same rationale as it could be passed via constructor/a builder from where the class metadata is created in RealmProcessor
). In turn, the local realmClassAnnotation
variable is immutable and takes its value from classType
which comes from the constructor as immutable too. All these things considered, there is no excuse to make internalClassName
a mutable property.
It is true that you might end up having a gazillion properties in your constructor, to which I would strongly object too. You could use a builder to handle that, but that adds a ton of boilerplate. I think the best solution is to encapsulate all these immutable properties behind a data structure/class. This way you would at least keep the clutter away from ClassMetaData
and those properties will be easily accessible via val
s with backing properties from ClassMetaData
, e.g.
class ClassMetaData(private val myMetadaContainer: MetaDataContainer) {
private val internalClassName: String
get() = myMetadaContainer.internalClassName
I don't think there's a silver bullet here in any case but makes the solution more robust IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no doubt there are things we can do to improve this class. You mentioned some of them, but I would rather not do it in this PR since it is too big already. I created #6891 to track it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, seems too much for the scope of this PR.
.../realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt
Outdated
Show resolved
Hide resolved
.../realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt
Outdated
Show resolved
Hide resolved
realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt
Outdated
Show resolved
Hide resolved
|
||
@PrimaryKey | ||
var id: String? = null | ||
var children: RealmList<EmbeddedSimpleChild> = RealmList() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's a test, but avoid var
unless you're updating the value of children
with a different list instance - remember you can still add elements to the list even if its property is immutable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't support final fields in the annotation processor. Probably mostly an oversight which is a lot more annoying in Kotlin. I created #6892. I suspect it would be a fairly trivial fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know, I had no idea.
var middleNode: EmbeddedTreeNode? = null | ||
var leafNode: EmbeddedTreeLeaf? = null | ||
var middleNodeList: RealmList<EmbeddedTreeNode> = RealmList() | ||
var leafNodeList: RealmList<EmbeddedTreeLeaf> = RealmList() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to have an individual leafNode
while having a leafNodeList
? Also, no var
for the lists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have both because it is different code paths in the generated code. I agree from a modeling perspective it looks weird.
For the var
see above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it 👍
...alm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt
Show resolved
Hide resolved
Co-authored-by: Claus Rørbech <claus.rorbech@gmail.com>
Hi, guys! Lets say, that I have parent class and child class, which connects to parent object by id:
Experimentally I found, that I should:
Am I right? |
@savelyevdm Thanks for trying out this new feature! Yes you've discovered the caveats, the embedded object can only have one parent and no primary key. For Migrations, In Java I believe you can just call RealmObjectSchema.setEmbedded(bool) as part of the migration. setEmbedded() checks the constrains of no primary keys and no more than one parent Let us know how it works for you |
Thanx for the fast reply! As my |
@savelyevdm Gotcha - is no primary key a blocker for you? Do you need a primary key for this object? |
Hi guys! let embeddedObj = MigrationObject()
embeddedObj["value"] = "10"
embeddedObj["trend"] = 1
parentObj["emb"] = embeddedObj and: let embeddedObj = migration.create(EmbeddedObj.className())
embeddedObj["value"] = "10"
embeddedObj["trend"] = 1
parentObj["emb"] = embeddedObj but both attempts failed. Thank you! |
Yes, you can create embedded objects during an migration, but you need to set the embedded flag on the class If that doesn't work. Can you create a new issue with the code you tried so far? |
Is there any guidance in the docs on how to migrate a collection to use embedded objects? Spent an hour looking through the documentation and could not find any guidance. The RealmObjectSchema class mentioned here does not seem to exist in the Swift library? I need to do this migration on both iOS and Android in order to be able to start syncing to MongoDb Realm |
@cmelchior What is the corresponding function for RealmObjectSchema.setEmbedded(true) in swift. I am trying to migrate my old data model from regular objects to embedded objects. Is this possible? |
Closes #6713
This PR adds support for "Embedded Objects". For non-synced Realms, the only benefit is that it can be be used to implement cascading deletes in some scenarios. For synced Realms it also describes how the data is serialized once it reaches a MongoDB server.
Semantics
Embedded objects have slightly different semantics than normal objects:
null
will also delete the embedded object.@PrimaryKey
is not supported on embedded objects.API
TODO
copyToRealm
insert
TODO for other PR's
@LinkingObject
configs. Especially in Kotlin.and
insertOrUpdate` with lists. All these variants use different compiled code.createFromJson
-variants.RealmResults.setObject(...)
+ TestscreateEmbeddedObject
not checking if the parent object has the correct typecopyToRealm
andinsert
are implemented using different strategies. The one used byinsert
seems much simpler, so would probably be helpful to refactor howcopyToRealm
is implemented.