Skip to content

Commit

Permalink
RDART-910: Add support for RealmObject.objectSchema (#1540)
Browse files Browse the repository at this point in the history
* Add support for RealmObject.objectSchema

* Dont use properties

* ditto

* Remove accidentally committed file

* Disable tests that rely on core schema notifications
  • Loading branch information
nirinchev authored Mar 14, 2024
1 parent 9e2285b commit 30d7db8
Show file tree
Hide file tree
Showing 39 changed files with 1,012 additions and 193 deletions.
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))
* `SyncProgress.transferredBytes` and `SyncProgress.transferableBytes` have been consolidated into `SyncProgress.progressEstimate`. The values reported previously were incorrect and did not accurately represent bytes either. The new field better conveys the uncertainty around the progress being reported. With this release, we're reporting accurate estimates for upload progress, but estimating downloads is still unreliable. A future server and SDK release will add better estimations for download progress. (Issue [#1562](https://github.com/realm/realm-dart/issues/1562))

### Enhancements
Expand Down Expand Up @@ -104,6 +105,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);

@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 @@ -3256,6 +3283,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

0 comments on commit 30d7db8

Please sign in to comment.