Skip to content
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

[POC] Experiments with relaxed schemas #1810

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 @@ -200,6 +200,7 @@ expect object RealmInterop {
fun realm_config_set_automatic_backlink_handling(config: RealmConfigurationPointer, enabled: Boolean)
fun realm_config_set_data_initialization_function(config: RealmConfigurationPointer, callback: DataInitializationCallback)
fun realm_config_set_in_memory(config: RealmConfigurationPointer, inMemory: Boolean)
fun realm_config_set_relaxed_schema(config: RealmConfigurationPointer, relaxedSchema: Boolean)
fun realm_schema_validate(schema: RealmSchemaPointer, mode: SchemaValidationMode): Boolean

fun realm_create_scheduler(): RealmSchedulerPointer
Expand Down Expand Up @@ -299,15 +300,26 @@ expect object RealmInterop {
fun realm_get_col_key(realm: RealmPointer, classKey: ClassKey, col: String): PropertyKey

fun MemAllocator.realm_get_value(obj: RealmObjectPointer, key: PropertyKey): RealmValue
fun MemAllocator.realm_get_value_by_name(obj: RealmObjectPointer, name: String): RealmValue
fun realm_set_value(
obj: RealmObjectPointer,
key: PropertyKey,
value: RealmValue,
isDefault: Boolean
)
fun realm_set_value_by_name(
obj: RealmObjectPointer,
name: String,
value: RealmValue,
)
fun realm_has_property(obj: RealmObjectPointer, name: String): Boolean
fun realm_get_additional_properties(obj: RealmObjectPointer): List<String>
fun realm_erase_property(obj: RealmObjectPointer, key: String): Boolean
fun realm_set_embedded(obj: RealmObjectPointer, key: PropertyKey): RealmObjectPointer
fun realm_set_list(obj: RealmObjectPointer, key: PropertyKey): RealmListPointer
fun realm_set_list_by_name(obj: RealmObjectPointer, propertyName: String): RealmListPointer
fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer
fun realm_set_dictionary_by_name(obj: RealmObjectPointer, propertyName: String): RealmMapPointer
fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long)
fun <T> realm_object_get_parent(
obj: RealmObjectPointer,
Expand All @@ -316,6 +328,7 @@ expect object RealmInterop {

// list
fun realm_get_list(obj: RealmObjectPointer, key: PropertyKey): RealmListPointer
fun realm_get_list_by_name(obj: RealmObjectPointer, propertyName: String): RealmListPointer
fun realm_get_backlinks(obj: RealmObjectPointer, sourceClassKey: ClassKey, sourcePropertyKey: PropertyKey): RealmResultsPointer
fun realm_list_size(list: RealmListPointer): Long
fun MemAllocator.realm_list_get(list: RealmListPointer, index: Long): RealmValue
Expand Down Expand Up @@ -354,6 +367,7 @@ expect object RealmInterop {

// dictionary
fun realm_get_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer
fun realm_get_dictionary_by_name(obj: RealmObjectPointer, propertyName: String): RealmMapPointer
fun realm_dictionary_clear(dictionary: RealmMapPointer)
fun realm_dictionary_size(dictionary: RealmMapPointer): Long
fun realm_dictionary_to_results(dictionary: RealmMapPointer): RealmResultsPointer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@ expect enum class WebsocketErrorCode : CodeDescription {
RLM_ERR_WEBSOCKET_UNAUTHORIZED,
RLM_ERR_WEBSOCKET_FORBIDDEN,
RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY,
RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD,
RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW,
RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH,
RLM_ERR_WEBSOCKET_RESOLVE_FAILED,
RLM_ERR_WEBSOCKET_CONNECTION_FAILED,
RLM_ERR_WEBSOCKET_READ_ERROR,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@
realmc.realm_config_set_in_memory(config.cptr(), inMemory)
}

actual fun realm_config_set_relaxed_schema(config: RealmConfigurationPointer, relaxedSchema: Boolean) {
realmc.realm_config_set_flexible_schema(config.cptr(), relaxedSchema)
}

actual fun realm_create_scheduler(): RealmSchedulerPointer =
LongPointerWrapper(realmc.realm_create_generic_scheduler())

Expand Down Expand Up @@ -489,6 +493,15 @@
return RealmValue(struct)
}

actual fun MemAllocator.realm_get_value_by_name(
obj: RealmObjectPointer,
name: String,
): RealmValue {
val struct = allocRealmValueT()
realmc.realm_get_value_by_name((obj as LongPointerWrapper).ptr, name, struct)
return RealmValue(struct)
}

actual fun realm_set_value(
obj: RealmObjectPointer,
key: PropertyKey,
Expand All @@ -498,6 +511,29 @@
realmc.realm_set_value(obj.cptr(), key.key, value.value, isDefault)
}

actual fun realm_set_value_by_name(
obj: RealmObjectPointer,
name: String,
value: RealmValue,
) {
realmc.realm_set_value_by_name(obj.cptr(), name, value.value)
}

actual fun realm_has_property(obj: RealmObjectPointer, name: String): Boolean {
val found = BooleanArray(1)
realmc.realm_has_property(obj.cptr(), name, found)
return found[0]
}

actual fun realm_get_additional_properties(obj: RealmObjectPointer): List<String> {
@Suppress("UNCHECKED_CAST")
val properties = realmc.realm_get_additional_properties_helper(obj.cptr()) as Array<String>

Check failure on line 530 in packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt

View workflow job for this annotation

GitHub Actions / static-analysis / ktlint

Unnecessary long whitespace (no-multi-spaces)
return properties.asList()
}
actual fun realm_erase_property(obj: RealmObjectPointer, key: String): Boolean {
return realmc.realm_erase_additional_property(obj.cptr(), key)
}

actual fun realm_set_embedded(obj: RealmObjectPointer, key: PropertyKey): RealmObjectPointer {
return LongPointerWrapper(realmc.realm_set_embedded(obj.cptr(), key.key))
}
Expand All @@ -506,10 +542,18 @@
realmc.realm_set_list(obj.cptr(), key.key)
return realm_get_list(obj, key)
}
actual fun realm_set_list_by_name(obj: RealmObjectPointer, propertyName: String): RealmListPointer {
realmc.realm_set_list_by_name(obj.cptr(), propertyName)
return realm_get_list_by_name(obj, propertyName)
}
actual fun realm_set_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer {
realmc.realm_set_dictionary(obj.cptr(), key.key)
return realm_get_dictionary(obj, key)
}
actual fun realm_set_dictionary_by_name(obj: RealmObjectPointer, propertyName: String): RealmMapPointer {
realmc.realm_set_dictionary_by_name(obj.cptr(), propertyName)
return realm_get_dictionary_by_name(obj, propertyName)
}

actual fun realm_object_add_int(obj: RealmObjectPointer, key: PropertyKey, value: Long) {
realmc.realm_object_add_int(obj.cptr(), key.key, value)
Expand Down Expand Up @@ -542,6 +586,14 @@
)
)
}
actual fun realm_get_list_by_name(obj: RealmObjectPointer, propertyName: String): RealmListPointer {
return LongPointerWrapper(
realmc.realm_get_list_by_name(
(obj as LongPointerWrapper).ptr,
propertyName
)
)
}

actual fun realm_get_backlinks(obj: RealmObjectPointer, sourceClassKey: ClassKey, sourcePropertyKey: PropertyKey): RealmResultsPointer {
return LongPointerWrapper(
Expand Down Expand Up @@ -731,6 +783,14 @@
return LongPointerWrapper(ptr)
}

actual fun realm_get_dictionary_by_name(
obj: RealmObjectPointer,
propertyName: String
): RealmMapPointer {
val ptr = realmc.realm_get_dictionary_by_name(obj.cptr(), propertyName)
return LongPointerWrapper(ptr)
}

actual fun realm_dictionary_clear(dictionary: RealmMapPointer) {
realmc.realm_dictionary_clear(dictionary.cptr())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,6 @@ actual enum class WebsocketErrorCode(
RLM_ERR_WEBSOCKET_UNAUTHORIZED("Unauthorized", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_UNAUTHORIZED),
RLM_ERR_WEBSOCKET_FORBIDDEN("Forbidden", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_FORBIDDEN),
RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY("MovedPermanently", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY),
RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD("ClientTooOld", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD),
RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW("ClientTooNew", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW),
RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH("ProtocolMismatch", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH),

RLM_ERR_WEBSOCKET_RESOLVE_FAILED("ResolveFailed", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_RESOLVE_FAILED),
RLM_ERR_WEBSOCKET_CONNECTION_FAILED("ConnectionFailed", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_CONNECTION_FAILED),
Expand Down
2 changes: 1 addition & 1 deletion packages/external/core
Submodule core updated 180 files
2 changes: 1 addition & 1 deletion packages/jni-swig-stub/realm.i
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*;
bool* erased, bool* out_erased, bool* did_refresh, bool* did_run,
bool* found, bool* out_collection_was_cleared, bool* did_compact,
bool* collection_was_cleared, bool* out_collection_was_deleted,
bool* out_was_deleted};
bool* out_was_deleted, bool* out_has_property };

// uint64_t output parameter for realm_get_num_versions
%apply int64_t* OUTPUT { uint64_t* out_versions_count };
Expand Down
22 changes: 22 additions & 0 deletions packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1411,3 +1411,25 @@ jobjectArray realm_get_log_category_names() {

return array;
}

jobjectArray realm_get_additional_properties_helper(realm_object_t* obj) {
JNIEnv* env = get_env(true);

size_t count = 0;
realm_get_additional_properties(obj, nullptr, 0xffffff, &count);

const char** properties = new const char*[count];
realm_get_additional_properties(obj, properties, count, &count);
// FIXME Guard count != count

auto array = env->NewObjectArray(count, JavaClassGlobalDef::java_lang_string(), nullptr);

for(size_t i = 0; i < count; i++) {
jstring string = env->NewStringUTF(properties[i]);
env->SetObjectArrayElement(array, i, string);
}

delete[] properties;

return array;
}
1 change: 1 addition & 0 deletions packages/jni-swig-stub/src/main/jni/realm_api_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,6 @@ bool realm_sync_websocket_message(int64_t observer_ptr, jbyteArray data, size_t
void realm_sync_websocket_closed(int64_t observer_ptr, bool was_clean, int error_code, const char* reason);

jobjectArray realm_get_log_category_names();
jobjectArray realm_get_additional_properties_helper(realm_object_t* obj);

#endif //TEST_REALM_API_HELPERS_H
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public interface BaseRealm : Versioned {
* @return the schema of the realm.
*/
public fun schema(): RealmSchema
// public fun schema(fullSchema: Boolean = false): RealmSchema

/**
* Returns the schema version of the realm.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ public interface Configuration {
*/
public val initialRealmFileConfiguration: InitialRealmFileConfiguration?

// FIXME DOCS
public val relaxedSchema: Boolean

/**
* Base class for configuration builders that holds properties available to both
* [RealmConfiguration] and [SyncConfiguration].
Expand Down Expand Up @@ -209,6 +212,7 @@ public interface Configuration {
protected var initialDataCallback: InitialDataCallback? = null
protected var inMemory: Boolean = false
protected var initialRealmFileConfiguration: InitialRealmFileConfiguration? = null
protected var relaxedSchema: Boolean = false

/**
* Sets the filename of the realm file.
Expand Down Expand Up @@ -399,6 +403,12 @@ public interface Configuration {
return this as S
}

// FIXME Docs
public fun relaxedSchema(relaxedSchema: Boolean): S {
this.relaxedSchema = relaxedSchema
return this as S
}

protected fun validateEncryptionKey(encryptionKey: ByteArray): ByteArray {
if (encryptionKey.size != Realm.ENCRYPTION_KEY_LENGTH) {
throw IllegalArgumentException("The provided key must be ${Realm.ENCRYPTION_KEY_LENGTH} bytes. The provided key was ${encryptionKey.size} bytes.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ public interface RealmConfiguration : Configuration {
initialDataCallback,
inMemory,
initialRealmFileConfiguration,
relaxedSchema,
realmLogger
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 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.annotations

@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
public annotation class DynamicAPI
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 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.dynamic.getgeneric

import io.realm.kotlin.annotations.DynamicAPI
import io.realm.kotlin.internal.RealmObjectHelper
import io.realm.kotlin.internal.runIfManagedOrThrow
import io.realm.kotlin.types.BaseRealmObject
import kotlin.reflect.KType
import kotlin.reflect.typeOf

// Proposal 1 of generic/dynamic API
// This seems like the most promising
@DynamicAPI
public inline operator fun <reified T> BaseRealmObject.get(propertyName: String): T {
return get(propertyName, typeOf<T>())
}

@DynamicAPI
public operator fun <T> BaseRealmObject.set(name: String, value: T) {
return this.runIfManagedOrThrow {
RealmObjectHelper.setValueByName(this, name, value)
}
}

@PublishedApi
internal fun <T> BaseRealmObject.get(propertyName: String, type: KType): T {
return this.runIfManagedOrThrow {
RealmObjectHelper.dynamicGetFromKType(
obj = this,
propertyName = propertyName,
type = type
)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2024 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.dynamic.getinterface

import io.realm.kotlin.internal.RealmObjectHelper
import io.realm.kotlin.internal.runIfManagedOrThrow
import io.realm.kotlin.types.BaseRealmObject
import kotlin.reflect.KType
import kotlin.reflect.typeOf

// Proposal 2 of generic/dynamic API
// Hiding things a bit by only exposing it on a specific type. This is a bit cumbersome as you
// cannot use a single entry point to get into the dynamic domain (requires to use .relaxed
// everywhere)
public interface RelaxedRealmObject

// FIXME Naming
// - Extras?
public val BaseRealmObject.relaxed: RelaxedRealmObject
get() = this as RelaxedRealmObject


public inline operator fun <reified T> RelaxedRealmObject.get(propertyName: String): T {
return get(propertyName, typeOf<T>())
}

public operator fun <T> RelaxedRealmObject.set(name: String, value: T) {
return (this as BaseRealmObject).runIfManagedOrThrow {
RealmObjectHelper.setValueByName(this, name, value)
}
}

@PublishedApi
internal fun <T> RelaxedRealmObject.get(propertyName: String, type: KType): T {
return (this as BaseRealmObject).runIfManagedOrThrow {
RealmObjectHelper.dynamicGetFromKType(
obj = this,
propertyName = propertyName,
type = type
)
}
}
Loading
Loading