diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index e012f627d4..e39d20bedb 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -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 @@ -299,12 +300,21 @@ 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 + 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_dictionary(obj: RealmObjectPointer, key: PropertyKey): RealmMapPointer diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index facb5d5814..02e6b623f6 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -216,6 +216,10 @@ actual object RealmInterop { 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()) @@ -489,6 +493,15 @@ actual object RealmInterop { 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, @@ -498,6 +511,29 @@ actual object RealmInterop { 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 { + @Suppress("UNCHECKED_CAST") + val properties = realmc.realm_get_additional_properties_helper(obj.cptr()) as Array + return properties.asList() + } + actual fun realm_erase_property(obj: RealmObjectPointer, key: String): Boolean { + return realmc.realm_erase_property(obj.cptr(), key) + } + actual fun realm_set_embedded(obj: RealmObjectPointer, key: PropertyKey): RealmObjectPointer { return LongPointerWrapper(realmc.realm_set_embedded(obj.cptr(), key.key)) } diff --git a/packages/external/core b/packages/external/core index c280bdb175..f49f8eed1a 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit c280bdb17522323d5c30dc32a2b9efc9dc80ca3b +Subproject commit f49f8eed1a12adda400390cd4f78bdb2d269580a diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index 96593c782c..28d2f7d543 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -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 }; diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index a112cf6d1a..69103e4603 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -1403,3 +1403,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; +} diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index a6bf096d4e..bc70b2fedf 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -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 diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/BaseRealm.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/BaseRealm.kt index 76d5d45fcf..366aee0572 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/BaseRealm.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/BaseRealm.kt @@ -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. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt index cd261b23bf..94e3a20c49 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt @@ -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]. @@ -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. @@ -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.") diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt index 7ef592841c..a23300d755 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt @@ -181,6 +181,7 @@ public interface RealmConfiguration : Configuration { initialDataCallback, inMemory, initialRealmFileConfiguration, + relaxedSchema, realmLogger ) } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt index 6b2d3a234a..6e72749fd7 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt @@ -65,6 +65,7 @@ public open class ConfigurationImpl( override val isFlexibleSyncConfiguration: Boolean, inMemory: Boolean, initialRealmFileConfiguration: InitialRealmFileConfiguration?, + relaxedSchema: Boolean, override val logger: ContextLogger ) : InternalConfiguration { @@ -96,6 +97,7 @@ public open class ConfigurationImpl( final override val initialDataCallback: InitialDataCallback? final override val inMemory: Boolean final override val initialRealmFileConfiguration: InitialRealmFileConfiguration? + final override val relaxedSchema: Boolean override fun createNativeConfiguration(): RealmConfigurationPointer { val nativeConfig: RealmConfigurationPointer = RealmInterop.realm_config_new() @@ -141,6 +143,7 @@ public open class ConfigurationImpl( this.initialDataCallback = initialDataCallback this.inMemory = inMemory this.initialRealmFileConfiguration = initialRealmFileConfiguration + this.relaxedSchema = relaxedSchema // We need to freeze `compactOnLaunchCallback` reference on initial thread for Kotlin Native val compactCallback = compactOnLaunchCallback?.let { callback -> @@ -225,6 +228,8 @@ public open class ConfigurationImpl( RealmInterop.realm_config_set_in_memory(nativeConfig, inMemory) + RealmInterop.realm_config_set_relaxed_schema(nativeConfig, relaxedSchema) + nativeConfig } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt index 4ca59cb4e7..5b252139a8 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt @@ -45,6 +45,7 @@ internal class RealmConfigurationImpl( initialDataCallback: InitialDataCallback?, inMemory: Boolean, initialRealmFileConfiguration: InitialRealmFileConfiguration?, + relaxedSchema: Boolean, logger: ContextLogger ) : ConfigurationImpl( directory, @@ -66,6 +67,7 @@ internal class RealmConfigurationImpl( false, inMemory, initialRealmFileConfiguration, + relaxedSchema, logger ), RealmConfiguration diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt index f21e85de1d..ed321417c5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectHelper.kt @@ -36,6 +36,7 @@ import io.realm.kotlin.internal.interop.PropertyKey import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.RealmInterop.realm_get_value +import io.realm.kotlin.internal.interop.RealmInterop.realm_get_value_by_name import io.realm.kotlin.internal.interop.RealmListPointer import io.realm.kotlin.internal.interop.RealmMapPointer import io.realm.kotlin.internal.interop.RealmObjectInterop @@ -50,6 +51,7 @@ import io.realm.kotlin.internal.schema.ClassMetadata import io.realm.kotlin.internal.schema.PropertyMetadata import io.realm.kotlin.internal.schema.RealmStorageTypeImpl import io.realm.kotlin.internal.schema.realmStorageType +import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.internal.util.Validation.sdkError import io.realm.kotlin.query.RealmResults import io.realm.kotlin.schema.RealmClassKind @@ -70,6 +72,8 @@ import org.mongodb.kbson.Decimal128 import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * This object holds helper methods for the compiler plugin generated methods, providing the @@ -186,6 +190,76 @@ internal object RealmObjectHelper { return setValueByKey(obj, key, value) } + @Suppress("ComplexMethod", "LongMethod") + internal inline fun setValueByName( + obj: RealmObjectReference, + name: String, + value: Any?, + updatePolicy: UpdatePolicy = UpdatePolicy.ALL, + cache: UnmanagedToManagedObjectCache = mutableMapOf() + ) { + // TODO optimize: avoid this by creating the scope in the accessor via the compiler plugin + // See comment in AccessorModifierIrGeneration.modifyAccessor about this. + inputScope { + when (value) { + null -> setValueTransportByName(obj, name, nullTransport()) + is String -> setValueTransportByName(obj, name, stringTransport(value)) + is ByteArray -> setValueTransportByName(obj, name, byteArrayTransport(value)) + is Long -> setValueTransportByName(obj, name, longTransport(value)) + is Boolean -> setValueTransportByName(obj, name, booleanTransport(value)) + is Timestamp -> setValueTransportByName(obj, name, timestampTransport(value)) + is Float -> setValueTransportByName(obj, name, floatTransport(value)) + is Double -> setValueTransportByName(obj, name, doubleTransport(value)) + is Decimal128 -> setValueTransportByName(obj, name, decimal128Transport(value)) + is BsonObjectId -> setValueTransportByName( + obj, + name, + objectIdTransport(value.toByteArray()) + ) + is RealmUUID -> setValueTransportByName(obj, name, uuidTransport(value.bytes)) + is RealmObjectInterop -> setValueTransportByName( + obj, + name, + realmObjectTransport(value) + ) + is MutableRealmInt -> setValueTransportByName(obj, name, longTransport(value.get())) + is RealmAny -> { + realmAnyHandler( + value = value, + primitiveValueAsRealmValueHandler = { realmValue -> + setValueTransportByName( + obj, + name, + realmValue + ) + }, + referenceAsRealmAnyHandler = { realmValue -> + TODO() +// setObjectByName(obj, name, realmValue.asRealmObject(), updatePolicy, cache) + }, + listAsRealmAnyHandler = { realmValue -> + TODO() +// val nativePointer = RealmInterop.realm_set_list(obj.objectPointer, name) +// RealmInterop.realm_list_clear(nativePointer) +// val operator = +// realmAnyListOperator(obj.mediator, obj.owner, nativePointer, false, false) +// operator.insertAll(0, value.asList(), updatePolicy, cache) + }, + dictionaryAsRealmAnyHandler = { realmValue -> + TODO() +// val nativePointer = RealmInterop.realm_set_dictionary(obj.objectPointer, name) +// RealmInterop.realm_dictionary_clear(nativePointer) +// val operator = +// realmAnyMapOperator(obj.mediator, obj.owner, nativePointer, false, false) +// operator.putAll(value.asDictionary(), updatePolicy, cache) + } + ) + } + else -> throw IllegalArgumentException("Unsupported value for transport: $value") + } + } + } + @Suppress("ComplexMethod", "LongMethod") internal inline fun setValueByKey( obj: RealmObjectReference, @@ -321,8 +395,166 @@ internal object RealmObjectHelper { { RealmInterop.realm_get_list(obj.objectPointer, key) } ) { RealmInterop.realm_get_dictionary(obj.objectPointer, key) } } + } + internal inline fun getRealmAnyByString( + obj: RealmObjectReference, + propertyName: String + ): RealmAny? = getterScope { + getRealmValueFromName(obj, propertyName) + ?.let { + realmValueToRealmAny( + it, obj, obj.mediator, obj.owner, + false, + false, + { TODO() // RealmInterop.realm_get_list(obj.objectPointer, key) + }, + { TODO() //RealmInterop.realm_get_dictionary(obj.objectPointer, key) + } + ) + } + } + + internal fun dynamicGetRealmAny( + obj: RealmObjectReference, + propertyName: String, + issueDynamicMutableObject: Boolean = false + ): R = dynamicGetFromKType(obj, propertyName, typeOf(), issueDynamicMutableObject) + + internal fun dynamicGetFromKType( + obj: RealmObjectReference, + propertyName: String, + type: KType, + issueDynamicMutableObject: Boolean = false + ): R { + obj.checkValid() + val collectionType = when { + type.classifier == RealmList::class -> CollectionType.RLM_COLLECTION_TYPE_LIST + type.classifier == RealmSet::class -> CollectionType.RLM_COLLECTION_TYPE_SET + type.classifier == RealmDictionary::class -> CollectionType.RLM_COLLECTION_TYPE_DICTIONARY + else -> CollectionType.RLM_COLLECTION_TYPE_NONE + } + val elementType: KType = if (collectionType != CollectionType.RLM_COLLECTION_TYPE_NONE) { + type.arguments[0].type!! + } else type + + val propertyMetadata = checkPropertyType( + obj, + propertyName, + collectionType, + elementType.classifier as KClass<*>, + elementType.isMarkedNullable + ) + val operatorType = when { + // FIXME Do we want extra properties to require + propertyMetadata == null || + propertyMetadata.type == PropertyType.RLM_PROPERTY_TYPE_MIXED -> + CollectionOperatorType.REALM_ANY + + propertyMetadata.type != PropertyType.RLM_PROPERTY_TYPE_OBJECT -> + CollectionOperatorType.PRIMITIVE + + !obj.owner.schemaMetadata[propertyMetadata.linkTarget]!!.isEmbeddedRealmObject -> + CollectionOperatorType.REALM_OBJECT + + // FIXME Embedded objects are not supported for sets + else -> CollectionOperatorType.EMBEDDED_OBJECT + } + // FIXME get to return collections in mixed + return when (collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> { + return getterScope { +// val transport = when(propertyMetadata) { +// null -> realm_get_value_by_name(obj.objectPointer, propertyName) +// else -> realm_get_value(obj.objectPointer, propertyMetadata.key) +// } + val transport = realm_get_value_by_name(obj.objectPointer, propertyName) + + // Consider moving this dynamic conversion to Converters.kt + val value = when (type.classifier) { + DynamicRealmObject::class, + DynamicMutableRealmObject::class -> realmValueToRealmObject( + transport, + type.classifier as KClass, + obj.mediator, + obj.owner + ) + RealmAny::class -> realmValueToRealmAny( + realmValue = transport, + parent = obj, + mediator = obj.mediator, + owner = obj.owner, + issueDynamicObject = true, + issueDynamicMutableObject = issueDynamicMutableObject, + getListFunction = { +// RealmInterop.realm_get_list( +// obj.objectPointer, +// propertyMetadata.key +// ) + TODO("realm_get_list does not support by name yet") + }, + getDictionaryFunction = { + TODO("realm_get_dictionary does not support by name yet") +// RealmInterop.realm_get_dictionary( +// obj.objectPointer, +// propertyMetadata.key +// ) + } + ) + + else -> when (type.classifier) { + Unit::class -> Unit as R // Special case to prevent get("name") to return errors when return value is not used but inferred to Unit + else -> with(primitiveTypeConverters.getValue(type.classifier as KClass)) { + realmValueToPublic(transport) + } + } + } + value?.let { + @Suppress("UNCHECKED_CAST") + if ((type.classifier as KClass).isInstance(value)) { + value as R + } else { + throw ClassCastException("Retrieving value of type '${(type.classifier as KClass).simpleName}' but was of type '${value::class.simpleName}'") + } + } as R + } + } + CollectionType.RLM_COLLECTION_TYPE_LIST -> { + getListByKey( + obj, + propertyMetadata, + elementType.classifier as KClass, + operatorType, + true, + issueDynamicMutableObject + ) as R + } + CollectionType.RLM_COLLECTION_TYPE_SET -> { + getSetByKey( + obj, + propertyMetadata, + elementType.classifier as KClass, + operatorType, + true, + issueDynamicMutableObject + ) as R + } + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { + getDictionaryByKey( + obj, + propertyMetadata, + elementType.classifier as KClass, + operatorType, + true, + issueDynamicMutableObject + ) as R + } + else -> sdkError("Unknown collection type $collectionType") + } + } + + internal inline fun MemAllocator.getRealmValue( obj: RealmObjectReference, propertyName: String, @@ -342,6 +574,20 @@ internal object RealmObjectHelper { } } + internal inline fun MemAllocator.getRealmValueFromName( + obj: RealmObjectReference, + name: String + ): RealmValue? { + val realmValue = realm_get_value_by_name( + obj.objectPointer, + name + ) + return when (realmValue.isNull()) { + true -> null + false -> realmValue + } + } + // --------------------------------------------------------------------- // End new implementation // --------------------------------------------------------------------- @@ -413,20 +659,44 @@ internal object RealmObjectHelper { return RealmResultsImpl(obj.owner, objects, sourceClassKey, sourceClass, obj.mediator) } + @Suppress("LongParameterList") + internal fun getListByName( + obj: RealmObjectReference, + propertyMetadata: PropertyMetadata?, + elementType: KClass, + operatorType: CollectionOperatorType, + issueDynamicObject: Boolean = false, + issueDynamicMutableObject: Boolean = false + ): ManagedRealmList { + Validation.isType(propertyMetadata) + val listPtr = RealmInterop.realm_get_list(obj.objectPointer, propertyMetadata.key) + val operator = createListOperator( + listPtr, + elementType, + propertyMetadata.linkTarget, + obj.mediator, + obj.owner, + operatorType, + issueDynamicObject, + issueDynamicMutableObject + ) + return ManagedRealmList(obj, listPtr, operator) + } @Suppress("LongParameterList") internal fun getListByKey( obj: RealmObjectReference, - propertyMetadata: PropertyMetadata, + propertyMetadata: PropertyMetadata?, elementType: KClass, operatorType: CollectionOperatorType, issueDynamicObject: Boolean = false, issueDynamicMutableObject: Boolean = false ): ManagedRealmList { + Validation.isType(propertyMetadata) val listPtr = RealmInterop.realm_get_list(obj.objectPointer, propertyMetadata.key) val operator = createListOperator( listPtr, elementType, - propertyMetadata, + propertyMetadata.linkTarget, obj.mediator, obj.owner, operatorType, @@ -440,7 +710,7 @@ internal object RealmObjectHelper { private fun createListOperator( listPtr: RealmListPointer, clazz: KClass, - propertyMetadata: PropertyMetadata, + targetClassName: String, mediator: Mediator, realm: RealmReference, operatorType: CollectionOperatorType, @@ -462,7 +732,7 @@ internal object RealmObjectHelper { issueDynamicMutableObject = issueDynamicMutableObject ) as ListOperator CollectionOperatorType.REALM_OBJECT -> { - val classKey: ClassKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey + val classKey: ClassKey = realm.schemaMetadata.getOrThrow(targetClassName).classKey RealmObjectListOperator( mediator, realm, @@ -472,7 +742,7 @@ internal object RealmObjectHelper { ) as ListOperator } CollectionOperatorType.EMBEDDED_OBJECT -> { - val classKey: ClassKey = realm.schemaMetadata.getOrThrow(propertyMetadata.linkTarget).classKey + val classKey: ClassKey = realm.schemaMetadata.getOrThrow(targetClassName).classKey EmbeddedRealmObjectListOperator( mediator, realm, @@ -506,12 +776,13 @@ internal object RealmObjectHelper { @Suppress("LongParameterList") internal fun getSetByKey( obj: RealmObjectReference, - propertyMetadata: PropertyMetadata, + propertyMetadata: PropertyMetadata?, elementType: KClass, operatorType: CollectionOperatorType, issueDynamicObject: Boolean = false, issueDynamicMutableObject: Boolean = false ): ManagedRealmSet { + Validation.isType(propertyMetadata) val setPtr = RealmInterop.realm_get_set(obj.objectPointer, propertyMetadata.key) val operator = createSetOperator( setPtr, @@ -590,12 +861,13 @@ internal object RealmObjectHelper { @Suppress("LongParameterList") internal fun getDictionaryByKey( obj: RealmObjectReference, - propertyMetadata: PropertyMetadata, + propertyMetadata: PropertyMetadata?, elementType: KClass, operatorType: CollectionOperatorType, issueDynamicObject: Boolean = false, issueDynamicMutableObject: Boolean = false ): ManagedRealmDictionary { + Validation.isType(propertyMetadata) val dictionaryPtr = RealmInterop.realm_get_dictionary(obj.objectPointer, propertyMetadata.key) val operator = createDictionaryOperator( @@ -675,6 +947,19 @@ internal object RealmObjectHelper { RealmInterop.realm_set_value(obj.objectPointer, key, transport, false) } + internal fun setValueTransportByName( + obj: RealmObjectReference, + name: String, + transport: RealmValue, + ) { + // TODO Consider making a RealmValue cinterop type and move the various to_realm_value + // implementations in the various platform RealmInterops here to eliminate + // RealmObjectInterop and make cinterop operate on primitive values and native pointers + // only. This relates to the overall concern of having a generic path for getter/setter + // instead of generating a typed path for each type. + RealmInterop.realm_set_value_by_name(obj.objectPointer, name, transport) + } + @Suppress("unused") // Called from generated code internal inline fun setList( obj: RealmObjectReference, @@ -908,7 +1193,7 @@ internal object RealmObjectHelper { CollectionType.RLM_COLLECTION_TYPE_NONE, clazz, nullable - ) + ) ?: throw IllegalStateException("Cannot access non-data model properties through the dynamic API") return getterScope { val transport = realm_get_value(obj.objectPointer, propertyInfo.key) @@ -960,7 +1245,7 @@ internal object RealmObjectHelper { CollectionType.RLM_COLLECTION_TYPE_LIST, clazz, nullable - ) + ) ?: throw IllegalStateException("Cannot access extra properties through this API") val operatorType = when { propertyMetadata.type == PropertyType.RLM_PROPERTY_TYPE_MIXED -> CollectionOperatorType.REALM_ANY @@ -995,7 +1280,7 @@ internal object RealmObjectHelper { CollectionType.RLM_COLLECTION_TYPE_SET, clazz, nullable - ) + ) ?: throw IllegalStateException("Cannot access non-data model properties through the dynamic API") val operatorType = when { propertyMetadata.type == PropertyType.RLM_PROPERTY_TYPE_MIXED -> CollectionOperatorType.REALM_ANY @@ -1030,7 +1315,7 @@ internal object RealmObjectHelper { CollectionType.RLM_COLLECTION_TYPE_DICTIONARY, clazz, nullable - ) + ) ?: throw IllegalStateException("Cannot access non-data model properties through the dynamic API") val operatorType = when { propertyMetadata.type == PropertyType.RLM_PROPERTY_TYPE_MIXED -> CollectionOperatorType.REALM_ANY @@ -1266,13 +1551,15 @@ internal object RealmObjectHelper { collectionType: CollectionType, elementType: KClass<*>, nullable: Boolean - ): PropertyMetadata { + ): PropertyMetadata? { val realElementType = elementType.realmStorageType() - return obj.metadata.getOrThrow(propertyName).also { propertyInfo -> + // FIXME Should this hold data model or realm schema? + return obj.metadata[propertyName]?.also { propertyInfo -> val kClass = RealmStorageTypeImpl.fromCorePropertyType(propertyInfo.type).kClass - if (collectionType != propertyInfo.collectionType || + // FIXME We can fix anything into RealmAny ... except EmbeddedObject + if (elementType != RealmAny::class && ( collectionType != propertyInfo.collectionType || realElementType != kClass || - nullable != propertyInfo.isNullable + nullable != propertyInfo.isNullable) ) { val expected = formatType(collectionType, realElementType, nullable) val actual = diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt index 4e86a39423..f46b721ac6 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmObjectUtil.kt @@ -136,6 +136,14 @@ internal val T.realmObjectReference: RealmObjectReference< internal inline fun T.runIfManaged(block: RealmObjectReference.() -> R): R? = realmObjectReference?.run(block) +internal inline fun T.runIfManagedOrThrow(block: RealmObjectReference.() -> R): R { + val realmObjectReference = realmObjectReference + return when (realmObjectReference) { + null -> throw IllegalStateException("Cannot perform operation on unmanaged object") + else -> realmObjectReference.run (block) + } +} + /** * Returns an identifier that uniquely identifies a RealmObject. This includes the version of the * object, so the same RealmObject at two different versions must have different identifiers, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmObjectImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmObjectImpl.kt index ad8d6088eb..83c439e2e2 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmObjectImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmObjectImpl.kt @@ -22,6 +22,7 @@ import io.realm.kotlin.types.RealmDictionary import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmSet import kotlin.reflect.KClass +import kotlin.reflect.typeOf internal class DynamicMutableRealmObjectImpl : DynamicMutableRealmObject, DynamicRealmObjectImpl() { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt index f6c91c7770..c6ec4acce5 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt @@ -22,18 +22,20 @@ import io.realm.kotlin.schema.RealmClass import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.schema.RealmProperty import io.realm.kotlin.schema.ValuePropertyType +import kotlin.reflect.KClass // TODO Public due to being a transitive dependency to RealmObjectCompanion public data class RealmClassImpl( // Optimization: Store the schema in the C-API alike structure directly from compiler plugin to // avoid unnecessary repeated initializations for realm_schema_new val cinteropClass: ClassInfo, - val cinteropProperties: List + val cinteropProperties: List, + override val inDataModel: Boolean = true, ) : RealmClass { override val name: String = cinteropClass.name override val properties: Collection = cinteropProperties.map { - RealmPropertyImpl.fromCoreProperty(it) + RealmPropertyImpl.fromCoreProperty(it, inDataModel) } override val primaryKey: RealmProperty? = properties.firstOrNull { it.type.run { this is ValuePropertyType && isPrimaryKey } @@ -47,4 +49,9 @@ public data class RealmClassImpl( } override fun get(key: String): RealmProperty? = properties.firstOrNull { it.name == key } + override val kClass: KClass<*>? + get() = TODO("Not yet implemented") + + // override val userDefined: Boolean +// get() = TODO("Not yet implemented") } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyImpl.kt index c1b7fb6847..b18ae5d07d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmPropertyImpl.kt @@ -24,10 +24,13 @@ import io.realm.kotlin.schema.RealmProperty import io.realm.kotlin.schema.RealmPropertyType import io.realm.kotlin.schema.SetPropertyType import io.realm.kotlin.schema.ValuePropertyType +import io.realm.kotlin.types.BaseRealmObject +import kotlin.reflect.KMutableProperty1 internal data class RealmPropertyImpl( override var name: String, override var type: RealmPropertyType, + override val inDataModel: Boolean, ) : RealmProperty { override val isNullable: Boolean = when (type) { @@ -37,8 +40,11 @@ internal data class RealmPropertyImpl( is MapPropertyType -> false } + override val accessor: KMutableProperty1? + get() = null + companion object { - fun fromCoreProperty(corePropertyImpl: PropertyInfo): RealmPropertyImpl { + fun fromCoreProperty(corePropertyImpl: PropertyInfo, inModel: Boolean): RealmPropertyImpl { return with(corePropertyImpl) { val storageType = RealmStorageTypeImpl.fromCorePropertyType(type) val type = when (collectionType) { @@ -64,7 +70,7 @@ internal data class RealmPropertyImpl( ) else -> error("Unsupported type $collectionType") } - RealmPropertyImpl(name, type) + RealmPropertyImpl(name, type, inModel) } } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmSchemaImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmSchemaImpl.kt index 272b7a657c..d754d3ed4f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmSchemaImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmSchemaImpl.kt @@ -42,7 +42,7 @@ internal data class RealmSchemaImpl( ).filter { property: PropertyInfo -> schemaMetadata == null || classMetadata?.get(property.name)?.isUserDefined() == true } - RealmClassImpl(table, properties) + RealmClassImpl(table, properties, true) } else { null } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClass.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClass.kt index 8b2e53f4ca..fc3d00c057 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClass.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClass.kt @@ -16,6 +16,8 @@ package io.realm.kotlin.schema +import kotlin.reflect.KClass + /** * A [RealmClass] describing the object model of a specific class. */ @@ -41,10 +43,17 @@ public interface RealmClass { */ public val kind: RealmClassKind + + public val inDataModel: Boolean + /** * Index operator to lookup a specific [RealmProperty] from its persisted property name. * * @return the [RealmProperty] with the given `propertyName` or `null` if no such property exists. */ public operator fun get(key: String): RealmProperty? + + // Nullable not reflected in model + // TODO Consider deprecating inDataModel + public val kClass: KClass<*>? } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmProperty.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmProperty.kt index edff005fb0..d15a40daa3 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmProperty.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmProperty.kt @@ -16,6 +16,9 @@ package io.realm.kotlin.schema +import io.realm.kotlin.types.BaseRealmObject +import kotlin.reflect.KMutableProperty1 + /** * A [RealmProperty] describes the properties of a class property in the object model. */ @@ -40,4 +43,9 @@ public interface RealmProperty { * other property types it will always be false. */ public val isNullable: Boolean + + public val inDataModel: Boolean + + // Nullable not reflected in model (replacement for isDataModel) + public val accessor: KMutableProperty1? } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/BaseRealmObject.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/BaseRealmObject.kt index d39ae21360..653a2eb536 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/BaseRealmObject.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/types/BaseRealmObject.kt @@ -17,8 +17,113 @@ package io.realm.kotlin.types import io.realm.kotlin.Deleteable +import io.realm.kotlin.internal.RealmObjectHelper +import io.realm.kotlin.internal.interop.RealmInterop +import io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow +import io.realm.kotlin.internal.runIfManaged +import io.realm.kotlin.internal.runIfManagedOrThrow +import io.realm.kotlin.schema.RealmClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +private const val s = "Cannot access or add additional properties to unmanaged object" /** * Base interface for all realm classes. */ -public interface BaseRealmObject : Deleteable +public interface BaseRealmObject : Deleteable { + // Dynamic API? +// public fun hasProperty(name: String): Boolean + + // Need to be isolated from data model properties + public val dataModelProperties: Set + get() = realmObjectCompanionOrThrow(this::class).io_realm_kotlin_fields.keys + public val extraProperties: Set + get() { + return this.runIfManaged { + RealmInterop.realm_get_additional_properties(this.objectPointer).toSet() + } ?: throw IllegalStateException(s) + } + + public val allProperties: Set + get() = dataModelProperties + extraProperties +} + +@PublishedApi +internal fun BaseRealmObject.get(propertyName: String, type: KType): T { + return this.runIfManagedOrThrow { + RealmObjectHelper.dynamicGetFromKType( + obj = this, + propertyName = propertyName, + type = type + ) + } +} +public fun BaseRealmObject.getAny(propertyName: String): RealmAny { + return this.runIfManagedOrThrow { + RealmObjectHelper.dynamicGetFromKType( + obj = this, + propertyName = propertyName, + type = typeOf() + ) + } +} + +public inline operator fun BaseRealmObject.get(propertyName: String): T { + return get(propertyName, typeOf()) +} + +public operator fun BaseRealmObject.set(name: String, value: T) { + return this.runIfManagedOrThrow { + RealmObjectHelper.setValueByName(this, name, value) + } +} + +public fun BaseRealmObject.setIfPresent(name: String, value: T): Boolean { + return this.runIfManagedOrThrow { + if (RealmInterop.realm_has_property(this.objectPointer, name)) { + RealmObjectHelper.setValueByName(this, name, value) + true + } else false + } +} + +public fun BaseRealmObject.setIfNotPresent(name: String, value: T): Boolean { + return this.runIfManagedOrThrow { + if (!RealmInterop.realm_has_property(this.objectPointer, name)) { + RealmObjectHelper.setValueByName(this, name, value) + true + } else false + } +} + +public fun BaseRealmObject.hasProperty(name: String): Boolean { + return this.runIfManagedOrThrow { + RealmInterop.realm_has_property(this.objectPointer, name) + } +} + +public fun BaseRealmObject.removeProperty(name: String): Boolean { + return this.runIfManagedOrThrow { + RealmInterop.realm_erase_property(this.objectPointer, name) + } +} + +public val BaseRealmObject.schema: RealmClass + get() = TODO() +// Dynamic objects + +public interface Extras: BaseRealmObject + +public val BaseRealmObject.extras: Extras + get() = this as Extras + +public inline operator fun Extras.get(propertyName: String): T { + return get(propertyName, typeOf()) +} + +public operator fun Extras.set(name: String, value: T) { + return this.runIfManagedOrThrow { + RealmObjectHelper.setValueByName(this, name, value) + } +} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt index b2c53632a1..0dc998b084 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt @@ -526,7 +526,8 @@ public interface SyncConfiguration : Configuration { partitionValue == null, inMemory, initialRealmFileConfiguration, - realmLogger + relaxedSchema, + realmLogger, ) return SyncConfigurationImpl( diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt index 0ce7399497..5c6df7feaa 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt @@ -193,7 +193,7 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi val realmClassImpl = pluginContext.lookupClassOrThrow(ClassIds.REALM_CLASS_IMPL) private val realmClassCtor = pluginContext.lookupConstructorInClass(ClassIds.REALM_CLASS_IMPL) { - it.owner.valueParameters.size == 2 + it.owner.valueParameters.size == 3 } private val validPrimaryKeyTypes = with(pluginContext.irBuiltIns) { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt index 1592abf877..7ff72e32bd 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmSchemaTests.kt @@ -303,7 +303,8 @@ class RealmSchemaTests { sampleDescriptor, RealmClassImpl( io.realm.kotlin.internal.interop.ClassInfo("NEW_CLASS", numProperties = 1), - listOf(io.realm.kotlin.internal.interop.PropertyInfo("NEW_PROPERTY", type = PropertyType.RLM_PROPERTY_TYPE_STRING)) + listOf(io.realm.kotlin.internal.interop.PropertyInfo("NEW_PROPERTY", type = PropertyType.RLM_PROPERTY_TYPE_STRING)), + true, ) ) ) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RelaxedSchemaTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RelaxedSchemaTests.kt new file mode 100644 index 0000000000..70e119d7c6 --- /dev/null +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RelaxedSchemaTests.kt @@ -0,0 +1,293 @@ +/* + * 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.test.common + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.dynamic.DynamicRealmObject +import io.realm.kotlin.entities.Sample +import io.realm.kotlin.ext.query +import io.realm.kotlin.internal.asDynamicRealm +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.query.RealmQuery +import io.realm.kotlin.query.RealmResults +import io.realm.kotlin.schema.RealmClass +import io.realm.kotlin.schema.RealmProperty +import io.realm.kotlin.schema.RealmSchema +import io.realm.kotlin.schema.ValuePropertyType +import io.realm.kotlin.test.common.utils.assertFailsWithMessage +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmList +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.extras +import io.realm.kotlin.types.get +import io.realm.kotlin.types.getAny +import io.realm.kotlin.types.removeProperty +import io.realm.kotlin.types.set +import io.realm.kotlin.types.setIfNotPresent +import io.realm.kotlin.types.setIfPresent +import kotlin.test.BeforeTest +import kotlin.test.Test + +class RelaxedSchemaTests { + + lateinit var realm: Realm + + @BeforeTest + fun setup() { + val configuration = RealmConfiguration.Builder(setOf(Sample::class, A::class)) + .relaxedSchema(true) + .build() + realm = Realm.open(configuration) + } + + @Test + fun basic() = runBlocking { + +// val configuration = RealmConfiguration.Builder(setOf(Sample::class, A::class)) +// .relaxedSchema(true) +// .build() +// Realm.deleteRealm(configuration) +// val realm = Realm.open(configuration) + val schema: RealmSchema = realm.schema() + val table: RealmClass = schema["Sample"]!! + val schemaProperties: Collection = table.properties + val property: RealmProperty = schemaProperties.first() +// property.run { +// this.name +// this.isNullable +// this.type.run { +// this.isNullable +// this.storageType +// this.collectionType +// when (this) { +// is ListPropertyType -> TODO() +// is MapPropertyType -> TODO() +// is SetPropertyType -> TODO() +// is ValuePropertyType -> TODO() +// } +// } +// } + var instance = realm.write { + copyToRealm(Sample()) + } + + // Iterate all strict properties dynamically (as RealmAny) +// val x1: RealmList = instance["strinaListField"] + val x2: RealmAny = instance.getAny("stringListField") + val x3: RealmList = instance["stringListField"] + val x4: RealmAny = instance["stringListField"] + val x = schemaProperties.forEach { +// if (it.type is ValuePropertyType) { + val y: RealmAny = instance[it.name] + println("instance[${it.name}] = $y") +// } + } + + instance = realm.write { + val instance = findLatest(instance)!! + instance["prop3"] = RealmAny.create("Realm") + instance["prop4"] = RealmAny.create(5) + instance + } + // Iterate all strict+relaxed properties dynamically (as RealmAny) + // Must be from instance as non-strict properties are differing across instances + instance.extraProperties.forEach { it -> + val y = instance.get(it) + println("instance.extras[${it}] = $y") + } + + // Iterate relaxed properties dynamically (as RealmAny) + // Must be from instance as non-strict properties are differing across instances + instance.extraProperties.forEach { it -> + if (table["it"] == null) { + instance.get(it) + } + } + + instance.extraProperties.forEach { it -> + instance.get(it) + } + // Can only remove extra properties, hence must not contain realm-schema-props + realm.write { + val instance1 = findLatest(instance)!! + instance1.extraProperties.forEach { + println("Removed: $it ${instance1.removeProperty(it)}") + } + } + + // What about these shortcuts for common patterns +// if (instance.hasProperty("age")) { +// instance.set("age", 6) +// } +// val present1 = instance.setIfPresent("age", 6) +// if (!instance.hasProperty("age")) { +// instance.set("age", 6) +// } +// val present1 = instance.setIfNotPresent("age", 6) + + instance = realm.write { + val obj = instance + val present2 = findLatest(obj)!!.setIfNotPresent("age", RealmAny.create("asdfasdf")) + obj + } + + // + // Strict properties + realm.schema()["Sample"]!!.properties.forEach { realmProperty: RealmProperty -> + if (realmProperty.type is ValuePropertyType) { + instance.get(realmProperty.name) + } + } + // If this is too verbose, we could make explicit accessors for the individual selections + // all-, dataModel-, realmSchema-, extra-Properties + instance.extraProperties.forEach { it -> + val y = instance.get(it) + println("instance.extras[${it}] = $y") + } + + // EXTRAS + // Pretty neat, but how to keep things in Extras-namespace + realm.write { + findLatest(instance)!!.extras["asdf"] = RealmAny.create("ASDf") + } + + // Non-strict properties +// instance.extraProperties().forEach { it -> instance.get(it) } + +// val x: DynamicRealm +// val y = x.query("Sample") +// val dynamicSample: DynamicRealmObject = y.find().get(0) +// dynamicSample.getNullableValue("prop") + + // ?? Do we need some RealmProperty? from RealmObject instances + // - We would newer have different type than RealmAny + // Are there any RealmProperty information that is interesting + // - Can we set/get/erase based on the type of property (model, schema, extra) + // - Think it would be extremely heavy to pull RealmProperty details + // - And if there is no concept of a server schema then I can't see why we need the info + } + + @Test + fun createRealm() = runBlocking { + val configuration = RealmConfiguration.Builder(setOf(A::class)) + .relaxedSchema(true) + .build() + val realm = Realm.open(configuration) + println("schema: ${realm.schema()}") + println("As: ${realm.query().find().size}") + val x = realm.write { + val x = copyToRealm(A()) + println("PROPS: ${x.extraProperties}") + x["PROP"] = RealmAny.create(34) + x + } + println("PROPS: ${x.extraProperties}") + for (property in x.extraProperties) { + println("PROP[$property] = ${x.get(property)}") + } + } + + @Test + fun iterateProperties() = runBlocking { + val configuration = RealmConfiguration.Builder(setOf(A::class)) + .relaxedSchema(true) + .build() + val realm = Realm.open(configuration) + realm.write { + delete(query()) + } + println("schema: ${realm.schema()}") + println("As: ${realm.query().find().size}") + val x = realm.write { + val x = copyToRealm(A()) + println("PROPS: ${x.extraProperties}") + x["PROP"] = RealmAny.create(34) + x + } + println("PROPS: ${x.extraProperties}") + // + val properties = realm.schema()["A"]!!.properties + properties.forEach { +// println("PROP[${it.name}] = ${(it.accessor as KMutableProperty1?)!!.get(x)}") +// println("PROP[${it.name}] = ${(it.accessor as KMutableProperty1?)!!.set(x, null)}") + } + for (property in x.extraProperties) { + println("PROP[$property] = ${x.get(property)}") + } + + // TODO Maybe have a speedier way to get to the dynamic object + val result: RealmQuery = realm.asDynamicRealm().query("A") + val find: RealmResults = result.find() + + find[0].run { + + // Can be iterated across instances + val allProps = properties.map { it.name } + x.extraProperties + println("allProps: $allProps") + for (allProp in allProps) { + // Needs to be generalised to DynamicObject.get(name: String) :thinking: RealmAny vs. T vs. Any +// this.get<>() + println("property: $allProp") + val y: String = x[allProp] +// x.asdf(allProp) + println("x[$allProp]: String = ${x.get(allProp)}") + println("this[$allProp]: RealmAny = ${this.get(allProp)}") + } + } + + } + + // Tests + // - Unmanaged objects + // - Non-existing keys!? + // - Open non-relaxed file with relaxed flag + // - open relaxed file without relaxed flag + // - Sync? + + // TEST + // - Add property outside write + // - Remove property outside write + @Test + fun removeModelPropertyThrows() { + // Can we remove a property from the schema - Needs to be in a write transaction + } + + @Test + fun removeSchemaPropertyThrows() { + // Can we remove a property from the schema - Needs to be in a write transaction + } + + @Test + fun unmanagedObject_throws() { + val sample = Sample() + assertFailsWithMessage("Cannot access or add additional properties to unmanaged object") { + sample["unknown"] + } + assertFailsWithMessage("Cannot access or add additional properties to unmanaged object") { + sample["unknown"] = "TEST" + } + } +} + +class A : RealmObject { + var id: String = "DEFAULT" +} + +class B : RealmObject { + var id: String = "DEFAULT" +}