Skip to content

Commit

Permalink
Explicit archiving now can archive based on all edges or just start n…
Browse files Browse the repository at this point in the history
…ode or both (#54)

Co-authored-by: Farhad Nowzari <[email protected]>
  • Loading branch information
farhadnowzari and Farhad Nowzari authored Jul 24, 2024
1 parent b923beb commit 0daf2d0
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 13 deletions.
37 changes: 37 additions & 0 deletions src/Neo4j.Berries.OGM/Models/General/ArchiveOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Neo4j.Berries.OGM.Models.General;

internal class ArchiveOptions
{
public bool StartNode { get; set; }
public bool Edges { get; set; }
}

public class ArchiveOptionsBuilder
{
internal ArchiveOptions Options { get; } = new ArchiveOptions();
/// <summary>
/// If true, the matched node in the root query will be archived.
/// </summary>
public ArchiveOptionsBuilder StartNode()
{
Options.StartNode = true;
return this;
}
/// <summary>
/// If true, the matched relation(s) in the result of WithRelation query will be archived.
/// </summary>
public ArchiveOptionsBuilder Edges()
{
Options.Edges = true;
return this;
}
/// <summary>
/// Everything matched with queries prior to the archive method will be archived.
/// </summary>
public ArchiveOptionsBuilder All()
{
Options.StartNode = true;
Options.Edges = true;
return this;
}
}
2 changes: 1 addition & 1 deletion src/Neo4j.Berries.OGM/Models/General/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using Neo4j.Berries.OGM.Models.Config;
using Neo4j.Berries.OGM.Utils;

namespace Neo4j.Berries.OGM.Models.Sets;
namespace Neo4j.Berries.OGM.Models.General;

internal class Node(string label, int depth = 0)
{
Expand Down
49 changes: 42 additions & 7 deletions src/Neo4j.Berries.OGM/Models/Queries/AnonymousNodeQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Neo4j.Berries.OGM.Contexts;
using Neo4j.Berries.OGM.Interfaces;
using Neo4j.Berries.OGM.Models.Config;
using Neo4j.Berries.OGM.Models.General;
using Neo4j.Berries.OGM.Models.Match;
using Neo4j.Berries.OGM.Models.Sets;
using Neo4j.Berries.OGM.Utils;
Expand Down Expand Up @@ -300,7 +301,8 @@ protected StringBuilder ExpandCypherWithReturn(StringBuilder builder)
builder.AppendLine($"RETURN {Matches.First().StartNodeAlias}");
return builder;
}
protected void AppendTimestamps(StringBuilder cypherBuilder) {
protected void AppendTimestamps(StringBuilder cypherBuilder)
{
var timestampConfig = Neo4jSingletonContext.TimestampConfiguration;
var nodeAlias = Matches.First().StartNodeAlias;
if (timestampConfig.Enabled)
Expand Down Expand Up @@ -391,7 +393,6 @@ private StringBuilder PrepareDisconnect()
#endregion

#region Archive

///<summary>
/// Archives the nodes matched with the query. The remove function will only set the ArchivedAt property to the node and will not HardDelete the node.<br>
///</summary>
Expand All @@ -414,14 +415,48 @@ public async Task<IEnumerable<T>> ArchiveAsync<T>(CancellationToken cancellation
return await ExecuteWithMapAsync(record => record.Convert<T>(key), archiveCypher, cancellationToken);
}

private StringBuilder PrepareArchive()
public IEnumerable<IRecord> Archive(Action<ArchiveOptionsBuilder> builder)
{
var key = Matches.First().StartNodeAlias;
var optionsBuilder = new ArchiveOptionsBuilder();
builder(optionsBuilder);
var options = optionsBuilder.Options;
var archiveCypher = PrepareArchive(options).ToString();
return ExecuteWithMap(record => record, archiveCypher);
}

public async Task<IEnumerable<IRecord>> ArchiveAsync(Action<ArchiveOptionsBuilder> builder, CancellationToken cancellationToken = default)
{
var optionsBuilder = new ArchiveOptionsBuilder();
builder(optionsBuilder);
var options = optionsBuilder.Options;
var archiveCypher = PrepareArchive(options).ToString();
return await ExecuteWithMapAsync(record => record, archiveCypher, cancellationToken);
}

private StringBuilder PrepareArchive(ArchiveOptions options = null)
{
options ??= new ArchiveOptions { StartNode = true };
var timestampConfig = Neo4jSingletonContext.TimestampConfiguration;
var aliases = new List<string>();
if (options.StartNode)
{
var key = Matches.First().StartNodeAlias;
CypherBuilder.AppendLine($"SET {key}.{timestampConfig.ArchivedTimestampKey} = timestamp()");
aliases.Add(key);
}
if (options.Edges)
{
foreach (var match in Matches.Skip(1))
{
var key = match.RelationAlias;
CypherBuilder.AppendLine($"SET {key}.{timestampConfig.ArchivedTimestampKey} = timestamp()");
aliases.Add(key);
}
}
var aliasesString = string.Join(", ", aliases);
return CypherBuilder.AppendLines(
$"SET {key}.{timestampConfig.ArchivedTimestampKey} = timestamp()",
$"WITH DISTINCT {key}",
$"RETURN {key}"
$"WITH DISTINCT {aliasesString}",
$"RETURN {aliasesString}"
);
}

Expand Down
1 change: 1 addition & 0 deletions src/Neo4j.Berries.OGM/Models/Sets/AnonymousNodeSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Neo4j.Berries.OGM.Interfaces;
using Neo4j.Berries.OGM.Models.Config;
using Neo4j.Berries.OGM.Models.Queries;
using Neo4j.Berries.OGM.Models.General;
using Neo4j.Berries.OGM.Utils;

namespace Neo4j.Berries.OGM.Models.Sets;
Expand Down
1 change: 1 addition & 0 deletions src/Neo4j.Berries.OGM/Models/Sets/TypedNodeSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Neo4j.Berries.OGM.Contexts;
using Neo4j.Berries.OGM.Interfaces;
using Neo4j.Berries.OGM.Models.Queries;
using Neo4j.Berries.OGM.Models.General;
using Neo4j.Berries.OGM.Utils;

namespace Neo4j.Berries.OGM.Models.Sets;
Expand Down
2 changes: 1 addition & 1 deletion src/Neo4j.Berries.OGM/Utils/Converters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static JsonSerializerOptions SerializerOptions
}
public static TResult Convert<TResult>(this IRecord record, string key)
{
var node = record[key].As<INode>();
var node = record[key].As<IEntity>();
var nodeProperties = JsonSerializer.Serialize(node.Properties, SerializerOptions);

return JsonSerializer.Deserialize<TResult>(nodeProperties, SerializerOptions);
Expand Down
4 changes: 1 addition & 3 deletions tests/Neo4j.Berries.OGM.Tests/Models/General/NodeTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System.Diagnostics.Contracts;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using FluentAssertions;
using Neo4j.Berries.OGM.Contexts;
using Neo4j.Berries.OGM.Models.Sets;
using Neo4j.Berries.OGM.Models.General;
using Neo4j.Berries.OGM.Tests.Common;

namespace Neo4j.Berries.OGM.Tests.Models.General;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using FluentAssertions;
using Neo4j.Berries.OGM.Tests.Mocks.Enums;
using Neo4j.Berries.OGM.Enums;
using Neo4j.Berries.OGM.Utils;

namespace Neo4j.Berries.OGM.Tests.Models.Sets;
public class UpdateNormalCasePropertyTests : TestBase
Expand Down Expand Up @@ -269,10 +270,60 @@ public void Should_Connect_Node_Anonymously()
x => x.Where("Id", TestMovieNode.Id)
)
.Connect("Actors", x => x.Where("Id", TestPersonNode.Id));

var testAnonymousMovie = anonymous.Match(x => x.Where("Id", TestMovieNode.Id))
.WithRelation("Actors", x => x.Where("Id", TestPersonNode.Id))
.FirstOrDefault<Dictionary<string, object>>();
testAnonymousMovie.Should().NotBeNull();
}

[Fact]
public void Should_Archive_Node_Anonymously()
{
var anonymous = TestGraphContext.Anonymous("Movie");
anonymous
.Match(
x => x.Where("Id", TestMovieNode.Id)
)
.Archive<Movie>();
var testAnonymousMovie = anonymous.Match(x => x.Where("Id", TestMovieNode.Id)).FirstOrDefault<Dictionary<string, object>>();
testAnonymousMovie.Should().NotBeNull();
testAnonymousMovie["archivedOn"].Should().NotBeNull();
}
[Fact]
public void Should_Archive_Node_Anonymously_And_Return_Archived_Records()
{
var anonymous = TestGraphContext.Anonymous("Movie");
var records = anonymous
.Match(
x => x.Where("Id", TestMovieNode.Id)
)
.Archive(x => x.StartNode());
var key = records.SelectMany(x => x.Keys).First();
records.Select(x => x.Convert<Dictionary<string, object>>(key)).First()["archivedOn"].Should().NotBeNull();
}
[Fact]
public void Should_Archive_Node_Anonymously_With_Relation()
{
var anonymous = TestGraphContext.Anonymous("Movie");
var records = anonymous
.Match(
x => x.Where("Id", TestMovieNode.Id)
)
.WithRelation("Actors", x => x.Where("Id", TestPersonNode.Id))
.Archive(x => x.All());
var keys = records.SelectMany(x => x.Keys);
keys.Should().HaveCount(2);
var result = records.Select(
x => {
var result = new Dictionary<string, Dictionary<string, object>>();
foreach(var key in keys) {
result[key] = x.Convert<Dictionary<string, object>>(key);
}
return result;
}
);
result.First()["l0"]["archivedOn"].Should().NotBeNull();
result.First()["r1"]["archivedOn"].Should().NotBeNull();
}
}

0 comments on commit 0daf2d0

Please sign in to comment.