diff --git a/PowerSync/PowerSync.Common/Client/Sync/Bucket/BucketStorageAdapter.cs b/PowerSync/PowerSync.Common/Client/Sync/Bucket/BucketStorageAdapter.cs index 4611bb2..dd590b2 100644 --- a/PowerSync/PowerSync.Common/Client/Sync/Bucket/BucketStorageAdapter.cs +++ b/PowerSync/PowerSync.Common/Client/Sync/Bucket/BucketStorageAdapter.cs @@ -2,6 +2,7 @@ namespace PowerSync.Common.Client.Sync.Bucket; using System; +using System.Linq; using System.Threading.Tasks; using PowerSync.Common.DB.Crud; @@ -54,12 +55,18 @@ public class SyncLocalDatabaseResult public override bool Equals(object? obj) { if (obj is not SyncLocalDatabaseResult other) return false; - return JsonConvert.SerializeObject(this) == JsonConvert.SerializeObject(other); + return Ready == other.Ready + && CheckpointValid == other.CheckpointValid + && CompareUtils.ArraysEqual(CheckpointFailures, other.CheckpointFailures); } public override int GetHashCode() { - return JsonConvert.SerializeObject(this).GetHashCode(); + return HashCode.Combine( + Ready, + CheckpointValid, + HashUtils.GetHashCodeArray(CheckpointFailures) + ); } } @@ -129,4 +136,4 @@ public interface IBucketStorageAdapter : IEventStream /// Invokes the `powersync_control` function for the sync client. /// Task Control(string op, object? payload); -} \ No newline at end of file +} diff --git a/PowerSync/PowerSync.Common/DB/Crud/CrudEntry.cs b/PowerSync/PowerSync.Common/DB/Crud/CrudEntry.cs index 5fe8f02..bea01b2 100644 --- a/PowerSync/PowerSync.Common/DB/Crud/CrudEntry.cs +++ b/PowerSync/PowerSync.Common/DB/Crud/CrudEntry.cs @@ -3,6 +3,8 @@ namespace PowerSync.Common.DB.Crud; using System.Collections.Generic; using Newtonsoft.Json; +using PowerSync.Common.Utils; + public enum UpdateType { [JsonProperty("PUT")] @@ -90,11 +92,23 @@ public static CrudEntry FromRow(CrudEntryJSON dbRow) public override bool Equals(object? obj) { if (obj is not CrudEntry other) return false; - return JsonConvert.SerializeObject(this) == JsonConvert.SerializeObject(other); + return ClientId == other.ClientId + && Id == other.Id + && Op == other.Op + && TransactionId == other.TransactionId + && Table == other.Table + && CompareUtils.DictionariesEqual(OpData, other.OpData); } public override int GetHashCode() { - return JsonConvert.SerializeObject(this).GetHashCode(); + return HashCode.Combine( + ClientId, + Id, + Op, + Table, + TransactionId, + HashUtils.GetHashCodeDictionary(OpData) + ); } -} \ No newline at end of file +} diff --git a/PowerSync/PowerSync.Common/PowerSync.Common.csproj b/PowerSync/PowerSync.Common/PowerSync.Common.csproj index abb53de..64dd646 100644 --- a/PowerSync/PowerSync.Common/PowerSync.Common.csproj +++ b/PowerSync/PowerSync.Common/PowerSync.Common.csproj @@ -31,6 +31,13 @@ + + + + 6.0.0 + + + @@ -50,7 +57,7 @@ - - + + diff --git a/PowerSync/PowerSync.Common/Utils/CompareUtils.cs b/PowerSync/PowerSync.Common/Utils/CompareUtils.cs new file mode 100644 index 0000000..0dfe36e --- /dev/null +++ b/PowerSync/PowerSync.Common/Utils/CompareUtils.cs @@ -0,0 +1,43 @@ +namespace PowerSync.Common.Utils; + +using System.Collections.Generic; +using System.Linq; + +public static class CompareUtils +{ + /// + /// Compare two dictionaries by value. Checks if both dictionaries have both the same + /// number of keys, as well as the same keys pointing to the same values. + /// + public static bool DictionariesEqual(Dictionary? dict1, Dictionary? dict2) + { + if (ReferenceEquals(dict1, dict2)) return true; + if (dict1 == null || dict2 == null) return false; + if (dict1.Count != dict2.Count) return false; + + var comparer = EqualityComparer.Default; + + foreach (var keyValuePair in dict1) + { + if (!dict2.TryGetValue(keyValuePair.Key, out TValue secondValue) || + !comparer.Equals(keyValuePair.Value, secondValue)) + { + return false; + } + } + + return true; + } + + /// + /// Check if two (maybe null) arrays contain the same elements in the same order. + /// Effectively arr1.SequenceEqual(arr2), but with null checks. + /// + public static bool ArraysEqual(TValue[]? arr1, TValue[]? arr2) + { + if (ReferenceEquals(arr1, arr2)) return true; + if (arr1 == null || arr2 == null) return false; + return arr1.SequenceEqual(arr2); + } +} + diff --git a/PowerSync/PowerSync.Common/Utils/HashUtils.cs b/PowerSync/PowerSync.Common/Utils/HashUtils.cs new file mode 100644 index 0000000..ba258e9 --- /dev/null +++ b/PowerSync/PowerSync.Common/Utils/HashUtils.cs @@ -0,0 +1,47 @@ +namespace PowerSync.Common.Utils; + +using System; +using System.Collections.Generic; + +public static class HashUtils +{ + /// + /// Create a hash from the key-value pairs in a dictionary. The hash does not depend + /// on the internal pair ordering of the dictionary. + /// + public static int GetHashCodeDictionary(Dictionary? dict) + { + if (dict == null) + { + return 0; + } + + // Use integer hash because order matters with System.HashCode + int hash = 0; + foreach (var kvp in dict) + { + // Combine hashes with XOR so that order doesn't matter + hash ^= HashCode.Combine(kvp.Key, kvp.Value); + } + return hash; + } + + /// + /// Create a hash from an array of values. The hash depends on the order of + /// elements in the array. + /// + public static int GetHashCodeArray(TValue[]? values) + { + if (values == null) + { + return 0; + } + + HashCode hash = new HashCode(); + foreach (var value in values) + { + hash.Add(value); + } + return hash.ToHashCode(); + } +}