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

RDART-910: Add support for RealmObject.objectSchema #1540

Merged
merged 8 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
Realm.shutdown();
}
```
* Removed `SchemaObject.properties` - instead, `SchemaObject` is now an iterable collection of `Property`. (Issue [#1449](https://github.com/realm/realm-dart/issues/1449))

### Enhancements
* Realm objects can now be serialized as [EJSON](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/)
Expand Down Expand Up @@ -103,6 +104,8 @@
```
* Added `RealmValueType` enum that contains all the possible types that can be wrapped by a `RealmValue`. (PR [#1469](https://github.com/realm/realm-dart/pull/1469))
* Added support for accessing `Set` and `Map` types using the dynamic object API - `obj.dynamic.getSet/getMap`. (PR [#1533](https://github.com/realm/realm-dart/pull/1533))
* Added `RealmObjectBase.objectSchema` that returns the schema for this object. In most cases, this would be the schema defined in the model, but in case the Realm is opened as dynamic (by providing an empty collection for schemaObjects in the config) or using `FlexibleSyncConfiguration`, it may change as the schema on disk changes. (Issue [#1449](https://github.com/realm/realm-dart/issues/1449))
* Added `Realm.schemaChanges` that returns a stream of schema changes that can be listened to. Only dynamic and synchronized Realms will emit schema changes. (Issue [#1449](https://github.com/realm/realm-dart/issues/1449))


### Fixed
Expand Down
10 changes: 8 additions & 2 deletions packages/realm/example/lib/main.realm.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions packages/realm_dart/example/bin/myapp.realm.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 23 additions & 5 deletions packages/realm_dart/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -398,21 +398,31 @@ class InMemoryConfiguration extends Configuration {
/// A collection of properties describing the underlying schema of a [RealmObjectBase].
///
/// {@category Configuration}
class SchemaObject {
class SchemaObject extends Iterable<SchemaProperty> {
final List<SchemaProperty> _properties;

/// Schema object type.
final Type type;

/// Collection of the properties of this schema object.
final List<SchemaProperty> properties;

/// Returns the name of this schema type.
final String name;

/// Returns the base type of this schema object.
final ObjectType baseType;

/// Creates schema instance with object type and collection of object's properties.
const SchemaObject(this.baseType, this.type, this.name, this.properties);
SchemaObject(this.baseType, this.type, this.name, Iterable<SchemaProperty> properties) : _properties = List.from(properties);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the loss of const on this ctor here really needed? Isn't it only caused by the List.from call?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, but I'm not sure how we can avoid the loss of const.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the top of my head, I think you can use a DelegatingIterable .. or just drop making SchemaObject iterable itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we want the _properties list to be mutable - i.e. we're adding stuff at runtime. My understanding was that this is incompatible with const-constructed objects, but it's very possible I'm missing something.

Copy link
Contributor

@nielsenko nielsenko Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because a ctor is marked as const doesn't mean it has to be used as such
with const arguments. Only that it can. If the backing list to DelegatingIterable is const, then you will get a runtime exception trying to modify it.

I guess that ties into my question, if we should track both the current schema, and the original schema used when generating the model. The latter could still be const constructed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still dream of using the flyweight property pattern from the old #903 PR 😄


@override
Iterator<SchemaProperty> get iterator => _properties.iterator;

@override
int get length => _properties.length;

SchemaProperty operator [](int index) => _properties[index];

@override
SchemaProperty elementAt(int index) => _properties.elementAt(index);
}

/// Describes the complete set of classes which may be stored in a `Realm`
Expand Down Expand Up @@ -441,6 +451,14 @@ class RealmSchema extends Iterable<SchemaObject> {
/// @nodoc
extension SchemaObjectInternal on SchemaObject {
bool get isGenericRealmObject => type == RealmObject || type == EmbeddedObject || type == RealmObjectBase;

void add(SchemaProperty property) => _properties.add(property);
}

extension RealmSchemaInternal on RealmSchema {
void add(SchemaObject obj) {
_schema.add(obj);
}
}

/// [ClientResetHandler] is triggered if the device and server cannot agree
Expand Down
89 changes: 60 additions & 29 deletions packages/realm_dart/lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ class _RealmCore {
for (var i = 0; i < classCount; i++) {
final schemaObject = schema.elementAt(i);
final classInfo = schemaClasses.elementAt(i).ref;
final propertiesCount = schemaObject.properties.length;
final computedCount = schemaObject.properties.where((p) => p.isComputed).length;
final propertiesCount = schemaObject.length;
final computedCount = schemaObject.where((p) => p.isComputed).length;
final persistedCount = propertiesCount - computedCount;

classInfo.name = schemaObject.name.toCharPtr(arena);
Expand All @@ -175,7 +175,7 @@ class _RealmCore {
final properties = arena<realm_property_info_t>(propertiesCount);

for (var j = 0; j < propertiesCount; j++) {
final schemaProperty = schemaObject.properties[j];
final schemaProperty = schemaObject[j];
final propInfo = properties.elementAt(j).ref;
propInfo.name = schemaProperty.mapTo.toCharPtr(arena);
propInfo.public_name = (schemaProperty.mapTo != schemaProperty.name ? schemaProperty.name : '').toCharPtr(arena);
Expand Down Expand Up @@ -275,7 +275,7 @@ class _RealmCore {
} else if (config is InMemoryConfiguration) {
_realmLib.realm_config_set_in_memory(configHandle._pointer, true);
} else if (config is FlexibleSyncConfiguration) {
_realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_EXPLICIT);
_realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED);
final syncConfigPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_flx_sync_config_new(config.user.handle._pointer));
try {
_realmLib.realm_sync_config_set_session_stop_policy(syncConfigPtr, config.sessionStopPolicy.index);
Expand Down Expand Up @@ -321,9 +321,16 @@ class _RealmCore {
_realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_EXPLICIT);
_realmLib.realm_config_set_force_sync_history(configPtr, true);
}

if (config.encryptionKey != null) {
_realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), encryptionKeySize);
}

// For sync and for dynamic Realms, we need to have a complete view of the schema in Core.
if (config.schemaObjects.isEmpty || config is FlexibleSyncConfiguration) {
_realmLib.realm_config_set_schema_subset_mode(configHandle._pointer, realm_schema_subset_mode.RLM_SCHEMA_SUBSET_MODE_COMPLETE);
}

return configHandle;
});
}
Expand Down Expand Up @@ -948,38 +955,42 @@ class _RealmCore {
}

final primaryKey = classInfo.ref.primary_key.cast<Utf8>().toRealmDartString(treatEmptyAsNull: true);
return RealmObjectMetadata(schema, classInfo.ref.key, _getPropertyMetadata(realm, classInfo.ref.key, primaryKey));
return RealmObjectMetadata(schema, classInfo.ref.key, _getPropertiesMetadata(realm, classInfo.ref.key, primaryKey, arena));
});
}

Map<String, RealmPropertyMetadata> _getPropertyMetadata(Realm realm, int classKey, String? primaryKeyName) {
Map<String, RealmPropertyMetadata> getPropertiesMetadata(Realm realm, int classKey, String? primaryKeyName) {
return using((Arena arena) {
final propertyCountPtr = arena<Size>();
_realmLib.invokeGetBool(
() => _realmLib.realm_get_property_keys(realm.handle._pointer, classKey, nullptr, 0, propertyCountPtr), "Error getting property count");

var propertyCount = propertyCountPtr.value;
final propertiesPtr = arena<realm_property_info_t>(propertyCount);
_realmLib.invokeGetBool(() => _realmLib.realm_get_class_properties(realm.handle._pointer, classKey, propertiesPtr, propertyCount, propertyCountPtr),
"Error getting class properties.");

propertyCount = propertyCountPtr.value;
Map<String, RealmPropertyMetadata> result = <String, RealmPropertyMetadata>{};
for (var i = 0; i < propertyCount; i++) {
final property = propertiesPtr.elementAt(i);
final propertyName = property.ref.name.cast<Utf8>().toRealmDartString()!;
final objectType = property.ref.link_target.cast<Utf8>().toRealmDartString(treatEmptyAsNull: true);
final linkOriginProperty = property.ref.link_origin_property_name.cast<Utf8>().toRealmDartString(treatEmptyAsNull: true);
final isNullable = property.ref.flags & realm_property_flags.RLM_PROPERTY_NULLABLE != 0;
final isPrimaryKey = propertyName == primaryKeyName;
final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, linkOriginProperty, RealmPropertyType.values.elementAt(property.ref.type),
isNullable, isPrimaryKey, RealmCollectionType.values.elementAt(property.ref.collection_type));
result[propertyName] = propertyMeta;
}
return result;
return _getPropertiesMetadata(realm, classKey, primaryKeyName, arena);
});
}

Map<String, RealmPropertyMetadata> _getPropertiesMetadata(Realm realm, int classKey, String? primaryKeyName, Arena arena) {
final propertyCountPtr = arena<Size>();
_realmLib.invokeGetBool(
() => _realmLib.realm_get_property_keys(realm.handle._pointer, classKey, nullptr, 0, propertyCountPtr), "Error getting property count");

var propertyCount = propertyCountPtr.value;
final propertiesPtr = arena<realm_property_info_t>(propertyCount);
_realmLib.invokeGetBool(() => _realmLib.realm_get_class_properties(realm.handle._pointer, classKey, propertiesPtr, propertyCount, propertyCountPtr),
"Error getting class properties.");

propertyCount = propertyCountPtr.value;
Map<String, RealmPropertyMetadata> result = <String, RealmPropertyMetadata>{};
for (var i = 0; i < propertyCount; i++) {
final property = propertiesPtr.elementAt(i);
final propertyName = property.ref.name.cast<Utf8>().toRealmDartString()!;
final objectType = property.ref.link_target.cast<Utf8>().toRealmDartString(treatEmptyAsNull: true);
final linkOriginProperty = property.ref.link_origin_property_name.cast<Utf8>().toRealmDartString(treatEmptyAsNull: true);
final isNullable = property.ref.flags & realm_property_flags.RLM_PROPERTY_NULLABLE != 0;
final isPrimaryKey = propertyName == primaryKeyName;
final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, linkOriginProperty, RealmPropertyType.values.elementAt(property.ref.type),
isNullable, isPrimaryKey, RealmCollectionType.values.elementAt(property.ref.collection_type));
result[propertyName] = propertyMeta;
}
return result;
}

RealmObjectHandle createRealmObject(Realm realm, int classKey) {
final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_object_create(realm.handle._pointer, classKey));
return RealmObjectHandle._(realmPtr, realm.handle);
Expand Down Expand Up @@ -1757,6 +1768,15 @@ class _RealmCore {
controller.onUserChanged();
}

static void schema_change_callback(Pointer<Void> userdata, Pointer<realm_schema> data) {
final Realm realm = userdata.toObject();
try {
realm.updateSchema();
} catch (e) {
Realm.logger.log(RealmLogLevel.error, 'Failed to update Realm schema: $e');
}
}

RealmNotificationTokenHandle subscribeResultsNotifications(RealmResults results, NotificationsController controller) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_add_notification_callback(
results.handle._pointer,
Expand Down Expand Up @@ -1817,6 +1837,13 @@ class _RealmCore {
return UserNotificationTokenHandle._(notification_token);
}

RealmCallbackTokenHandle subscribeForSchemaNotifications(Realm realm) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_add_schema_changed_callback(realm.handle._pointer,
Pointer.fromFunction(schema_change_callback), realm.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle));

return RealmCallbackTokenHandle._(pointer, realm.handle);
}

bool getObjectChangesIsDeleted(RealmObjectChangesHandle handle) {
return _realmLib.realm_object_changes_is_deleted(handle._pointer);
}
Expand Down Expand Up @@ -3248,6 +3275,10 @@ class _RealmQueryHandle extends RootedHandleBase<realm_query> {
_RealmQueryHandle._(Pointer<realm_query> pointer, RealmHandle root) : super(root, pointer, 256);
}

class RealmCallbackTokenHandle extends RootedHandleBase<realm_callback_token> {
RealmCallbackTokenHandle._(Pointer<realm_callback_token> pointer, RealmHandle root) : super(root, pointer, 32);
}

class RealmNotificationTokenHandle extends RootedHandleBase<realm_notification_token> {
RealmNotificationTokenHandle._(Pointer<realm_notification_token> pointer, RealmHandle root) : super(root, pointer, 32);
}
Expand Down
Loading
Loading