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

Realm Set support #1102

Merged
merged 29 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
aa0d7e9
basic managed realm set
blagoev Jan 13, 2023
6483033
add tests
blagoev Jan 14, 2023
a07edd9
more tests
blagoev Jan 14, 2023
46070ea
add iterator and test
blagoev Jan 14, 2023
a13a04d
add notifications support and tests
blagoev Jan 14, 2023
655c29f
addd API docs where needed
blagoev Jan 14, 2023
85d377e
fix api docs
blagoev Jan 14, 2023
9735b09
add DateTime, ObjectId, UUID tests
blagoev Jan 14, 2023
0ce8170
add nullable types tests
blagoev Jan 14, 2023
9b84db6
add command for debugging Generator tests
blagoev Jan 14, 2023
a60d6b9
add Set support to generator
blagoev Jan 15, 2023
045811a
fix generated code
blagoev Jan 15, 2023
3d909dd
Merge branch 'main' into blagoev/realm_set
blagoev Jan 15, 2023
aa30d76
update CHANGELOG
blagoev Jan 15, 2023
e7d862f
remove dead code
blagoev Jan 15, 2023
4c362e8
fix generator tests
blagoev Jan 15, 2023
e318b6d
fix expected files
blagoev Jan 15, 2023
93a1eba
fix RealmValue tests
blagoev Jan 15, 2023
fa823d5
support Set<RealmValue>
blagoev Jan 15, 2023
24a676d
format gen set file
blagoev Jan 15, 2023
9ae1f55
fix expected file
blagoev Jan 16, 2023
b8e94a6
support RealmObject type and add tests
blagoev Jan 16, 2023
19a45a7
added realm set to generator and generator tests
blagoev Jan 16, 2023
ef60a64
fix expected generated files
blagoev Jan 16, 2023
816aec2
add RealmSet.deleteAll() implementaiton and test
blagoev Jan 16, 2023
9338e5c
remove dead comment
blagoev Jan 16, 2023
72870cf
handle PR feedback
blagoev Jan 18, 2023
107fabb
Handle PR feedback. Fixes for RealmValue<RealmObject> handling
blagoev Jan 20, 2023
fe8f550
handle PR review
blagoev Jan 20, 2023
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
11 changes: 10 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
"program": "test/realm_test.dart",
"cwd": "${workspaceFolder}",
"args": ["--name", ""]
}
},
{
"name": "Debug Generator Tests",
"type": "dart",
"request": "launch",
// "program": "test/good_test.dart",
"program": "test/error_test.dart",
"args": ["-p", "vm", "-n", "unsupported_realm_set_with_default_values.dart"],
"cwd": "generator",
},
]
}
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
**This project is in Release Candidate stage.**

### Enhancements
* None
* Add supoprt for Realm set data type ([#1102](https://github.com/realm/realm-dart/pull/1102))

### Fixed
* None
* Added an error for default values for Realm object references in the Realm generator ([#1102](https://github.com/realm/realm-dart/pull/1102))

### Compatibility
* Realm Studio: 13.0.0 or later.
Expand Down
1 change: 1 addition & 0 deletions common/lib/src/realm_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ enum RealmCollectionType {
none,
list,
set,
_3, // ignore: unused_field, constant_identifier_names
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
dictionary,
}

Expand Down
2 changes: 2 additions & 0 deletions flutter/realm_flutter/tests/test_driver/realm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import '../test/session_test.dart' as session_test;
import '../test/subscription_test.dart' as subscription_test;
import '../test/user_test.dart' as user_test;
import '../test/client_reset_test.dart' as client_reset_test;
import '../test/realm_set_test.dart' as realm_set_test;

Future<String> main(List<String> args) async {
final Completer<String> completer = Completer<String>();
Expand All @@ -41,6 +42,7 @@ Future<String> main(List<String> args) async {
await subscription_test.main(args);
await user_test.main(args);
await client_reset_test.main(args);
await realm_set_test.main(args);

tearDown(() {
if (Invoker.current?.liveTest.state.result == test_api.Result.error || Invoker.current?.liveTest.state.result == test_api.Result.failure) {
Expand Down
8 changes: 1 addition & 7 deletions generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@ class _Car {

# Debugging

* On first use .dart_tool/build/entrypoint/build.dart needs to be generated with pub run build_runer build

* Use a terminal to launch a debuggee with command

```
dart run --observe --pause-isolates-on-start --enable-vm-service:5858/127.0.0.1 --disable-service-auth-codes .dart_tool/build/entrypoint/build.dart build
```
* Use the Debug Generator Tests launch config in VS Code. The launch config also has an arg for filtering specifc tests.

##### The "Dart" name and logo and the "Flutter" name and logo are trademarks owned by Google.
2 changes: 1 addition & 1 deletion generator/lib/src/dart_type_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ extension DartTypeEx on DartType {

bool get isRealmValue => const TypeChecker.fromRuntime(RealmValue).isAssignableFromType(this);
bool get isRealmCollection => realmCollectionType != RealmCollectionType.none;
bool get isRealmSet => realmCollectionType == RealmCollectionType.set;
bool get isRealmModel => element2 != null ? realmModelChecker.annotationsOfExact(element2!).isNotEmpty : false;

bool get isNullable => session.typeSystem.isNullable(this);
Expand Down Expand Up @@ -110,7 +111,6 @@ extension DartTypeEx on DartType {
if (isExactly<Uint8List>()) return RealmPropertyType.binary;
if (isRealmValue) return RealmPropertyType.mixed;
if (isExactly<DateTime>()) return RealmPropertyType.timestamp;
if (isExactly<Float>()) return RealmPropertyType.float;
if (isDartCoreNum || isDartCoreDouble) return RealmPropertyType.double;
if (isExactly<Decimal128>()) return RealmPropertyType.decimal128;
if (isRealmModel) return RealmPropertyType.object;
Expand Down
55 changes: 51 additions & 4 deletions generator/lib/src/field_element_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import 'session.dart';
import 'type_checkers.dart';

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

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

AnnotationValue? get ignoredInfo => annotationInfoOfExact(ignoredChecker);
Expand All @@ -59,6 +61,14 @@ extension FieldElementEx on FieldElement {
[span!],
);

FileSpan? initializerExpressionSpan(SourceFile file, Expression initializerExpression) => ExpandedContextSpan(
ExpandedContextSpan(
(initializerExpression).span(file),
[span!],
),
[span!],
);

DartType get modelType => typeAnnotation?.type?.nullIfDynamic ?? initializerExpression?.staticType ?? PseudoType(typeAnnotation.toString());

String get modelTypeName => modelType.getDisplayString(withNullability: true);
Expand All @@ -82,9 +92,7 @@ extension FieldElementEx on FieldElement {
final backlink = backlinkInfo;

// Check for as-of-yet unsupported type
if (type.isDartCoreSet || //
type.isDartCoreMap ||
type.isExactly<Decimal128>()) {
if (type.isDartCoreMap || type.isExactly<Decimal128>()) {
throw RealmInvalidGenerationSourceError(
'Field type not supported yet',
element: this,
Expand Down Expand Up @@ -186,7 +194,7 @@ extension FieldElementEx on FieldElement {
} else {
// Validate collections and backlinks
if (type.isRealmCollection || backlink != null) {
final typeDescription = type.isRealmCollection ? 'collections' : 'backlinks';
final typeDescription = type.isRealmCollection ? (type.isRealmSet ? 'sets' : 'collections') : 'backlinks';
if (type.isNullable) {
throw RealmInvalidGenerationSourceError(
'Realm $typeDescription cannot be nullable',
Expand All @@ -204,6 +212,34 @@ extension FieldElementEx on FieldElement {
element: this,
todo: 'Ensure element type is non-nullable');
}

if (type.isRealmSet) {
final typeArgument = (type as ParameterizedType).typeArguments.first;
if (realmSetUnsupportedRealmTypes.contains(realmType)) {
throw RealmInvalidGenerationSourceError('$type is not supported',
primarySpan: typeSpan(file),
primaryLabel: 'Set element type is not supported',
element: this,
todo: 'Ensure set element type ${typeArgument} is a type supported by RealmSet.');
}

if (realmType == RealmPropertyType.mixed && typeArgument.isNullable) {
throw RealmInvalidGenerationSourceError('$type is not supported',
primarySpan: typeSpan(file),
primaryLabel: 'Set of nullable RealmValues is not supported',
element: this,
todo: 'Did you mean to use Set<RealmValue> instead?');
}

final initExpression = initializerExpression;
if (initExpression != null) {
throw RealmInvalidGenerationSourceError('Default values for set are not supported.',
primarySpan: initializerExpressionSpan(file, initExpression),
primaryLabel: 'Remove the default value.',
element: this,
todo: 'Remove the default value for field $displayName.');
}
}
}

// Validate backlinks
Expand Down Expand Up @@ -260,6 +296,17 @@ extension FieldElementEx on FieldElement {
element: this,
);
}

final intiExpression = initializerExpression;
if (intiExpression != null) {
throw RealmInvalidGenerationSourceError(
'Realm object references should not have default values',
primarySpan: initializerExpressionSpan(file, intiExpression),
primaryLabel: ' Remove the default value',
todo: 'Remove the default value for field "$displayName"',
element: this,
);
}
}

// Validate mixed (RealmValue)
Expand Down
4 changes: 3 additions & 1 deletion generator/lib/src/realm_field_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class RealmFieldInfo {

bool get isFinal => fieldElement.isFinal;
bool get isRealmCollection => type.isRealmCollection;
bool get isDartCoreSet => type.isDartCoreSet;
bool get isLate => fieldElement.isLate;
bool get hasDefaultValue => fieldElement.hasInitializer;
bool get optional => type.basicType.isNullable || realmType == RealmPropertyType.mixed;
Expand All @@ -68,8 +69,9 @@ class RealmFieldInfo {

String get initializer {
if (type.isDartCoreList) return ' = const []';
if (isMixed) return ' = const RealmValue.nullValue()';
if (isMixed && !type.isRealmCollection) return ' = const RealmValue.nullValue()';
if (hasDefaultValue) return ' = ${fieldElement.initializerExpression}';
if (type.isDartCoreSet) return ' = const {}';
return ''; // no initializer
}

Expand Down
10 changes: 8 additions & 2 deletions generator/lib/src/realm_model_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ class RealmModelInfo {
yield* required.map((f) => '${f.mappedTypeName} ${f.name},');

final notRequired = allSettable.where((f) => !f.isRequired && !f.isPrimaryKey);
final collections = fields.where((f) => f.isRealmCollection).toList();
if (notRequired.isNotEmpty || collections.isNotEmpty) {
final collections = fields.where((f) => f.isRealmCollection && !f.isDartCoreSet).toList();
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* collections.map((c) => 'Iterable<${c.type.basicMappedName}> ${c.name}${c.initializer},');
yield* sets.map((c) => 'Set<${c.type.basicMappedName}> ${c.name}${c.initializer},');
yield '}';
}

Expand All @@ -73,6 +75,10 @@ class RealmModelInfo {
yield* collections.map((c) {
return "RealmObjectBase.set<${c.mappedTypeName}>(this, '${c.realmName}', ${c.mappedTypeName}(${c.name}));";
});

yield* sets.map((c) {
return "RealmObjectBase.set<${c.mappedTypeName}>(this, '${c.realmName}', ${c.mappedTypeName}(${c.name}));";
});
}
yield '}';
yield '';
Expand Down
7 changes: 4 additions & 3 deletions generator/test/error_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ void main() {
const directory = 'test/error_test_data';
getListOfTestFiles(directory).forEach((inputFile, outputFile) {
executeTest(getTestName(inputFile), () async {
final expectedContent = await readFileAsErrorFormattedString(directory, outputFile);
expectLater(
generatorTestBuilder(directory, inputFile),
throwsA(
isA<RealmInvalidGenerationSourceError>().having(
(e) => e.format(),
'format()',
await readFileAsErrorFormattedString(directory, outputFile),
'',
LinesEqualsMatcher(expectedContent),
),
),
);
});
});
}
}
11 changes: 11 additions & 0 deletions generator/test/error_test_data/nullable_set.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:realm_common/realm_common.dart';

//part 'nullable_set.g.dart';

@RealmModel()
class _Bad {
@PrimaryKey()
late int id;

Set<int>? wrong;
}
11 changes: 11 additions & 0 deletions generator/test/error_test_data/nullable_set.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Realm sets cannot be nullable

in: asset:pkg/test/error_test_data/nullable_set.dart:10:3
5 │ @RealmModel()
6 │ class _Bad {
│ ━━━━ in realm model for 'Bad'
... │
10 │ Set<int>? wrong;
│ ^^^^^^^^^ is nullable
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'package:realm_common/realm_common.dart';
import 'dart:typed_data';

//part 'set_unsupported.g.dart';
import 'package:realm_common/realm_common.dart';

@RealmModel()
class _Person {
late Set<_Person> children;
int x = 0;
late _Person? parent = Person();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Realm object references should not have default values

in: asset:pkg/test/error_test_data/realm_object_reference_default_values.dart:8:26
5 │ @RealmModel()
6 │ class _Person {
│ ━━━━━━━ in realm model for 'Person'
... │
8 │ late _Person? parent = Person();
│ ^^^^^^^^ Remove the default value
Remove the default value for field "parent"

12 changes: 0 additions & 12 deletions generator/test/error_test_data/set_unsupported.expected

This file was deleted.

11 changes: 11 additions & 0 deletions generator/test/error_test_data/unsupported_non_realm_type_set.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:realm_common/realm_common.dart';

//part 'unsupported_non_realm_type_set.g.dart';

@RealmModel()
class _Bad {
@PrimaryKey()
late int id;

late Set<RealmStateError> wrong;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Not a realm type

in: asset:pkg/test/error_test_data/unsupported_non_realm_type_set.dart:10:8
5 │ @RealmModel()
6 │ class _Bad {
│ ━━━━ in realm model for 'Bad'
... │
10 │ late Set<RealmStateError> wrong;
│ ^^^^^^^^^^^^^^^^^^^^ Set<RealmStateError> is not a realm model type
Remove the invalid field or add an @Ignored annotation on 'wrong'.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:realm_common/realm_common.dart';

//part 'unsupported_realm_set_of_nullable_realmvalue.g.dart';

@RealmModel()
class _Bad {
@PrimaryKey()
late int id;

late Set<_Bad?> wrong1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Nullable realm objects are not allowed in sets

in: asset:pkg/test/error_test_data/unsupported_realm_set_of_nullable_realmobject.dart:10:8
5 │ @RealmModel()
6 │ class _Bad {
│ ━━━━ in realm model for 'Bad'
... │
10 │ late Set<_Bad?> wrong1;
│ ^^^^^^^^^^ which has a nullable realm object element type
Ensure element type is non-nullable

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:realm_common/realm_common.dart';

//part 'unsupported_realm_set_of_nullable_realmvalue.g.dart';

@RealmModel()
class _Bad {
@PrimaryKey()
late int id;

late Set<RealmValue?> wrong1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Set<RealmValue?> is not supported

in: asset:pkg/test/error_test_data/unsupported_realm_set_of_nullable_realmvalue.dart:10:8
5 │ @RealmModel()
6 │ class _Bad {
│ ━━━━ in realm model for 'Bad'
... │
10 │ late Set<RealmValue?> wrong1;
│ ^^^^^^^^^^^^^^^^ Set of nullable RealmValues is not supported
Did you mean to use Set<RealmValue> instead?

Loading