Skip to content

Commit

Permalink
RealmValue interface changes to support collections in mixed
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsenko committed Sep 18, 2023
1 parent e432f53 commit 4c2cc69
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 24 deletions.
153 changes: 131 additions & 22 deletions common/lib/src/realm_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:ffi';
import 'dart:typed_data';
import 'package:objectid/objectid.dart';
import 'package:sane_uuid/uuid.dart';
Expand Down Expand Up @@ -172,10 +171,20 @@ abstract class AsymmetricObjectMarker implements RealmObjectBaseMarker {}
/// }
/// ```
class RealmValue {
static const true_ = RealmValue.bool(true);
static const false_ = RealmValue.bool(false);
static const null_ = RealmValue.nullValue();

final Object? value;
Type get type => value.runtimeType;
T as<T>() => value as T; // better for code completion

List<RealmValue> get asList => as<List<RealmValue>>();
Map<String, RealmValue> get asDictionary => as<Map<String, RealmValue>>();
Set<RealmValue> get asSet => as<Set<RealmValue>>();

T call<T>() => as<T>(); // is this useful?

// This is private, so user cannot accidentally construct an invalid instance
const RealmValue._(this.value);

Expand All @@ -191,34 +200,90 @@ class RealmValue {
const RealmValue.decimal128(Decimal128 decimal) : this._(decimal);
const RealmValue.uuid(Uuid uuid) : this._(uuid);
const RealmValue.uint8List(Uint8List binary) : this._(binary);
const RealmValue.dictionary(Map<String, RealmValue> dictionary) : this._(dictionary);
const RealmValue.list(Iterable<RealmValue> list) : this._(list);
const RealmValue.set(Set<RealmValue> set) : this._(set);

static bool _isCollection(Object? value) => value is List<RealmValue> || value is Set<RealmValue> || value is Map<String, RealmValue>;

/// Will throw [ArgumentError]
/// Will throw [ArgumentError] if the type is not supported
factory RealmValue.from(Object? object) {
if (object == null ||
object is bool ||
object is String ||
object is int ||
object is Float ||
object is double ||
object is RealmObjectMarker ||
object is DateTime ||
object is ObjectId ||
object is Decimal128 ||
object is Uuid ||
object is Uint8List) {
return RealmValue._(object);
} else {
throw ArgumentError.value(object, 'object', 'Unsupported type');
return switch (object) {
Object? o
when o == null ||
o is bool ||
o is String ||
o is int ||
o is double ||
o is RealmObjectMarker ||
o is DateTime ||
o is ObjectId ||
o is Decimal128 ||
o is Uuid ||
o is Uint8List =>
RealmValue._(o),
Map<String, RealmValue> d => RealmValue.dictionary(d),
Map<String, dynamic> d => RealmValue.dictionary(Map.fromEntries(d.entries.map((e) => MapEntry(e.key, RealmValue.from(e.value))))),
List<RealmValue> l => RealmValue.list(l),
List<dynamic> l => RealmValue.list(l.map((o) => RealmValue.from(o)).toList()),
Set<RealmValue> s => RealmValue.set(s),
Set<dynamic> s => RealmValue.set(s.map((o) => RealmValue.from(o)).toSet()),
_ => throw ArgumentError.value(object.runtimeType, 'object', 'Unsupported type'),
};
}

RealmValue operator [](Object? index) {
final v = value;
return switch (index) {
int i when v is List<RealmValue> => v[i], // throws on range error
String s when v is Map<String, RealmValue> => v[s],
Iterable<Object?> l when _isCollection(v) => this[l.first][l.skip(1)],
_ => throw ArgumentError.value(index, 'index', 'Unsupported type'),
} ??
const RealmValue.nullValue();
}

void operator []=(Object? index, RealmValue value) {
final v = this.value;
switch (index) {
case int i when v is List<RealmValue>:
v[i] = value;
break;
case String s when v is Map<String, RealmValue>:
v[s] = value;
break;
case Iterable<Object?> l when _isCollection(v):
this[l.first][l.skip(1)] = value;
break;
default:
throw ArgumentError.value(index, 'index', 'Unsupported type');
}
}

RealmValue lookup<T>(T item) {
final v = value as Set<RealmValue>?;
if (v == null) throw ArgumentError.value(item, 'item', 'Unsupported type'); // TODO: Wrong exception
return v.lookup(item) ?? const RealmValue.nullValue();
}

bool add<T>(T item) {
if (_isCollection(item)) throw ArgumentError.value(item, 'item', 'Unsupported type'); // TODO: Wrong exception
final v = value as Set<RealmValue>?;
if (v == null) throw ArgumentError.value(item, 'item', 'Unsupported type'); // TODO: Wrong exception
return v.add(RealmValue.from(item));
}

bool remove<T>(T item) {
final v = value as Set<RealmValue>?;
if (v == null) throw ArgumentError.value(item, 'item', 'Unsupported type'); // TODO: Wrong exception
return v.remove(item);
}

@override
operator ==(Object? other) {
if (other is RealmValue) {
return value == other.value;
}

return value == other;
if (identical(this, other)) return true;
if (other is! RealmValue) return false;
return value == other.value;
}

@override
Expand All @@ -227,3 +292,47 @@ class RealmValue {
@override
String toString() => 'RealmValue($value)';
}

void demo() {
final any = RealmValue.from([
1,
2,
{
'x': {1, 2}
},
]);

if (any[2]['x'].lookup(1) != const RealmValue.nullValue()) {}

// access list element at index 2,
// then map element with key 'x',
// then set element 1, if it exists
// assuming an int, or null if not found
final x = any[2]['x'].lookup(1).as<int?>();
assert(x == 1);

// or, a bit shorter
final y = any[2]['x'].lookup(1)<int?>();
assert(y == 1);

// or, if you are sure
int z = any[2]['x'].lookup(1)(); // <-- shortest
assert(z == 1);

// or, using a list of indexes
final u = any[[2, 'x']]();
assert(u == RealmValue.from({1, 2}));

// which allows for a different layout
int v = any[[
2,
'x',
]]
.lookup(1)();
assert(v == 1);

any[1] = RealmValue.from({'z': 'foo'}); // replace int with a map
any[2]['x'].add(3); // add an element to the set
any[2]['x'] = RealmValue.from(1); // replace set with an int
any[2]['y'] = RealmValue.from(true); // add a new key to the map
}
7 changes: 5 additions & 2 deletions test/realm_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ Future<void> main([List<String>? args]) async {
ObjectId.fromTimestamp(now),
Uuid.v4(),
Decimal128.fromDouble(128.128),
Uint8List.fromList([1, 2, 0])
Uint8List.fromList([1, 2, 0]),
[1, 2, 3],
{1, 2, 3},
{'a': 1, 'b': 2},
];

for (final x in values) {
Expand All @@ -80,7 +83,7 @@ Future<void> main([List<String>? args]) async {
test('Illegal value', () {
final config = Configuration.local([AnythingGoes.schema, Stuff.schema, TuckedIn.schema]);
final realm = getRealm(config);
expect(() => realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.from(<int>[1, 2])))), throwsArgumentError);
expect(() => realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.from((1, 2))))), throwsArgumentError); // records not supported
});

test('Embedded object not allowed in RealmValue', () {
Expand Down

0 comments on commit 4c2cc69

Please sign in to comment.