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

binary type support #1320

Merged
merged 19 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 18 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: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
## 1.2.0 (2023-06-08)

### Enhancements
* Added support for Full-Text search (simple term) queries. ([#1300](https://github.com/realm/realm-dart/pull/1300))
* Added support binary data type. ([#1320](https://github.com/realm/realm-dart/pull/1320))
* Added support for Full-Text search (simple term) queries. ([#1300](https://github.com/realm/realm-dart/pull/1300))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was just formatted

* To enable FTS queries on string properties, add the `@Indexed(RealmIndexType.fullText)` annotation.
* To run queries, use the `TEXT` operator: `realm.all<Book>().query("description TEXT \$0", "fantasy novel")`.

Expand Down
32 changes: 18 additions & 14 deletions common/lib/src/realm_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////////

import 'dart:ffi';
import 'dart:typed_data';
import 'package:objectid/objectid.dart';
import 'package:sane_uuid/uuid.dart';

Expand Down Expand Up @@ -186,23 +187,25 @@ class RealmValue {
const RealmValue.objectId(ObjectId id) : this._(id);
const RealmValue.decimal128(Decimal128 decimal) : this._(decimal);
const RealmValue.uuid(Uuid uuid) : this._(uuid);
const RealmValue.uint8List(Uint8List binary) : this._(binary);

/// Will throw [ArgumentError]
factory RealmValue.from(Object? o) {
if (o == null ||
o is bool ||
o is String ||
o is int ||
o is Float ||
o is double ||
o is RealmObjectMarker ||
o is DateTime ||
o is ObjectId ||
o is Decimal128 ||
o is Uuid) {
return RealmValue._(o);
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(o, 'o', 'Unsupported type');
throw ArgumentError.value(object, 'object', 'Unsupported type');
}
}

Expand All @@ -211,6 +214,7 @@ class RealmValue {
if (other is RealmValue) {
return value == other.value;
}

return value == other;
}

Expand Down
1 change: 1 addition & 0 deletions generator/lib/src/dart_type_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ extension DartTypeEx on DartType {
bool get isRealmCollection => realmCollectionType != RealmCollectionType.none;
bool get isRealmSet => realmCollectionType == RealmCollectionType.set;
bool get isRealmModel => element2 != null ? realmModelChecker.annotationsOfExact(element2!).isNotEmpty : false;
bool get isUint8List => isExactly<Uint8List>();

bool get isNullable => session.typeSystem.isNullable(this);
DartType get asNonNullable => session.typeSystem.promoteToNonNull(this);
Expand Down
2 changes: 1 addition & 1 deletion generator/lib/src/field_element_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import 'session.dart';
import 'type_checkers.dart';

extension FieldElementEx on FieldElement {
static const realmSetUnsupportedRealmTypes = [RealmPropertyType.binary, RealmPropertyType.linkingObjects];
static const realmSetUnsupportedRealmTypes = [RealmPropertyType.linkingObjects];

FieldDeclaration get declarationAstNode => getDeclarationFromElement(this)!.node.parent!.parent as FieldDeclaration;

Expand Down
22 changes: 17 additions & 5 deletions generator/lib/src/realm_model_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
//
////////////////////////////////////////////////////////////////////////////////

import 'dart:typed_data';

blagoev marked this conversation as resolved.
Show resolved Hide resolved
import 'package:realm_common/realm_common.dart';

import 'dart_type_ex.dart';
Expand All @@ -36,8 +38,9 @@ class RealmModelInfo {
{
final allSettable = fields.where((f) => !f.type.isRealmCollection && !f.isRealmBacklink).toList();

final hasDefaults = allSettable.where((f) => f.hasDefaultValue).toList();
if (hasDefaults.isNotEmpty) {
final fieldsWithDefaultValue = allSettable.where((f) => f.hasDefaultValue && !f.type.isUint8List).toList();
final shouldEmitDefaultsSet = fieldsWithDefaultValue.isNotEmpty;
if (shouldEmitDefaultsSet) {
yield 'static var _defaultsSet = false;';
yield '';
}
Expand All @@ -52,23 +55,32 @@ class RealmModelInfo {
final sets = fields.where((f) => f.isDartCoreSet).toList();
if (notRequired.isNotEmpty || collections.isNotEmpty || sets.isNotEmpty) {
yield '{';
yield* notRequired.map((f) => '${f.mappedTypeName} ${f.name}${f.initializer},');
yield* notRequired.map((f) {
if (f.type.isUint8List && f.hasDefaultValue) {
return '${f.mappedTypeName}? ${f.name},';
}
return '${f.mappedTypeName} ${f.name}${f.initializer},';
});
yield* collections.map((c) => 'Iterable<${c.type.basicMappedName}> ${c.name}${c.initializer},');
yield* sets.map((c) => 'Set<${c.type.basicMappedName}> ${c.name}${c.initializer},');
yield '}';
}

yield ') {';

if (hasDefaults.isNotEmpty) {
if (shouldEmitDefaultsSet) {
yield 'if (!_defaultsSet) {';
yield ' _defaultsSet = RealmObjectBase.setDefaults<$name>({';
yield* hasDefaults.map((f) => "'${f.realmName}': ${f.fieldElement.initializerExpression},");
yield* fieldsWithDefaultValue.map((f) => "'${f.realmName}': ${f.fieldElement.initializerExpression},");
yield ' });';
yield '}';
}

yield* allSettable.map((f) {
if (f.type.isUint8List && f.hasDefaultValue) {
return "RealmObjectBase.set(this, '${f.realmName}', ${f.name} ?? ${f.fieldElement.initializerExpression});";
}

return "RealmObjectBase.set(this, '${f.realmName}', ${f.name});";
});

Expand Down
8 changes: 3 additions & 5 deletions generator/test/good_test_data/all_types.expected
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
ObjectId objectId,
Uuid uuid,
Decimal128 decimal, {
Uint8List data = Uint8List(16),
Uint8List? data,
DateTime timestamp = DateTime.now(),
double aDouble = 0.0,
Foo? foo,
Expand All @@ -70,15 +70,14 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
}) {
if (!_defaultsSet) {
_defaultsSet = RealmObjectBase.setDefaults<Bar>({
'data': Uint8List(16),
'tidspunkt': DateTime.now(),
'aDouble': 0.0,
});
}
RealmObjectBase.set(this, 'name', name);
RealmObjectBase.set(this, 'aBool', aBool);
RealmObjectBase.set(this, 'another', another);
RealmObjectBase.set(this, 'data', data);
RealmObjectBase.set(this, 'data', data ?? Uint8List(16));
RealmObjectBase.set(this, 'tidspunkt', timestamp);
RealmObjectBase.set(this, 'aDouble', aDouble);
RealmObjectBase.set(this, 'foo', foo);
Expand All @@ -88,8 +87,7 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
RealmObjectBase.set(this, 'any', any);
RealmObjectBase.set(this, 'decimal', decimal);
RealmObjectBase.set<RealmList<int>>(this, 'list', RealmList<int>(list));
RealmObjectBase.set<RealmList<RealmValue>>(
this, 'manyAny', RealmList<RealmValue>(manyAny));
RealmObjectBase.set<RealmList<RealmValue>>(this, 'manyAny', RealmList<RealmValue>(manyAny));
}

Bar._();
Expand Down
10 changes: 10 additions & 0 deletions generator/test/good_test_data/binary_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'dart:typed_data';

import 'package:realm_common/realm_common.dart';

@RealmModel()
class _Foo {
late Uint8List requiredBinaryProp;
var defaultValueBinaryProp = Uint8List(8);
late Uint8List? nullableBinaryProp;
}
58 changes: 58 additions & 0 deletions generator/test/good_test_data/binary_type.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// **************************************************************************
// RealmObjectGenerator
// **************************************************************************

class Foo extends _Foo with RealmEntity, RealmObjectBase, RealmObject {
Foo(
Uint8List requiredBinaryProp, {
Uint8List? defaultValueBinaryProp,
Uint8List? nullableBinaryProp,
}) {
RealmObjectBase.set(this, 'requiredBinaryProp', requiredBinaryProp);
RealmObjectBase.set(this, 'defaultValueBinaryProp', defaultValueBinaryProp ?? Uint8List(8));
RealmObjectBase.set(this, 'nullableBinaryProp', nullableBinaryProp);
}

Foo._();

@override
Uint8List get requiredBinaryProp =>
RealmObjectBase.get<Uint8List>(this, 'requiredBinaryProp') as Uint8List;
@override
set requiredBinaryProp(Uint8List value) =>
RealmObjectBase.set(this, 'requiredBinaryProp', value);

@override
Uint8List get defaultValueBinaryProp =>
RealmObjectBase.get<Uint8List>(this, 'defaultValueBinaryProp')
as Uint8List;
@override
set defaultValueBinaryProp(Uint8List value) =>
RealmObjectBase.set(this, 'defaultValueBinaryProp', value);

@override
Uint8List? get nullableBinaryProp =>
RealmObjectBase.get<Uint8List>(this, 'nullableBinaryProp') as Uint8List?;
@override
set nullableBinaryProp(Uint8List? value) =>
RealmObjectBase.set(this, 'nullableBinaryProp', value);

@override
Stream<RealmObjectChanges<Foo>> get changes =>
RealmObjectBase.getChanges<Foo>(this);

@override
Foo freeze() => RealmObjectBase.freezeObject<Foo>(this);

static SchemaObject get schema => _schema ??= _initSchema();
static SchemaObject? _schema;
static SchemaObject _initSchema() {
RealmObjectBase.registerFactory(Foo._);
return const SchemaObject(ObjectType.realmObject, Foo, 'Foo', [
SchemaProperty('requiredBinaryProp', RealmPropertyType.binary),
SchemaProperty('defaultValueBinaryProp', RealmPropertyType.binary),
SchemaProperty('nullableBinaryProp', RealmPropertyType.binary,
optional: true),
]);
}
}
106 changes: 55 additions & 51 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2268,7 +2268,7 @@ class _RealmCore {
}

String getBundleId() {
readBundleId () {
readBundleId() {
try {
if (!isFlutterPlatform) {
var pubspecPath = path.join(path.current, 'pubspec.yaml');
Expand Down Expand Up @@ -2907,55 +2907,59 @@ void _intoRealmValue(Object? value, Pointer<realm_value_t> realm_value, Allocato
realm_value.ref.values.link.target = link.targetKey;
realm_value.ref.values.link.target_table = link.classKey;
realm_value.ref.type = realm_value_type.RLM_TYPE_LINK;
} else {
if (value is int) {
realm_value.ref.values.integer = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_INT;
} else if (value is bool) {
realm_value.ref.values.boolean = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_BOOL;
} else if (value is String) {
String string = value;
final units = utf8.encode(string);
final result = allocator<Uint8>(units.length);
final Uint8List nativeString = result.asTypedList(units.length);
nativeString.setAll(0, units);
realm_value.ref.values.string.data = result.cast();
realm_value.ref.values.string.size = units.length;
realm_value.ref.type = realm_value_type.RLM_TYPE_STRING;
} else if (value is double) {
realm_value.ref.values.dnum = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_DOUBLE;
} else if (value is ObjectId) {
final bytes = value.bytes;
for (var i = 0; i < 12; i++) {
realm_value.ref.values.object_id.bytes[i] = bytes[i];
}
realm_value.ref.type = realm_value_type.RLM_TYPE_OBJECT_ID;
} else if (value is Uuid) {
final bytes = value.bytes.asUint8List();
for (var i = 0; i < 16; i++) {
realm_value.ref.values.uuid.bytes[i] = bytes[i];
}
realm_value.ref.type = realm_value_type.RLM_TYPE_UUID;
} else if (value is DateTime) {
final microseconds = value.toUtc().microsecondsSinceEpoch;
final seconds = microseconds ~/ _microsecondsPerSecond;
int nanoseconds = _nanosecondsPerMicrosecond * (microseconds % _microsecondsPerSecond);
if (microseconds < 0 && nanoseconds != 0) {
nanoseconds = nanoseconds - _nanosecondsPerMicrosecond * _microsecondsPerSecond;
}
realm_value.ref.values.timestamp.seconds = seconds;
realm_value.ref.values.timestamp.nanoseconds = nanoseconds;
realm_value.ref.type = realm_value_type.RLM_TYPE_TIMESTAMP;
} else if (value is RealmValue) {
return _intoRealmValue(value.value, realm_value, allocator);
} else if (value is Decimal128) {
realm_value.ref.values.decimal128 = value.value;
realm_value.ref.type = realm_value_type.RLM_TYPE_DECIMAL128;
} else {
throw RealmException("Property type ${value.runtimeType} not supported");
}
} else if (value is int) {
realm_value.ref.values.integer = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_INT;
} else if (value is bool) {
realm_value.ref.values.boolean = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_BOOL;
} else if (value is String) {
String string = value;
final units = utf8.encode(string);
final result = allocator<Uint8>(units.length);
final Uint8List nativeString = result.asTypedList(units.length);
nativeString.setAll(0, units);
realm_value.ref.values.string.data = result.cast();
realm_value.ref.values.string.size = units.length;
realm_value.ref.type = realm_value_type.RLM_TYPE_STRING;
} else if (value is double) {
realm_value.ref.values.dnum = value;
realm_value.ref.type = realm_value_type.RLM_TYPE_DOUBLE;
} else if (value is ObjectId) {
final bytes = value.bytes;
for (var i = 0; i < 12; i++) {
realm_value.ref.values.object_id.bytes[i] = bytes[i];
}
realm_value.ref.type = realm_value_type.RLM_TYPE_OBJECT_ID;
} else if (value is Uuid) {
final bytes = value.bytes.asUint8List();
for (var i = 0; i < 16; i++) {
realm_value.ref.values.uuid.bytes[i] = bytes[i];
}
realm_value.ref.type = realm_value_type.RLM_TYPE_UUID;
} else if (value is DateTime) {
final microseconds = value.toUtc().microsecondsSinceEpoch;
final seconds = microseconds ~/ _microsecondsPerSecond;
int nanoseconds = _nanosecondsPerMicrosecond * (microseconds % _microsecondsPerSecond);
if (microseconds < 0 && nanoseconds != 0) {
nanoseconds = nanoseconds - _nanosecondsPerMicrosecond * _microsecondsPerSecond;
}
realm_value.ref.values.timestamp.seconds = seconds;
realm_value.ref.values.timestamp.nanoseconds = nanoseconds;
realm_value.ref.type = realm_value_type.RLM_TYPE_TIMESTAMP;
} else if (value is RealmValue) {
return _intoRealmValue(value.value, realm_value, allocator);
} else if (value is Decimal128) {
realm_value.ref.values.decimal128 = value.value;
realm_value.ref.type = realm_value_type.RLM_TYPE_DECIMAL128;
} else if (value is Uint8List) {
realm_value.ref.type = realm_value_type.RLM_TYPE_BINARY;
realm_value.ref.values.binary.size = value.length;
realm_value.ref.values.binary.data = allocator<Uint8>(value.length);
realm_value.ref.values.binary.data.asTypedList(value.length).setAll(0, value);
}
else {
throw RealmException("Property type ${value.runtimeType} not supported");
}
}

Expand Down Expand Up @@ -2984,7 +2988,7 @@ extension on Pointer<realm_value_t> {
if (realm.metadata.getByClassKeyIfExists(classKey) == null) return null; // temprorary workaround to avoid crash on assertion
return realmCore._getObject(realm, classKey, objectKey);
case realm_value_type.RLM_TYPE_BINARY:
throw Exception("Not implemented");
return Uint8List.fromList(ref.values.binary.data.asTypedList(ref.values.binary.size));
case realm_value_type.RLM_TYPE_TIMESTAMP:
final seconds = ref.values.timestamp.seconds;
final nanoseconds = ref.values.timestamp.nanoseconds;
Expand Down
Loading