Skip to content

Commit

Permalink
POC exploring a potential projections API
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Melchior committed Oct 10, 2023
1 parent 0cc98c0 commit d5d721a
Show file tree
Hide file tree
Showing 18 changed files with 414 additions and 0 deletions.
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

0 comments on commit d5d721a

Please sign in to comment.