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

Add objectSchema as a property on RealmObjectBase #1493

Closed
wants to merge 3 commits into from
Closed
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
10 changes: 8 additions & 2 deletions example/bin/myapp.g.dart

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

10 changes: 8 additions & 2 deletions flutter/realm_flutter/example/lib/main.g.dart

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

5 changes: 4 additions & 1 deletion generator/lib/src/realm_model_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class RealmModelInfo {
yield 'static SchemaObject _initSchema() {';
{
yield 'RealmObjectBase.registerFactory($name._);';
yield "return const SchemaObject(ObjectType.${baseType.name}, $name, '$realmName', [";
yield "return SchemaObject(ObjectType.${baseType.name}, $name, '$realmName', [";
{
yield* fields.map((f) {
final namedArgs = {
Expand All @@ -146,6 +146,9 @@ class RealmModelInfo {
yield ']);';
}
yield '}';
yield '';
yield '@override';
yield 'SchemaObject get objectSchema => RealmObjectBase.getSchema(this) ?? schema;';
}
yield '}';
}
Expand Down
28 changes: 23 additions & 5 deletions lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -413,21 +413,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 @@ -456,6 +466,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
88 changes: 59 additions & 29 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,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 @@ -189,7 +189,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 @@ -289,7 +289,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 @@ -338,6 +338,12 @@ class _RealmCore {
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 @@ -962,38 +968,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 @@ -1735,6 +1745,15 @@ class _RealmCore {
}
}

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 @@ -1783,6 +1802,13 @@ class _RealmCore {
return RealmNotificationTokenHandle._(pointer, map.realm.handle);
}

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

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

bool getObjectChangesIsDeleted(RealmObjectChangesHandle handle) {
return _realmLib.realm_object_changes_is_deleted(handle._pointer);
}
Expand Down Expand Up @@ -3159,6 +3185,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
56 changes: 50 additions & 6 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class Realm implements Finalizable {
late final RealmMetadata _metadata;
late final RealmHandle _handle;
final bool _isInMigration;
late final RealmCallbackTokenHandle? _schemaCallbackHandle;

/// An object encompassing this `Realm` instance's dynamic API.
late final DynamicRealm dynamic = DynamicRealm._(this);
Expand All @@ -179,6 +180,15 @@ class Realm implements Finalizable {

Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) : _handle = handle ?? _openRealm(config) {
_populateMetadata();
// The schema of a Realm file may change due to sync adding new properties/classes. We subscribe for notifications
// in order to update the managed schema instance in case this happens. The same is true for dynamic Realms. For
// local Realms with user-supplied schema, the schema on disk may still change, but Core doesn't report the updated
// schema, so even if we subscribe, we wouldn't be able to see the updates.
if (config is FlexibleSyncConfiguration || config.schemaObjects.isEmpty) {
_schemaCallbackHandle = realmCore.subscribeForSchemaNotifications(this);
} else {
_schemaCallbackHandle = null;
}
}

/// A method for asynchronously opening a [Realm].
Expand Down Expand Up @@ -436,6 +446,7 @@ class Realm implements Finalizable {
return;
}

_schemaCallbackHandle?.release();
realmCore.closeRealm(this);
handle.release();
}
Expand Down Expand Up @@ -645,6 +656,29 @@ class Realm implements Finalizable {
Future<bool> refreshAsync() async {
return realmCore.realmRefreshAsync(this);
}

void _updateSchema() {
final newSchema = realmCore.readSchema(this);

for (final schemaObject in newSchema) {
final existing = schema.firstWhereOrNull((s) => s.name == schemaObject.name);
if (existing == null) {
schema.add(schemaObject);
final meta = realmCore.getObjectMetadata(this, schemaObject);
metadata._add(meta);
} else if (schemaObject.length > existing.length) {
final existingMeta = metadata.getByName(schemaObject.name);
final propertyMeta = realmCore.getPropertiesMetadata(this, existingMeta.classKey, existingMeta.primaryKey);
for (final property in schemaObject) {
final existingProp = existing.firstWhereOrNull((e) => e.mapTo == property.mapTo);
if (existingProp == null) {
existing.add(property);
existingMeta[property.name] = propertyMeta[property.name]!;
}
}
}
}
}
}

/// Provides a scope to safely write data to a [Realm]. Can be created using [Realm.beginWrite] or
Expand Down Expand Up @@ -839,6 +873,10 @@ extension RealmInternal on Realm {
static void logMessageForTesting(Level logLevel, String message) {
realmCore.logMessageForTesting(logLevel, message);
}

void updateSchema() {
_updateSchema();
}
}

/// @nodoc
Expand Down Expand Up @@ -930,13 +968,19 @@ class RealmMetadata {

RealmMetadata._(Iterable<RealmObjectMetadata> objectMetadatas) {
for (final metadata in objectMetadatas) {
if (!metadata.schema.isGenericRealmObject) {
_typeMap[metadata.schema.type] = metadata;
} else {
_stringMap[metadata.schema.name] = metadata;
}
_classKeyMap[metadata.classKey] = metadata;
_add(metadata);
}
}

/// This is used when constructing the metadata, but also when the Realm schema
/// changes.
void _add(RealmObjectMetadata metadata) {
if (!metadata.schema.isGenericRealmObject) {
_typeMap[metadata.schema.type] = metadata;
} else {
_stringMap[metadata.schema.name] = metadata;
}
_classKeyMap[metadata.classKey] = metadata;
}

RealmObjectMetadata getByType(Type type) {
Expand Down
Loading
Loading