Skip to content

Commit

Permalink
Fix RealmObject.hashCode
Browse files Browse the repository at this point in the history
  • Loading branch information
nirinchev committed Oct 30, 2023
1 parent d5aaf25 commit b50667c
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

### Fixed
* Fixed iteration after `skip` bug ([#1409](https://github.com/realm/realm-dart/issues/1409))
* Fixed RealmObject not overriding `hashCode`, which would lead to sets of RealmObjects potentially containing duplicates. ([#1418](https://github.com/realm/realm-dart/issues/1418))

### Compatibility
* Realm Studio: 13.0.0 or later.
Expand Down
9 changes: 9 additions & 0 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,15 @@ class _RealmCore {
bool userEquals(User first, User second) => _equals(first.handle, second.handle);
bool subscriptionEquals(Subscription first, Subscription second) => _equals(first.handle, second.handle);

int objectGetHashCode(RealmObjectBase value) {
final link = realmCore._getObjectAsLink(value);

var hashCode = -986587137;
hashCode = (hashCode * -1521134295) + link.classKey;
hashCode = (hashCode * -1521134295) + link.targetKey;
return hashCode;
}

RealmResultsHandle resultsSnapshot(RealmResults results) {
final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_snapshot(results.handle._pointer));
return RealmResultsHandle._(resultsPointer, results.realm.handle);
Expand Down
12 changes: 12 additions & 0 deletions lib/src/realm_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,21 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab
if (identical(this, other)) return true;
if (other is! RealmObjectBase) return false;
if (!isManaged || !other.isManaged) return false;

return realmCore.objectEquals(this, other);
}

late final int _managedHashCode = realmCore.objectGetHashCode(this);

@override
int get hashCode {
if (!isManaged) {
return super.hashCode;
}

return _managedHashCode;
}

/// Gets a value indicating whether this object is managed and represents a row in the database.
///
/// If a managed object has been removed from the [Realm], it is no longer valid and accessing properties on it
Expand Down
99 changes: 98 additions & 1 deletion test/realm_object_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
// ignore_for_file: unused_local_variable, avoid_relative_lib_imports

import 'dart:io';
import 'dart:typed_data';
import 'package:test/test.dart' hide test, throws;
import '../lib/realm.dart';
Expand Down Expand Up @@ -723,4 +722,102 @@ Future<void> main([List<String>? args]) async {
if (count > 1) fail('Should only receive one event');
}
});

test('RealmObject read deleted object properties', () {
var config = Configuration.local([Team.schema, Person.schema]);
var realm = getRealm(config);

var team = Team("TeamOne");
realm.write(() => realm.add(team));
var teams = realm.all<Team>();
var teamBeforeDelete = teams[0];
realm.write(() => realm.delete(team));
expect(team.isValid, false);
expect(teamBeforeDelete.isValid, false);
expect(team, teamBeforeDelete);
expect(() => team.name, throws<RealmException>("Accessing object of type Team which has been invalidated or deleted"));
expect(() => teamBeforeDelete.name, throws<RealmException>("Accessing object of type Team which has been invalidated or deleted"));
});

test('RealmObject.hashCode changes after adding to Realm', () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

final team = Team("TeamOne");

final unmanagedHash = team.hashCode;

realm.write(() => realm.add(team));

final managedHash = team.hashCode;

expect(managedHash, isNot(unmanagedHash));
expect(managedHash, equals(team.hashCode));
});

test('RealmObject.hashCode is different for different objects', () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

final a = Team("a");
final b = Team("b");

expect(a.hashCode, isNot(b.hashCode));

realm.write(() {
realm.add(a);
realm.add(b);
});

expect(a.hashCode, isNot(b.hashCode));
});

test('RealmObject.hashCode is same for equal objects', () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

final team = Team("TeamOne");

realm.write(() {
realm.add(team);
});

final teamAgain = realm.all<Team>().first;

expect(team.hashCode, equals(teamAgain.hashCode));
});

test('RealmObject.hashCode remains stable after deletion', () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

final team = Team("TeamOne");

realm.write(() {
realm.add(team);
});

final teamAgain = realm.all<Team>().first;

final managedHash = team.hashCode;

realm.write(() => realm.delete(team));

expect(team.hashCode, equals(managedHash)); // Object that was just deleted shouldn't change its hash code
expect(teamAgain.hashCode, equals(managedHash)); // Object that didn't hash its hash code and its row got deleted should still have the same hash code
});

test("RealmObject when added to set doesn't have duplicates", () {
final config = Configuration.local([Team.schema, Person.schema]);
final realm = getRealm(config);

realm.write(() {
realm.add(Team("TeamOne"));
});

final setOne = realm.all<Team>().toSet();
final setTwo = realm.all<Team>().toSet();

expect(setOne.difference(setTwo).length, 0);
});
}

0 comments on commit b50667c

Please sign in to comment.