Skip to content

[POC] Projections API #1539

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import io.realm.kotlin.types.RealmDictionaryMutableEntry
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.RealmSet
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

/**
* Instantiates an **unmanaged** [RealmDictionary] from a variable number of [Pair]s of [String]
Expand Down Expand Up @@ -112,3 +114,10 @@ public fun <T : BaseRealmObject> RealmDictionary<T?>.query(
} else {
throw IllegalArgumentException("Unmanaged dictionary values cannot be queried.")
}

/**
* TODO Docs
*/
public inline fun <reified O : TypedRealmObject, T: Any> RealmDictionary<O?>.projectInto(target: KClass<T>): Map<String, T> {
TODO()
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.realm.kotlin.types.RealmDictionary
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmSet
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

/**
* Instantiates an **unmanaged** [RealmList].
Expand Down Expand Up @@ -72,3 +73,10 @@ public fun <T : BaseRealmObject> RealmList<T>.query(
} else {
throw IllegalArgumentException("Unmanaged list cannot be queried")
}

/**
* TODO Docs
*/
public inline fun <reified O : TypedRealmObject, T: Any> RealmList<O>.projectInto(target: KClass<T>): List<T> {
TODO()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package io.realm.kotlin.ext

import io.realm.kotlin.TypedRealm
import io.realm.kotlin.internal.getRealm
import io.realm.kotlin.internal.realmObjectReference
import io.realm.kotlin.internal.realmProjectionCompanionOrNull
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmProjectionFactory
import io.realm.kotlin.types.RealmSet
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

/**
* Makes an unmanaged in-memory copy of the elements in a [RealmResults]. This is a deep copy
Expand All @@ -20,3 +25,22 @@ public inline fun <reified T : TypedRealmObject> RealmResults<T>.copyFromRealm(d
// the Realm is closed, so all error handling is done inside the `getRealm` method.
return this.getRealm<TypedRealm>().copyFromRealm(this, depth)
}

/**
* TODO Docs
*/
public fun <O : TypedRealmObject, T: Any> RealmResults<O>.projectInto(target: KClass<T>): List<T> {
// TODO Should this also automatically release the pointer for the results object after finishing the
// projection? I would be leaning towards yes, as I suspect this is primary use case. But if
// enough use cases show up for keeping the backing object around, we can add a
// `releaseRealmObjectAfterUse` boolean with a default value of `true` to this this method.
val projectionFactory: RealmProjectionFactory<O, T>? = target.realmProjectionCompanionOrNull()
return projectionFactory?.let { factory ->
this.map { obj: O ->
projectionFactory.createProjection(obj).also {
obj.realmObjectReference?.objectPointer?.release()
}
}
} ?: throw IllegalStateException("TODO")
}

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

/*
* Copyright 2022 Realm Inc.
*
Expand Down Expand Up @@ -29,6 +30,8 @@ import io.realm.kotlin.types.RealmDictionary
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.RealmSet
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

/**
* Instantiates an **unmanaged** [RealmSet].
Expand Down Expand Up @@ -70,3 +73,10 @@ public fun <T : BaseRealmObject> RealmSet<T>.query(
} else {
throw IllegalArgumentException("Unmanaged set cannot be queried")
}

/**
* TODO Docs
*/
public inline fun <reified O : TypedRealmObject, T: Any> RealmSet<O>.projectInto(target: KClass<T>): Set<T> {
TODO()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ package io.realm.kotlin.ext

import io.realm.kotlin.TypedRealm
import io.realm.kotlin.internal.getRealm
import io.realm.kotlin.internal.realmObjectCompanionOrNull
import io.realm.kotlin.internal.realmProjectionCompanionOrNull
import io.realm.kotlin.types.RealmDictionary
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmSet
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

/**
* Makes an unmanaged in-memory copy of an already persisted [io.realm.kotlin.types.RealmObject].
Expand All @@ -37,3 +40,15 @@ public inline fun <reified T : TypedRealmObject> T.copyFromRealm(depth: UInt = U
?.copyFromRealm(this, depth)
?: throw IllegalArgumentException("This object is unmanaged. Only managed objects can be copied.")
}

/**
* TODO Docs
*/
public inline fun <reified O : TypedRealmObject, reified T: Any> O.projectInto(target: KClass<T>): T {
// TODO Should this also automatically release the pointer for the object after finishing the
// projection? I would be leaning towards yes, as I suspect this is primary use case. But if
// enough use cases show up for keeping the backing object around, we can add a
// `releaseRealmObjectAfterUse` boolean with a default value of `true` to this this method.
return T::class.realmProjectionCompanionOrNull<O, T>()?.createProjection(this)
?: throw IllegalStateException("TODO")
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import io.realm.kotlin.internal.interop.RealmInterop
import io.realm.kotlin.internal.interop.RealmObjectPointer
import io.realm.kotlin.internal.platform.realmObjectCompanionOrNull
import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow
import io.realm.kotlin.internal.platform.realmProjectionCompanionOrNull
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.RealmProjectionFactory
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

internal fun <T : BaseRealmObject> RealmObjectInternal.manage(
Expand Down Expand Up @@ -117,6 +120,11 @@ internal inline fun <reified T : BaseRealmObject> KClass<T>.realmObjectCompanion
return realmObjectCompanionOrThrow(this)
}

public inline fun <O: TypedRealmObject, T: Any> KClass<T>.realmProjectionCompanionOrNull(): RealmProjectionFactory<O, T>? {
return realmProjectionCompanionOrNull(this)
}


/**
* Convenience property to get easy access to the RealmObjectReference of a BaseRealmObject.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package io.realm.kotlin.internal.platform

import io.realm.kotlin.internal.RealmObjectCompanion
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.RealmProjectionFactory
import io.realm.kotlin.types.TypedRealmObject
import kotlin.reflect.KClass

/**
Expand All @@ -31,3 +33,8 @@ internal expect fun <T : Any> realmObjectCompanionOrNull(clazz: KClass<T>): Real
* Returns the [RealmObjectCompanion] associated with a given [BaseRealmObject]'s [KClass].
*/
internal expect fun <T : BaseRealmObject> realmObjectCompanionOrThrow(clazz: KClass<T>): RealmObjectCompanion

/**
* TODO
*/
public expect fun <O: TypedRealmObject, T: Any> realmProjectionCompanionOrNull(clazz: KClass<T>): RealmProjectionFactory<O, T>?
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import io.realm.kotlin.internal.interop.RealmQueryPointer
import io.realm.kotlin.internal.interop.RealmResultsPointer
import io.realm.kotlin.internal.interop.inputScope
import io.realm.kotlin.internal.schema.ClassMetadata
import io.realm.kotlin.notifications.ProjectionsChange
import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults
Expand Down Expand Up @@ -85,6 +86,10 @@ internal class ObjectQuery<E : BaseRealmObject> constructor(
override fun find(): RealmResults<E> =
RealmResultsImpl(realmReference, resultsPointer, classKey, clazz, mediator)

override fun <T : Any> find(projection: KClass<T>): List<T> {
TODO("Not yet implemented")
}

override fun query(filter: String, vararg arguments: Any?): RealmQuery<E> =
inputScope {
val appendedQuery = RealmInterop.realm_query_append_query(
Expand Down Expand Up @@ -175,6 +180,10 @@ internal class ObjectQuery<E : BaseRealmObject> constructor(
.registerObserver(this)
}

override fun <T : Any> asFlow(projection: KClass<T>): ProjectionsChange<T> {
TODO("Not yet implemented")
}

override fun delete() {
// TODO C-API doesn't implement realm_query_delete_all so just fetch the result and delete
// that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import io.realm.kotlin.internal.interop.RealmQueryPointer
import io.realm.kotlin.internal.realmObjectReference
import io.realm.kotlin.internal.runIfManaged
import io.realm.kotlin.internal.toRealmObject
import io.realm.kotlin.notifications.ProjectionChange
import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.notifications.SingleQueryChange
import io.realm.kotlin.notifications.internal.DeletedObjectImpl
Expand Down Expand Up @@ -45,6 +46,10 @@ internal class SingleQuery<E : BaseRealmObject> constructor(
)
}

override fun <T : Any> find(projection: KClass<T>): List<T> {
TODO("Not yet implemented")
}

/**
* Because Core does not support subscribing to the head element of a query this feature
* must be shimmed.
Expand Down Expand Up @@ -92,6 +97,10 @@ internal class SingleQuery<E : BaseRealmObject> constructor(
}
}

override fun <T : Any> asFlow(projection: KClass<T>): ProjectionChange<T> {
TODO("Not yet implemented")
}

/**
* Thaw the frozen query result, turning it back into a live, thread-confined RealmResults.
* The results object is then used to fetch the object with index 0, which can be `null`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2021 Realm Inc.
*
* 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 io.realm.kotlin.notifications

public sealed interface SingleProjectionQueryChange<O : Any> {
/**
* Returns the newest state of object being observed. `null` is returned if there is no object to
* observe.
*/
public val obj: O?
}

/**
* TODO Docs
*/
public interface PendingProjection<O : Any> : SingleProjectionQueryChange<O>

/**
* TODO Docs
* TODO Annoying to have both `ProjectionsChanges` and `ProjectionChange`...other name for one of them?
*/
public sealed interface ProjectionChange<O : Any> : SingleProjectionQueryChange<O> {
/**
* Returns the newest state of object being observed. `null` is returned if the object
* has been deleted.
*/
override val obj: O?
}

/**
* TODO Docs
*/
public interface InitialProjection<O : Any> : SingleProjectionQueryChange<O> {
override val obj: O
}

/**
* TODO Docs
* TODO Can we actually track all changed fields?
*/
public interface UpdatedProjection<O : Any> : SingleProjectionQueryChange<O> {
override val obj: O

/**
* Returns the names of properties that has changed.
*/
public val changedFields: Array<String>

/**
* Checks if a given field has been changed.
*
* @param fieldName to be checked if its value has been changed.
* @return `true` if the field has been changed. It returns `false` the field cannot be found
* or the field hasn't been changed.
*/
public fun isFieldChanged(fieldName: String): Boolean {
return changedFields.firstOrNull { it == fieldName } != null
}
}

/**
* TODO Docs
*/
public interface DeletedProjection<O : Any> : ProjectionChange<O>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2022 Realm Inc.
*
* 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 io.realm.kotlin.notifications

/**
* TODO Docs (copy from ResultsChange)
*/
public sealed interface ProjectionsChange<T : Any> {
public val list: List<T>
}

/**
* TODO Docs (copy from ResultsChange)
*/
public interface InitialProjections<T : Any> : ProjectionsChange<T>

/**
* TODO Docs (copy from ResultsChange)
*/
public interface UpdatedProjections<T : Any> : ProjectionsChange<T>, ListChangeSet
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ import io.realm.kotlin.Deleteable
import io.realm.kotlin.MutableRealm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.ListChange
import io.realm.kotlin.notifications.ProjectionsChange
import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.types.BaseRealmObject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlin.reflect.KClass

/**
* Query returning [RealmResults].
Expand Down Expand Up @@ -63,4 +66,15 @@ public interface RealmElementQuery<T : BaseRealmObject> : Deleteable {
* @return a flow representing changes to the [RealmResults] resulting from running this query.
*/
public fun asFlow(): Flow<ResultsChange<T>>

/**
* TODO
*/
public fun <T: Any> find(projection: KClass<T>): List<T>

/**
* TODO
*/
public fun <T: Any> asFlow(projection: KClass<T>): ProjectionsChange<T>

}
Loading