Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace PowerSync.Common.Client.Sync.Bucket;

using System;
using System.Linq;
using System.Threading.Tasks;

using PowerSync.Common.DB.Crud;
Expand Down Expand Up @@ -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)
);
}
}

Expand Down Expand Up @@ -129,4 +136,4 @@ public interface IBucketStorageAdapter : IEventStream<BucketStorageEvent>
/// Invokes the `powersync_control` function for the sync client.
/// </summary>
Task<string> Control(string op, object? payload);
}
}
20 changes: 17 additions & 3 deletions PowerSync/PowerSync.Common/DB/Crud/CrudEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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)
);
}
}
}
11 changes: 9 additions & 2 deletions PowerSync/PowerSync.Common/PowerSync.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup>

<!-- System.HashCode polyfill for .NET Standard 2.0 and .NET Framework 4.8 -->
<ItemGroup Condition="$(TargetFramework) == 'net48' OR $(TargetFramework) == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.HashCode">
<Version>6.0.0</Version>
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Include="PowerSync.Common.targets" Pack="true" PackagePath="build\" />
<None Include="PowerSync.Common.targets" Pack="true" PackagePath="buildTransitive\" />
Expand All @@ -50,7 +57,7 @@
</ItemGroup>

<ItemGroup>
<None Include="..\..\icon.png" Pack="true" PackagePath=""/>
<None Include="README.md" Pack="true" PackagePath=""/>
<None Include="..\..\icon.png" Pack="true" PackagePath="" />
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
43 changes: 43 additions & 0 deletions PowerSync/PowerSync.Common/Utils/CompareUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace PowerSync.Common.Utils;

using System.Collections.Generic;
using System.Linq;

public static class CompareUtils
{
/// <summary>
/// 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.
/// </summary>
public static bool DictionariesEqual<TKey, TValue>(Dictionary<TKey, TValue>? dict1, Dictionary<TKey, TValue>? dict2)
{
if (ReferenceEquals(dict1, dict2)) return true;
if (dict1 == null || dict2 == null) return false;
if (dict1.Count != dict2.Count) return false;

var comparer = EqualityComparer<TValue>.Default;

foreach (var keyValuePair in dict1)
{
if (!dict2.TryGetValue(keyValuePair.Key, out TValue secondValue) ||
!comparer.Equals(keyValuePair.Value, secondValue))
{
return false;
}
}

return true;
}

/// <summary>
/// Check if two (maybe null) arrays contain the same elements in the same order.
/// Effectively arr1.SequenceEqual(arr2), but with null checks.
/// </summary>
public static bool ArraysEqual<TValue>(TValue[]? arr1, TValue[]? arr2)
{
if (ReferenceEquals(arr1, arr2)) return true;
if (arr1 == null || arr2 == null) return false;
return arr1.SequenceEqual(arr2);
}
}

47 changes: 47 additions & 0 deletions PowerSync/PowerSync.Common/Utils/HashUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace PowerSync.Common.Utils;

using System;
using System.Collections.Generic;

public static class HashUtils
{
/// <summary>
/// Create a hash from the key-value pairs in a dictionary. The hash does not depend
/// on the internal pair ordering of the dictionary.
/// </summary>
public static int GetHashCodeDictionary<TKey, TValue>(Dictionary<TKey, TValue>? 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;
}

/// <summary>
/// Create a hash from an array of values. The hash depends on the order of
/// elements in the array.
/// </summary>
public static int GetHashCodeArray<TValue>(TValue[]? values)
{
if (values == null)
{
return 0;
}

HashCode hash = new HashCode();
foreach (var value in values)
{
hash.Add(value);
}
return hash.ToHashCode();
}
}