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

Compensating write error details #1291

Merged
merged 78 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
e8219f2
reading ompensating writers
desistefanova May 18, 2023
9bcd009
Merge branch 'main' into ds/compensating_write_error_details
desistefanova May 25, 2023
0fe2315
Merge branch 'main' into ds/compensating_write_error_details
desistefanova May 25, 2023
0256377
SyncError details implementation
desistefanova May 26, 2023
41d9189
Extend the test for compensatingWrite
desistefanova May 26, 2023
60feb81
update changelog
desistefanova May 26, 2023
3662acd
ClientResetError details
desistefanova May 26, 2023
5064f1b
Fix the message text
desistefanova May 26, 2023
444eda9
Update changelog
desistefanova May 26, 2023
6b0c5d5
Detailed message added
desistefanova May 26, 2023
6b457b2
Update changelog
desistefanova May 26, 2023
79bde07
Refactor realm_core methods
desistefanova May 26, 2023
b381db8
fix changelog
desistefanova May 26, 2023
c140f05
Code review changes
desistefanova May 30, 2023
64df956
Update lib/src/native/realm_core.dart
desistefanova May 30, 2023
b11d845
function renamed
desistefanova May 30, 2023
744190a
Code review changes
desistefanova May 30, 2023
4cd98a7
Code review changes
desistefanova May 30, 2023
4f249c8
ObjectId bytes size constant
desistefanova May 30, 2023
1b95699
Code review changes
desistefanova May 30, 2023
a2eb435
Fix the native build
desistefanova May 30, 2023
ea068bd
Merge branch 'main' into ds/compensating_write_error_details
desistefanova May 30, 2023
a5be0e7
Update changelog
desistefanova May 30, 2023
c37d061
Fix Instance(length:17) of '_GrowableList'
desistefanova May 30, 2023
cebbea1
Fix toDartValue function
desistefanova Jun 1, 2023
8778292
Fix toDartValue function
desistefanova Jun 1, 2023
4b2608b
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 2, 2023
264c276
Fix after merge
desistefanova Jun 2, 2023
6c8a2df
Update CHANGELOG.md
desistefanova Jun 5, 2023
42fe489
Update CHANGELOG.md
desistefanova Jun 5, 2023
3e69943
Update CHANGELOG.md
desistefanova Jun 5, 2023
a6d4bd7
Update lib/src/configuration.dart
desistefanova Jun 5, 2023
c86c20e
Code review changes
desistefanova Jun 5, 2023
f2aa4a3
Merge branch 'ds/compensating_write_error_details' of https://github.…
desistefanova Jun 5, 2023
60566f9
detailedMessage named parameter for base class
desistefanova Jun 5, 2023
ac7bc51
Code review changes
desistefanova Jun 5, 2023
7589f22
Code review changes
desistefanova Jun 5, 2023
2ea9b5f
Code review changes
desistefanova Jun 5, 2023
59c09ab
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 7, 2023
ef46439
Move SyncError.create to _RealmCore.dart
desistefanova Jun 7, 2023
08ac1c1
Code review changes
desistefanova Jun 7, 2023
268aa96
Changelog update
desistefanova Jun 7, 2023
0a3e08a
Update changelog
desistefanova Jun 7, 2023
12d32d6
Force update
desistefanova Jun 7, 2023
953332c
force update
desistefanova Jun 7, 2023
f453b9b
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 8, 2023
f16a92a
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 9, 2023
5d999a1
Fix ClientResetError
desistefanova Jun 12, 2023
789fcde
Code review changes
desistefanova Jun 15, 2023
36e1b82
Update test/client_reset_test.dart
desistefanova Jun 15, 2023
e7d36e5
Update lib/src/configuration.dart
desistefanova Jun 15, 2023
fb45fcd
Update CHANGELOG.md
desistefanova Jun 15, 2023
275adfe
fix ClientResetError params
desistefanova Jun 15, 2023
987bdc7
Merge branch 'ds/compensating_write_error_details' of https://github.…
desistefanova Jun 15, 2023
d13044c
Refactor _createSyncError
desistefanova Jun 16, 2023
8fd315e
Automatic client reset and session errors
desistefanova Jun 16, 2023
76bf609
Update changelog and toUserInfo
desistefanova Jun 16, 2023
fd67dd5
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 16, 2023
b85af1b
properties reordered
desistefanova Jun 16, 2023
6df68f8
rename a class
desistefanova Jun 16, 2023
56d0c7d
Removed breacking changes
desistefanova Jun 19, 2023
8581373
Fix API text
desistefanova Jun 19, 2023
a90e31a
Fixes
desistefanova Jun 19, 2023
3251403
retuned old code of deprecated method
desistefanova Jun 19, 2023
f38798e
Code formatting
desistefanova Jun 19, 2023
6cc0e07
Fix tests
desistefanova Jun 19, 2023
ac7aacf
Nullable compensatingWrites
desistefanova Jun 19, 2023
35c8a36
Simplify getting recoveryFilePathKey
desistefanova Jun 19, 2023
b17a183
Introduce breaking changes only in the the `ClientResetError` constru…
desistefanova Jun 20, 2023
1f8305a
Merge branch 'ds/compensating_write_error_details' of https://github.…
desistefanova Jun 20, 2023
63a2e63
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 20, 2023
02906b9
Fix errors after merge
desistefanova Jun 20, 2023
6c2d70b
Deprecate SyncError constructors
desistefanova Jun 20, 2023
c790b64
Update changelog
desistefanova Jun 20, 2023
1d6d787
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 20, 2023
7a61816
Update lib/src/configuration.dart
desistefanova Jun 21, 2023
0a97ebb
Merge branch 'main' into ds/compensating_write_error_details
desistefanova Jun 21, 2023
b5be0e8
Remove redundant files
desistefanova Jun 21, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## vNext (TBD)

### Enhancements

* Added `ClientResetError.backupFilePath` where the backup copy of the realm will be placed once the client reset process is complete ([#1291](https://github.com/realm/realm-dart/pull/1291)).
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
* Added `CompensatingWriteError` that contains detailed information about the writes that have been reverted by the server due to permissions or subscription view restrictions. It will be received on `syncErrorHandle` callbak, which is set to `Configuration.flexibleSync`, similarly to other session errors ([#1291](https://github.com/realm/realm-dart/pull/1291)).
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
* Added `SyncError.detailedMessage` that contains error details. In case of server error, it contains the link to the server log ([#1291](https://github.com/realm/realm-dart/pull/1291)).
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
* Add `RealmResults.isValid` ([#1231](https://github.com/realm/realm-dart/pull/1231)).
* Support `Decimal128` datatype ([#1192](https://github.com/realm/realm-dart/pull/1192)).
* Realm logging is extended to support logging of all Realm storage level messages. (Core upgrade).
Expand Down
112 changes: 94 additions & 18 deletions lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,20 @@ class ClientResetError extends SyncError {
/// The [ClientResetError] has error code of [SyncClientErrorCode.autoClientResetFailure]
SyncClientErrorCode get code => SyncClientErrorCode.autoClientResetFailure;

ClientResetError(String message, [this._config]) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.code);
/// The path to the original realm file.
String get _originalFilePath => _userInfo?["ORIGINAL_FILE_PATH"] ?? "";
desistefanova marked this conversation as resolved.
Show resolved Hide resolved

/// The path where the backup copy of the realm will be placed once the client reset process is complete.
String get backupFilePath => _userInfo?["RECOVERY_FILE_PATH"] ?? "";
desistefanova marked this conversation as resolved.
Show resolved Hide resolved

final Map<String, String>? _userInfo;

ClientResetError(
String message,
String detailedMessage, [
this._config,
this._userInfo,
]) : super(message, detailedMessage, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.code);

@override
String toString() {
Expand All @@ -622,6 +635,9 @@ class ClientResetError extends SyncError {
if (_config is! FlexibleSyncConfiguration) {
throw RealmException("The current configuration is not FlexibleSyncConfiguration.");
}
if (_originalFilePath != _config?.path) {
blagoev marked this conversation as resolved.
Show resolved Hide resolved
throw RealmException("The current configuration does not match the original realm file path.");
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
}
final flexibleConfig = _config as FlexibleSyncConfiguration;
return realmCore.immediatelyRunFileActions(flexibleConfig.user.app, flexibleConfig.path);
}
Expand All @@ -636,27 +652,27 @@ class SyncError extends RealmError {
/// The category of the sync error
final SyncErrorCategory category;

SyncError(String message, this.category, this.codeValue) : super(message);
/// Detailed error message.
/// In case of server error, it contains the link to the server log.
final String detailedMessage;

SyncError(String message, this.detailedMessage, this.category, this.codeValue) : super(message);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved

/// Creates a specific type of [SyncError] instance based on the [category] and the [code] supplied.
static SyncError create(String message, SyncErrorCategory category, int code, {bool isFatal = false}) {
static SyncError create(String message, String detailedMessage, SyncErrorCategory category, int code, {bool isFatal = false}) {
switch (category) {
case SyncErrorCategory.client:
final SyncClientErrorCode errorCode = SyncClientErrorCode.fromInt(code);
if (errorCode == SyncClientErrorCode.autoClientResetFailure) {
return ClientResetError(message);
}
return SyncClientError(message, category, errorCode, isFatal: isFatal);
return SyncClientError(message, detailedMessage, category, SyncClientErrorCode.fromInt(code), isFatal: isFatal);
case SyncErrorCategory.connection:
return SyncConnectionError(message, category, SyncConnectionErrorCode.fromInt(code), isFatal: isFatal);
return SyncConnectionError(message, detailedMessage, category, SyncConnectionErrorCode.fromInt(code), isFatal: isFatal);
case SyncErrorCategory.session:
return SyncSessionError(message, category, SyncSessionErrorCode.fromInt(code), isFatal: isFatal);
return SyncSessionError(message, detailedMessage, category, SyncSessionErrorCode.fromInt(code), isFatal: isFatal);
case SyncErrorCategory.webSocket:
return SyncWebSocketError(message, category, SyncWebSocketErrorCode.fromInt(code));
return SyncWebSocketError(message, detailedMessage, category, SyncWebSocketErrorCode.fromInt(code));
case SyncErrorCategory.system:
case SyncErrorCategory.unknown:
default:
return GeneralSyncError(message, category, code);
return GeneralSyncError(message, detailedMessage, category, code);
}
}

Expand All @@ -680,10 +696,11 @@ class SyncClientError extends SyncError {

SyncClientError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncClientErrorCode errorCode, {
this.isFatal = false,
}) : super(message, category, errorCode.code);
}) : super(message, detailedMessage, category, errorCode.code);

@override
String toString() {
Expand All @@ -702,10 +719,11 @@ class SyncConnectionError extends SyncError {

SyncConnectionError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncConnectionErrorCode errorCode, {
this.isFatal = false,
}) : super(message, category, errorCode.code);
}) : super(message, detailedMessage, category, errorCode.code);

@override
String toString() {
Expand All @@ -724,10 +742,11 @@ class SyncSessionError extends SyncError {

SyncSessionError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncSessionErrorCode errorCode, {
this.isFatal = false,
}) : super(message, category, errorCode.code);
}) : super(message, detailedMessage, category, errorCode.code);

@override
String toString() {
Expand All @@ -744,7 +763,12 @@ class SyncResolveError extends SyncError {
/// The numeric value indicating the type of the network resolution sync error.
SyncResolveErrorCode get code => SyncResolveErrorCode.fromInt(codeValue);

SyncResolveError(String message, SyncErrorCategory category, SyncResolveErrorCode errorCode) : super(message, category, errorCode.index);
SyncResolveError(
blagoev marked this conversation as resolved.
Show resolved Hide resolved
String message,
String detailedMessage,
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
SyncErrorCategory category,
SyncResolveErrorCode errorCode,
) : super(message, detailedMessage, category, errorCode.index);

@override
String toString() {
Expand All @@ -757,7 +781,12 @@ class SyncWebSocketError extends SyncError {
/// The numeric value indicating the type of the web socket error.
SyncWebSocketErrorCode get code => SyncWebSocketErrorCode.fromInt(codeValue);

SyncWebSocketError(String message, SyncErrorCategory category, SyncWebSocketErrorCode errorCode) : super(message, category, errorCode.code);
SyncWebSocketError(
String message,
String detailedMessage,
SyncErrorCategory category,
SyncWebSocketErrorCode errorCode,
) : super(message, detailedMessage, category, errorCode.code);

@override
String toString() {
Expand All @@ -770,7 +799,12 @@ class GeneralSyncError extends SyncError {
/// The numeric value indicating the type of the general sync error.
int get code => codeValue;

GeneralSyncError(String message, SyncErrorCategory category, int code) : super(message, category, code);
GeneralSyncError(
String message,
String detailedMessage,
SyncErrorCategory category,
int code,
) : super(message, detailedMessage, category, code);

@override
String toString() {
Expand All @@ -792,3 +826,45 @@ enum GeneralSyncErrorCode {
final int code;
const GeneralSyncErrorCode(this.code);
}

/// A class containing the details for a compensating write performed by the server.
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
class CompensatingWriteInfo {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
CompensatingWriteInfo(this.objectType, this.reason, this.primaryKey);

/// The type of the object which was affected by the compensating write.
final String objectType;

/// The reason for the server to perform a compensating write.
final String reason;

/// The primary key of the object which was affected by the compensating write.
final RealmValue primaryKey;

@override
String toString() {
return "CompensatingWriteInfo: objectType: $objectType\n reason: $reason\n primaryKey: $primaryKey\n";
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// An error type that describes a compensating write error,
/// which indicates that one more object changes have been reverted
/// by the server.
/// {@category Sync}
class CompensatingWriteError extends SyncError {
/// The [CompensatingWriteError] has error code of [SyncSessionErrorCode.compensatingWrite]
SyncSessionErrorCode get code => SyncSessionErrorCode.compensatingWrite;

/// The list of the compensating writes performed by the server.
late final List<CompensatingWriteInfo> compensatingWrites;

CompensatingWriteError(
String message,
String detailedMessage,
this.compensatingWrites,
) : super(message, detailedMessage, SyncErrorCategory.session, SyncSessionErrorCode.compensatingWrite.code);

@override
String toString() {
return "CompensatingWriteError message: $message category: $category code: $code\n $compensatingWrites";
}
}
94 changes: 75 additions & 19 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2907,45 +2907,63 @@ extension on Pointer<realm_value_t> {
if (this == nullptr) {
throw RealmException("Can not convert nullptr realm_value to Dart value");
}
return ref.toDartValueByRef(realm);
blagoev marked this conversation as resolved.
Show resolved Hide resolved
}
}

switch (ref.type) {
extension on realm_value_t {
Object? toDartValueByRef(Realm? realm) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
switch (type) {
case realm_value_type.RLM_TYPE_NULL:
return null;
case realm_value_type.RLM_TYPE_INT:
return ref.values.integer;
return values.integer;
case realm_value_type.RLM_TYPE_BOOL:
return ref.values.boolean;
return values.boolean;
case realm_value_type.RLM_TYPE_STRING:
return ref.values.string.data.cast<Utf8>().toRealmDartString(length: ref.values.string.size)!;
return values.string.data.cast<Utf8>().toRealmDartString(length: values.string.size)!;
case realm_value_type.RLM_TYPE_FLOAT:
return ref.values.fnum;
return values.fnum;
case realm_value_type.RLM_TYPE_DOUBLE:
return ref.values.dnum;
return values.dnum;
case realm_value_type.RLM_TYPE_LINK:
final objectKey = ref.values.link.target;
final classKey = ref.values.link.target_table;
if (realm == null) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
final objectKey = values.link.target;
final classKey = values.link.target_table;
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");
case realm_value_type.RLM_TYPE_TIMESTAMP:
final seconds = ref.values.timestamp.seconds;
final nanoseconds = ref.values.timestamp.nanoseconds;
final seconds = values.timestamp.seconds;
final nanoseconds = values.timestamp.nanoseconds;
return DateTime.fromMicrosecondsSinceEpoch(seconds * _microsecondsPerSecond + nanoseconds ~/ _nanosecondsPerMicrosecond, isUtc: true);
case realm_value_type.RLM_TYPE_DECIMAL128:
var decimal = ref.values.decimal128; // NOTE: Does not copy the struct!
var decimal = values.decimal128; // NOTE: Does not copy the struct!
decimal = _realmLib.realm_dart_decimal128_copy(decimal); // This is a workaround to that
return Decimal128Internal.fromNative(decimal);
case realm_value_type.RLM_TYPE_OBJECT_ID:
return ObjectId.fromBytes(cast<Uint8>().asTypedList(12));
return ObjectId.fromBytes(values.object_id.bytes.toIntList(ObjectId.byteLength));
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
case realm_value_type.RLM_TYPE_UUID:
return Uuid.fromBytes(cast<Uint8>().asTypedList(16).buffer);
return Uuid.fromBytes(values.uuid.bytes.toIntList(16).buffer);
default:
throw RealmException("realm_value_type ${ref.type} not supported");
throw RealmException("realm_value_type $type not supported");
}
}
}

extension on Array<Uint8> {
Uint8List toIntList(int count) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
List<int> result = List.filled(count, this[0]);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
for (var i = 1; i < count; i++) {
result[i] = this[i];
}
return Uint8List.fromList(result);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
}
}

extension on Pointer<Size> {
List<int> toIntList(int count) {
List<int> result = List.filled(count, elementAt(0).value);
Expand Down Expand Up @@ -2994,22 +3012,60 @@ extension on Pointer<Utf8> {

extension on realm_sync_error {
SyncError toSyncError(Configuration config) {
final message = detailed_message.cast<Utf8>().toRealmDartString()!;
final message = error_code.message.cast<Utf8>().toRealmDartString()!;
final detailedMessage = detailed_message.cast<Utf8>().toRealmDartString()!;
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
final SyncErrorCategory category = SyncErrorCategory.values[error_code.category];

//client reset can be requested with is_client_reset_requested disregarding the error_code.value
if (is_client_reset_requested) {
return ClientResetError(message, config);
if (is_client_reset_requested || error_code.value == SyncClientErrorCode.autoClientResetFailure.code) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
final userInfoMap = user_info_map.toDartCollection(user_info_length);
return ClientResetError(message, detailedMessage, config, userInfoMap);
}
if (category == SyncErrorCategory.session) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
final sessionErrorCode = SyncSessionErrorCode.fromInt(error_code.value);
if (sessionErrorCode == SyncSessionErrorCode.compensatingWrite) {
final compensatingWrites = compensating_writes.toDartCollection(compensating_writes_length);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
return CompensatingWriteError(message, detailedMessage, compensatingWrites);
}
}
return SyncError.create(message, detailedMessage, category, error_code.value, isFatal: is_fatal);
}
}

extension on Pointer<realm_sync_error_user_info_t> {
Map<String, String> toDartCollection(int length) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
Map<String, String> userInfoMap = {};
final userInfoMapPtr = cast<realm_sync_error_user_info>();
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < length; ++i) {
blagoev marked this conversation as resolved.
Show resolved Hide resolved
final userInfoItem = userInfoMapPtr[i];
final key = userInfoItem.key.cast<Utf8>().toDartString();
final value = userInfoItem.value.cast<Utf8>().toDartString();
userInfoMap.addEntries([MapEntry(key, value)]);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
}
return userInfoMap;
}
}

return SyncError.create(message, category, error_code.value, isFatal: is_fatal);
extension on Pointer<realm_sync_error_compensating_write_info_t> {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
List<CompensatingWriteInfo> toDartCollection(int length) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
List<CompensatingWriteInfo> compensatingWrites = [];

final compensatingWritesPtr = cast<realm_sync_error_compensating_write_info>();
for (int i = 0; i < length; ++i) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
final compensatingWrite = compensatingWritesPtr[i];
final object_name = compensatingWrite.object_name.cast<Utf8>().toDartString();
final reason = compensatingWrite.reason.cast<Utf8>().toDartString();
final primary_key = compensatingWrite.primary_key.toDartValueByRef(null);
compensatingWrites.add(CompensatingWriteInfo(object_name, reason, RealmValue.from(primary_key)));
}
return compensatingWrites;
}
}

extension on Pointer<realm_sync_error_code_t> {
SyncError toSyncError() {
final message = ref.message.cast<Utf8>().toDartString();
return SyncError.create(message, SyncErrorCategory.values[ref.category], ref.value, isFatal: false);
return SyncError.create(message, "", SyncErrorCategory.values[ref.category], ref.value, isFatal: false);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
Loading