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

Add keypath filtering to notifications #3501

Merged
merged 60 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
a6a1b0d
A comment before pausing this for now
papafe Nov 30, 2023
f1e1877
Merge branch 'main' into fp/keypath-filtering
papafe Jan 11, 2024
af61b27
Stub
papafe Jan 12, 2024
996e583
Added basic tests and keypath to extension mehtods
papafe Jan 15, 2024
c915e0c
Returned to initial implementation
papafe Jan 15, 2024
be5c6cb
Stubs
papafe Jan 15, 2024
836fcbf
Updated core to v13.25.1
papafe Jan 15, 2024
0898049
Updated changelog
papafe Jan 16, 2024
a40558a
Merge branch 'fp/update-core' into fp/keypath-filtering
papafe Jan 16, 2024
1e27a79
Stub
papafe Jan 16, 2024
2d985b4
Stub
papafe Jan 17, 2024
eaf5406
Merge branch 'main' into fp/keypath-filtering
papafe Jan 17, 2024
a5f3b04
Adding tests
papafe Jan 18, 2024
44939c7
Fixed unsubuscription
papafe Jan 18, 2024
a7157d8
Added new tests
papafe Jan 18, 2024
c0415a4
Small cleaning up
papafe Jan 18, 2024
9e0065c
Added tests
papafe Jan 19, 2024
d8eb427
Added lots of tests
papafe Jan 19, 2024
74b940a
Added more tests
papafe Jan 19, 2024
284d34a
Moved tests down
papafe Jan 19, 2024
c414305
Slight improvement to handlers
papafe Jan 19, 2024
d391248
Corrected order of operations
papafe Jan 19, 2024
c269bb3
Improved docs
papafe Jan 22, 2024
c930def
Added tests
papafe Jan 22, 2024
c9ac4d3
Fixes
papafe Jan 23, 2024
dd48576
Improved API
papafe Jan 23, 2024
f453127
Improved API and corrected tests
papafe Jan 23, 2024
a4a5b07
Fixing API
papafe Jan 24, 2024
2459a09
Removed test
papafe Jan 24, 2024
f1733da
Small corrections
papafe Jan 24, 2024
54fbe60
Stub
papafe Jan 24, 2024
a967957
First step internal API unification
papafe Jan 25, 2024
e88ac84
almost finished unification
papafe Jan 25, 2024
8313fa5
Improved API unification
papafe Jan 25, 2024
35257aa
Simplified API and added tests for collection construction [skip-ci]
papafe Jan 25, 2024
5e4ce66
Small corrections
papafe Jan 25, 2024
6a8ba0e
Simplified with the use of marshaled vector
papafe Jan 25, 2024
3dba3ce
Moved keypaths related to another file
papafe Jan 26, 2024
96edc6e
Small corrections [skip-ci]
papafe Jan 26, 2024
11448bd
Small corrections
papafe Jan 26, 2024
2f96851
Divided implementation
papafe Jan 26, 2024
e6931d9
Fixed API for collections
papafe Jan 26, 2024
2f3428d
Corrected tests
papafe Jan 26, 2024
74eadb2
Added missing tests
papafe Jan 26, 2024
7f466c0
Small fix
papafe Jan 26, 2024
d3fa6f8
Removed comment
papafe Jan 26, 2024
a912e75
Fixes following PR
papafe Jan 26, 2024
caf4735
Apply suggestions from code review
papafe Jan 26, 2024
949d2c4
Various PR comments fixes
papafe Jan 26, 2024
32978d9
Various corrections according to PR
papafe Jan 30, 2024
9b07657
Merge branch 'main' into fp/keypath-filtering
papafe Feb 6, 2024
461f725
Updated changelog and docs
papafe Feb 6, 2024
dc29014
Fixes
papafe Feb 6, 2024
99b7a22
Changed marshaling to go around .net framework limitation
papafe Feb 6, 2024
59232bf
Proposed delegate change (#3512)
nirinchev Feb 7, 2024
436a80d
Small fix
papafe Feb 7, 2024
9b8667b
Update CHANGELOG.md
papafe Feb 7, 2024
9ed3181
Update Realm/Realm/DatabaseTypes/RealmCollectionBase.cs
papafe Feb 7, 2024
5d16d4f
Merge branch 'main' into fp/keypath-filtering
papafe Feb 8, 2024
8409f36
Merge branch 'main' into fp/keypath-filtering
papafe Feb 8, 2024
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
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
## vNext (TBD)

### Enhancements
* None
* Add support for passing a key paths collection (`KeyPathsCollection`) when using `IRealmCollection.SubscribeForNotifications`. Passing a `KeyPathsCollection` allows to specify which changes in properties should raise a notification.

A `KeyPathsCollection` can be obtained by:
- building it explicitly by using the method `KeyPathsCollection`;
papafe marked this conversation as resolved.
Show resolved Hide resolved
- building it implicitly with the conversion from a `List` or array of `KeyPath` or strings;
- getting one of the static values `Full` and `Shallow` for full and shallow notifications respectively.


For example:
```csharp
var query = realm.All<Person>();

KeyPathsCollection kpc;

//Equivalent declarations
kpc = KeyPathsCollection.Of("Email", "Name");
kpc = new List<KeyPath> {"Email", "Name"};

query.SubscribeForNotifications(NotificationCallback, kpc);
```
(PR [#3501 ](https://github.com/realm/realm-dotnet/pull/3501))

### Fixed
* None
Expand Down
4 changes: 3 additions & 1 deletion Realm/Realm/DatabaseTypes/Accessors/ManagedAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,10 @@ public void UnsubscribeFromNotifications()
}

/// <inheritdoc/>
void INotifiable<NotifiableObjectHandleBase.CollectionChangeSet>.NotifyCallbacks(NotifiableObjectHandleBase.CollectionChangeSet? changes, bool shallow)
void INotifiable<NotifiableObjectHandleBase.CollectionChangeSet>.NotifyCallbacks(NotifiableObjectHandleBase.CollectionChangeSet? changes,
KeyPathsCollectionType type, Delegate? callback)
{
Debug.Assert(callback == null, "Object notifications don't support keypaths, so callback should always be null");
if (changes.HasValue)
{
foreach (var propertyIndex in changes.Value.Properties)
Expand Down
5 changes: 3 additions & 2 deletions Realm/Realm/DatabaseTypes/INotifiable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ internal interface INotifiable<TChangeset>
/// Method called when there are changes to report for that object.
/// </summary>
/// <param name="changes">The changes that occurred.</param>
/// <param name="shallow">Whether the changes are coming from a shallow notifier or not.</param>
void NotifyCallbacks(TChangeset? changes, bool shallow);
/// <param name="type">The type of the key paths collection related to the notification.</param>
/// <param name="callback">The eventual callback to call for the notification (if type == Explicit).</param>
void NotifyCallbacks(TChangeset? changes, KeyPathsCollectionType type, Delegate? callback);
}

internal class NotificationToken<TCallback> : IDisposable
Expand Down
166 changes: 166 additions & 0 deletions Realm/Realm/DatabaseTypes/KeyPathCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

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

namespace Realms;

/// <summary>
/// Represents a collection of <see cref="KeyPath"/> that can be used when subscribing to notifications with <see cref="IRealmCollection{T}.SubscribeForNotifications(Realms.NotificationCallbackDelegate{T}, Realms.KeyPathsCollection?)"/>.
/// <remarks>
/// <para>
/// A <see cref="KeyPathsCollection"/> can be obtained by:
/// <list type="bullet">
/// <item>
/// <description>building it explicitly by using the method <see cref="KeyPathsCollection.Of(Realms.KeyPath[])"/>;</description>
/// </item>
/// <item>
/// <description>building it implicitly with the conversion from a <see cref="List{T}"/> or array of <see cref="KeyPath"/> or strings;</description>
/// </item>
/// <item>
/// <description>getting one of the static values <see cref="Full"/> and <see cref="Shallow"/> for full and shallow notifications respectively.</description>
/// </item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public class KeyPathsCollection : IEnumerable<KeyPath>
{
private IEnumerable<KeyPath> _collection;

private static readonly KeyPathsCollection _shallow = new(KeyPathsCollectionType.Shallow);
private static readonly KeyPathsCollection _full = new(KeyPathsCollectionType.Full);

internal KeyPathsCollectionType Type { get; }

private KeyPathsCollection(KeyPathsCollectionType type, ICollection<KeyPath>? collection = null)
{
Debug.Assert(type == KeyPathsCollectionType.Explicit == (collection?.Any() == true),
$"If collection isn't empty, then the type must be {nameof(KeyPathsCollectionType.Explicit)}");

Type = type;
_collection = collection ?? Enumerable.Empty<KeyPath>();

VerifyKeyPaths();
}

/// <summary>
/// Builds a <see cref="KeyPathsCollection"/> from an array of <see cref="KeyPath"/>.
/// </summary>
/// <param name="paths">The array of <see cref="KeyPath"/> to use for the <see cref="KeyPathsCollection"/>.</param>
/// <returns>The <see cref="KeyPathsCollection"/> built from the input array of <see cref="KeyPath"/>.</returns>
public static KeyPathsCollection Of(params KeyPath[] paths)
{
if (paths.Length == 0)
{
return new KeyPathsCollection(KeyPathsCollectionType.Shallow);
}

return new KeyPathsCollection(KeyPathsCollectionType.Explicit, paths);
}

/// <summary>
/// Gets a <see cref="KeyPathsCollection"/> value for shallow notifications, that will raise notifications only for changes to the collection itself (for example when an element is added or removed),
/// but not for changes to any of the properties of the elements of the collection.
/// </summary>
public static KeyPathsCollection Shallow => _shallow;

/// <summary>
/// Gets a <see cref="KeyPathsCollection"/> value for full notifications, for which changes to all top-level properties and 4 nested levels will raise a notification. This is the default <see cref="KeyPathsCollection"/> value.
/// </summary>
public static KeyPathsCollection Full => _full;

public static implicit operator KeyPathsCollection(List<string> paths) =>
new(KeyPathsCollectionType.Explicit, paths.Select(path => (KeyPath)path).ToArray());

public static implicit operator KeyPathsCollection(List<KeyPath> paths) => new(KeyPathsCollectionType.Explicit, paths);

public static implicit operator KeyPathsCollection(string[] paths) =>
new(KeyPathsCollectionType.Explicit, paths.Select(path => (KeyPath)path).ToArray());

public static implicit operator KeyPathsCollection(KeyPath[] paths) => new(KeyPathsCollectionType.Explicit, paths);

internal IEnumerable<string> GetStrings() => _collection.Select(x => x.Path);

internal void VerifyKeyPaths()
{
foreach (var item in _collection)
{
if (string.IsNullOrWhiteSpace(item.Path))
{
throw new ArgumentException("A key path cannot be null, empty, or consisting only of white spaces");
}
}
}

/// <inheritdoc/>
public IEnumerator<KeyPath> GetEnumerator()
{
return _collection.GetEnumerator();
}

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

/// <summary>
/// Represents a key path that can be used as a part of a <see cref="KeyPathsCollection"/> when subscribing for notifications.
/// A <see cref="KeyPath"/> can be implicitly built from a string, where the string is the name of a property (e.g "FirstName"), eventually dotted to indicated nested properties.
/// (e.g "Dog.Name"). Wildcards can also be used in key paths to capture all properties at a given level (e.g "*", "Friends.*" or "*.FirstName").
/// </summary>
public readonly struct KeyPath
{
internal string Path { get; }

private KeyPath(string path)
{
Path = path;
}

public static implicit operator KeyPath(string s) => new(s);
papafe marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc/>
public override bool Equals(object? obj) => obj is KeyPath path && Path == path.Path;

/// <inheritdoc/>
public override int GetHashCode() => Path.GetHashCode();

public static bool operator ==(KeyPath left, KeyPath right)
{
return left.Equals(right);
}

public static bool operator !=(KeyPath left, KeyPath right)
{
return !(left == right);
}
}

internal enum KeyPathsCollectionType
{
Full,
Shallow,
Explicit
}
Loading
Loading