From 815281a678af2834759665efd2adf106d3d6d39a Mon Sep 17 00:00:00 2001
From: Richard Irons <115992270+RichardIrons-neo4j@users.noreply.github.com>
Date: Fri, 28 Apr 2023 11:16:51 +0100
Subject: [PATCH] ExecuteQuery API ready for release (#700)
* Transformation fluent working
* tidying up; xml docs
* remove wrong file
* Licensing text
* Fix build problem
* Transformation fluent working
* tidying up; xml docs
* remove wrong file
* Licensing text
* Fix build problem
* fixups
* Map/Filter/Reduce working (no xml docs)
* Tests for stream processor
* XML docs
* Review notes part 1
* Review notes part 2
* CofigureAwaits
* readme
* Docs fixes
* Spelling
* ConfigureAwait
---
.../Direct/ResultIT.cs | 7 +-
.../Internals/Cluster/ExistingCluster.cs | 6 +-
.../Neo4j.Driver.Tests.Integration.csproj | 4 +-
.../BookmarkManager/NewBookmarkManager.cs | 3 +-
.../Protocol/Driver/DriverClose.cs | 2 +-
.../Protocol/DriverQuery/ExecuteQuery.cs | 4 +-
.../Types/CypherToNative.cs | 2 +-
.../Neo4j.Driver.Tests/AsyncSessionTests.cs | 1 -
.../ExecutableQuery/ExecutableQueryTests.cs | 396 ++++++++++++++++++
.../BookmarkManagerFactoryTests.cs | 1 -
.../DefaultBookmarkManagerTests.cs | 1 -
.../Neo4j.Driver.Tests.csproj | 3 +-
.../{Preview => }/BookmarkManagerConfig.cs | 2 +-
.../{Preview => ExecuteQuery}/EagerResult.cs | 11 +-
.../ExecuteQuery/IExecutableQuery.cs | 146 +++++++
.../{Preview => ExecuteQuery}/QueryConfig.cs | 2 +-
Neo4j.Driver/Neo4j.Driver/GraphDatabase.cs | 8 +
.../{Preview => }/IBookmarkManager.cs | 9 +-
.../{Preview => }/IBookmarkManagerFactory.cs | 4 +-
Neo4j.Driver/Neo4j.Driver/IDriver.cs | 12 +
.../Neo4j.Driver/Internal/AsyncSession.cs | 1 -
.../Internal/BookmarkManagerFactory.cs | 3 -
.../Internal/DefaultBookmarkManager.cs | 1 -
Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs | 65 ++-
.../Internal/ExecuteQuery/DriverRowSource.cs | 76 ++++
.../Internal/ExecuteQuery/ExecutableQuery.cs | 133 ++++++
.../Internal/ExecuteQuery/IQueryRowSource.cs | 29 ++
.../Internal/ExecuteQuery/ReduceToList.cs | 36 ++
.../ExecuteQuery/ReducedExecutableQuery.cs | 54 +++
.../StreamProcessorExecutableQuery.cs | 42 ++
.../Neo4j.Driver/Internal/IInternalDriver.cs | 10 +-
Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj | 3 +
.../Neo4j.Driver.csproj.DotSettings | 27 +-
.../Preview/FluentQueries/ExecutableQuery.cs | 100 -----
.../Preview/FluentQueries/IExecutableQuery.cs | 70 ----
.../Neo4j.Driver/Preview/GraphDatabase.cs | 36 --
.../Neo4j.Driver/Preview/PreviewExtensions.cs | 98 -----
Neo4j.Driver/Neo4j.Driver/SessionConfig.cs | 9 +-
README.md | 81 ++--
39 files changed, 1086 insertions(+), 412 deletions(-)
create mode 100644 Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs
rename Neo4j.Driver/Neo4j.Driver/{Preview => }/BookmarkManagerConfig.cs (99%)
rename Neo4j.Driver/Neo4j.Driver/{Preview => ExecuteQuery}/EagerResult.cs (78%)
create mode 100644 Neo4j.Driver/Neo4j.Driver/ExecuteQuery/IExecutableQuery.cs
rename Neo4j.Driver/Neo4j.Driver/{Preview => ExecuteQuery}/QueryConfig.cs (99%)
rename Neo4j.Driver/Neo4j.Driver/{Preview => }/IBookmarkManager.cs (87%)
rename Neo4j.Driver/Neo4j.Driver/{Preview => }/IBookmarkManagerFactory.cs (90%)
create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/DriverRowSource.cs
create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ExecutableQuery.cs
create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/IQueryRowSource.cs
create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReduceToList.cs
create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReducedExecutableQuery.cs
create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/StreamProcessorExecutableQuery.cs
delete mode 100644 Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/ExecutableQuery.cs
delete mode 100644 Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/IExecutableQuery.cs
delete mode 100644 Neo4j.Driver/Neo4j.Driver/Preview/GraphDatabase.cs
delete mode 100644 Neo4j.Driver/Neo4j.Driver/Preview/PreviewExtensions.cs
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/ResultIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/ResultIT.cs
index a3a5aeacc..18015181d 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/ResultIT.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/ResultIT.cs
@@ -177,7 +177,12 @@ public async Task GetNotification()
notification.Code.Should().NotBeNullOrEmpty();
notification.Description.Should().NotBeNullOrEmpty();
notification.Title.Should().NotBeNullOrEmpty();
- notification.Severity.Should().NotBeNullOrEmpty();
+ notification.RawSeverityLevel.Should().NotBeNullOrEmpty();
+
+#pragma warning disable CS0618 // deprecated method
+ notification.Severity.Should().Be(notification.RawSeverityLevel);
+#pragma warning restore CS0618
+
notification.Position.Should().NotBeNull();
}
finally
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/ExistingCluster.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/ExistingCluster.cs
index dc172d4e0..90f8681dd 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/ExistingCluster.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/ExistingCluster.cs
@@ -45,13 +45,13 @@ public static bool IsClusterProvided()
var uri = Environment.GetEnvironmentVariable(ClusterUri);
var password = Environment.GetEnvironmentVariable(ClusterPassword);
// both of the two above env var should be provided.
- return !uri.IsNullOrEmpty() && !password.IsNullOrEmpty();
+ return !string.IsNullOrEmpty(uri) && !string.IsNullOrEmpty(password);
}
private static string GetEnvOrThrow(string env)
{
var value = Environment.GetEnvironmentVariable(env);
- if (value.IsNullOrEmpty())
+ if (string.IsNullOrEmpty(value))
{
throw new ArgumentException($"Missing env variable {env}");
}
@@ -62,6 +62,6 @@ private static string GetEnvOrThrow(string env)
private static string GetEnvOrDefault(string env, string defaultValue)
{
var value = Environment.GetEnvironmentVariable(env);
- return value.IsNullOrEmpty() ? defaultValue : value;
+ return string.IsNullOrEmpty(value) ? defaultValue : value;
}
}
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Neo4j.Driver.Tests.Integration.csproj b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Neo4j.Driver.Tests.Integration.csproj
index ed0b8d8cb..11fb4ea15 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Neo4j.Driver.Tests.Integration.csproj
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Neo4j.Driver.Tests.Integration.csproj
@@ -19,11 +19,11 @@
-
+
-
+
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/BookmarkManager/NewBookmarkManager.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/BookmarkManager/NewBookmarkManager.cs
index 52b5d498d..559b61b3f 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/BookmarkManager/NewBookmarkManager.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/BookmarkManager/NewBookmarkManager.cs
@@ -18,7 +18,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using Neo4j.Driver.Preview;
using Newtonsoft.Json;
namespace Neo4j.Driver.Tests.TestBackend;
@@ -62,7 +61,7 @@ async Task NotifyBookmarks(string[] bookmarks, CancellationToken _)
}
BookmarkManager =
- Preview.GraphDatabase.BookmarkManagerFactory.NewBookmarkManager(
+ GraphDatabase.BookmarkManagerFactory.NewBookmarkManager(
new BookmarkManagerConfig(initialBookmarks, BookmarkSupplier, NotifyBookmarks));
return Task.CompletedTask;
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/DriverClose.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/DriverClose.cs
index ec1816eb3..d35de0eae 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/DriverClose.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/DriverClose.cs
@@ -26,7 +26,7 @@ internal class DriverClose : IProtocolObject
public override async Task Process()
{
var driver = ((NewDriver)ObjManager.GetObject(data.driverId)).Driver;
- await driver.CloseAsync();
+ await driver.DisposeAsync();
}
public override string Respond()
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs
index 6947e6734..52a3526cd 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs
@@ -19,7 +19,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using Neo4j.Driver.Preview;
using Newtonsoft.Json;
namespace Neo4j.Driver.Tests.TestBackend;
@@ -28,7 +27,8 @@ internal class ExecuteQuery : IProtocolObject
{
public ExecuteQueryDto data { get; set; }
- [JsonIgnore] public EagerResult> Result { get; set; }
+ [JsonIgnore]
+ public EagerResult> Result { get; set; }
public override async Task Process()
{
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Types/CypherToNative.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Types/CypherToNative.cs
index 2270fda73..e882fb6ed 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Types/CypherToNative.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Types/CypherToNative.cs
@@ -49,7 +49,7 @@ public static Dictionary ConvertDictionaryToNative(
internal class SimpleValue
{
- public object? value { get; set; }
+ public object value { get; set; }
}
public class DateTimeParameterValue
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs
index 7d84c9e8f..8bf9902e5 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs
@@ -28,7 +28,6 @@
using Neo4j.Driver.Internal.MessageHandling;
using Neo4j.Driver.Internal.Messaging;
using Neo4j.Driver.Internal.Routing;
-using Neo4j.Driver.Preview;
using Xunit;
namespace Neo4j.Driver.Tests
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs
new file mode 100644
index 000000000..2628f0eb1
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs
@@ -0,0 +1,396 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Moq;
+using Moq.AutoMock;
+using Neo4j.Driver.Internal.Result;
+using Xunit;
+
+namespace Neo4j.Driver.Tests.ExecutableQuery
+{
+ public class ExecutableQueryTests
+ {
+ [Fact]
+ public async Task ShouldReturnSimpleList()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ });
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var results = new List();
+ await subject.GetRowsAsync(i => results.Add(i), CancellationToken.None);
+
+ results.Should().BeEquivalentTo(Enumerable.Range(0, 10));
+ }
+
+ [Fact]
+ public async Task ShouldReturnFilteredList()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithFilter(i => i < 5)
+ .ExecuteAsync();
+
+ result.Result.Should().BeEquivalentTo(Enumerable.Range(0, 5));
+ }
+
+ [Fact]
+ public async Task ShouldReturnMappedList()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithMap(i => i + 100)
+ .ExecuteAsync();
+
+ result.Result.Should().BeEquivalentTo(Enumerable.Range(100, 10));
+ }
+
+ [Fact]
+ public async Task ShouldReturnCorrectResultSummary()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ var query = new Query("fake cypher");
+ var summary = new SummaryBuilder(
+ query,
+ autoMock.GetMock().Object).Build();
+
+ var keys = new[] { "alpha", "bravo", "charlie" };
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(summary, keys));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithMap(i => i + 100)
+ .WithMap(i => i + 100)
+ .WithFilter(i => i < 5)
+ .ExecuteAsync();
+
+ result.Keys.Should().BeSameAs(keys);
+ result.Summary.Should().BeSameAs(summary);
+ }
+
+ [Fact]
+ public async Task ShouldReturnMappedAndFilteredList()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithMap(i => i + 100)
+ .WithFilter(i => i < 105)
+ .ExecuteAsync();
+
+ result.Result.Should().BeEquivalentTo(Enumerable.Range(100, 5));
+ }
+
+ [Fact]
+ public async Task ShouldReturnMultiMappedAndFilteredList()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 100; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithMap(i => i * 2) // 0, 2, 4.. 198
+ .WithFilter(i => i < 100) // 0, 2, 4.. 98
+ .WithMap(i => i / 2) // 0, 1, 2.. 49
+ .WithFilter(i => i < 20) // 0, 1, 2.. 19
+ .WithMap(i => i * 3) // 0, 3, 6.. 57
+ .WithFilter(i => i < 10) // 0, 3, 6, 9
+ .ExecuteAsync();
+
+ result.Result.Should().BeEquivalentTo(new[] { 0, 3, 6, 9 });
+ }
+
+ [Fact]
+ public async Task ShouldReturnReducedValue()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithReduce(() => 0, (x, y) => x + y)
+ .ExecuteAsync();
+
+ result.Result.Should().Be(45);
+ }
+
+ [Fact]
+ public async Task ShouldReturnMappedReducedValue()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithMap(i => i * 10)
+ .WithReduce(() => 0, (x, y) => x + y)
+ .ExecuteAsync();
+
+ result.Result.Should().Be(450);
+ }
+
+ [Fact]
+ public async Task ShouldReturnMappedTransformedReducedValue()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ autoMock.GetMock>()
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .Callback(
+ (Action p, CancellationToken _) =>
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ p(i);
+ }
+ })
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(autoMock.GetMock>().Object, i => i);
+
+ var result = await subject
+ .WithMap(i => i * 10)
+ .WithReduce(() => 0, (x, y) => x + y, i => $"<{(i * 2)}>")
+ .ExecuteAsync();
+
+ result.Result.Should().Be("<900>");
+ }
+
+ [Fact]
+ public async Task ShouldSetConfig()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+ var config = new QueryConfig();
+
+ var driver = autoMock.GetMock>();
+ driver
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(driver.Object, i => i);
+
+ await subject
+ .WithConfig(config)
+ .ExecuteAsync();
+
+ driver.Verify(x => x.SetConfig(config));
+ }
+
+ [Fact]
+ public async Task ShouldSetParametersWithObject()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+ var parameters = new { Shaken = true, Stirred = false };
+
+ var driver = autoMock.GetMock>();
+ driver
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(driver.Object, i => i);
+
+ await subject
+ .WithParameters(parameters)
+ .ExecuteAsync();
+
+ driver.Verify(x => x.SetParameters(parameters));
+ }
+
+ [Fact]
+ public async Task ShouldSetParametersWithDictionary()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+ var parameters = new Dictionary
+ {
+ ["shaken"] = true,
+ ["stirred"] = false
+ };
+
+ var driver = autoMock.GetMock>();
+ driver
+ .Setup(x => x.GetRowsAsync(It.IsAny>(), It.IsAny()))
+ .ReturnsAsync(new ExecutionSummary(null, null));
+
+ var subject = new ExecutableQuery(driver.Object, i => i);
+
+ await subject
+ .WithParameters(parameters)
+ .ExecuteAsync();
+
+ driver.Verify(x => x.SetParameters(parameters));
+ }
+
+ [Fact]
+ public async Task ShouldUseStreamProcessor()
+ {
+ var autoMock = new AutoMocker(MockBehavior.Loose);
+
+ async IAsyncEnumerable GetInts(int start, int count)
+ {
+ foreach(var i in Enumerable.Range(start, count))
+ {
+ await Task.Yield();
+ yield return i;
+ }
+ }
+
+ var driverMock = autoMock.GetMock>();
+ driverMock
+ .Setup(
+ x => x.ProcessStreamAsync(
+ It.IsAny, Task>>(),
+ It.IsAny()))
+ .Returns, Task>, CancellationToken>(
+ (p, _) =>
+ Task.FromResult(
+ new EagerResult(
+ p(GetInts(0, 10)).GetAwaiter().GetResult(),
+ null,
+ null)));
+
+ var subject = new ExecutableQuery(driverMock.Object, i => i);
+
+ var rnd = Random.Shared.Next();
+
+ var queryExecution = await subject.WithStreamProcessor(
+ async stream =>
+ {
+ var result = rnd;
+
+ await foreach (var i in stream)
+ {
+ result += i;
+ }
+
+ return result;
+ })
+ .ExecuteAsync();
+
+ queryExecution.Result.Should().Be(45 + rnd);
+ }
+ }
+}
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/BookmarkManagerFactoryTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/BookmarkManagerFactoryTests.cs
index 1dd94485c..64b374d3e 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/BookmarkManagerFactoryTests.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/BookmarkManagerFactoryTests.cs
@@ -18,7 +18,6 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
-using Neo4j.Driver.Preview;
using Xunit;
namespace Neo4j.Driver.Internal.BookmarkManager
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/DefaultBookmarkManagerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/DefaultBookmarkManagerTests.cs
index c31989b3f..e69a2af5b 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/DefaultBookmarkManagerTests.cs
+++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/BookmarkManager/DefaultBookmarkManagerTests.cs
@@ -20,7 +20,6 @@
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
-using Neo4j.Driver.Preview;
using Xunit;
namespace Neo4j.Driver.Internal.BookmarkManager
diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj b/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj
index 66915c7ea..e1bc43962 100644
--- a/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj
+++ b/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj
@@ -19,7 +19,8 @@
-
+
+
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/BookmarkManagerConfig.cs b/Neo4j.Driver/Neo4j.Driver/BookmarkManagerConfig.cs
similarity index 99%
rename from Neo4j.Driver/Neo4j.Driver/Preview/BookmarkManagerConfig.cs
rename to Neo4j.Driver/Neo4j.Driver/BookmarkManagerConfig.cs
index 148c1b69f..5d819b5b3 100644
--- a/Neo4j.Driver/Neo4j.Driver/Preview/BookmarkManagerConfig.cs
+++ b/Neo4j.Driver/Neo4j.Driver/BookmarkManagerConfig.cs
@@ -21,7 +21,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Neo4j.Driver.Preview;
+namespace Neo4j.Driver;
///
/// The record encapsulates configuration values for initializing a new
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/EagerResult.cs b/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/EagerResult.cs
similarity index 78%
rename from Neo4j.Driver/Neo4j.Driver/Preview/EagerResult.cs
rename to Neo4j.Driver/Neo4j.Driver/ExecuteQuery/EagerResult.cs
index 2ff3f8d30..cc2cb39e2 100644
--- a/Neo4j.Driver/Neo4j.Driver/Preview/EagerResult.cs
+++ b/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/EagerResult.cs
@@ -15,12 +15,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-namespace Neo4j.Driver.Preview;
+namespace Neo4j.Driver;
///
-/// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
-/// Complete result from a cypher query.
+/// Complete, materialized result from a cypher query.
///
+/// The type of the value that will be in the property.
public sealed class EagerResult
{
internal EagerResult(T result, IResultSummary summary, string[] keys)
@@ -31,19 +31,16 @@ internal EagerResult(T result, IResultSummary summary, string[] keys)
}
///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
/// Least common set of fields in .
///
public string[] Keys { get; init; }
///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- /// All Records from query.
+ /// The materialized result of the query.
///
public T Result { get; init; }
///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
/// Query summary.
///
public IResultSummary Summary { get; init; }
diff --git a/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/IExecutableQuery.cs b/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/IExecutableQuery.cs
new file mode 100644
index 000000000..de7f32ce2
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/IExecutableQuery.cs
@@ -0,0 +1,146 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo4j.Driver;
+
+///
+/// Exposes methods for configuring and executing a driver-level query.
+///
+/// The type of items the query will receive.
+/// The type of items the query will output.
+public interface IExecutableQuery : IConfiguredQuery
+{
+ ///
+ /// Sets the query config on the query to be executed.
+ ///
+ /// The config to set.
+ /// The same instance to allow method chaining.
+ IExecutableQuery WithConfig(QueryConfig config);
+
+ ///
+ /// Sets the parameters on the query to be executed.
+ ///
+ /// A dictionary of parameter values, keyed by their names.
+ /// The same instance to allow method chaining.
+ IExecutableQuery WithParameters(Dictionary parameters);
+
+ ///
+ /// Sets the parameters on the query to be executed.
+ ///
+ /// An object whose properties have names matching the names of the query's parameters
+ /// and values that should be used as those parameters' values.
+ /// The same instance to allow method chaining.
+ IExecutableQuery WithParameters(object parameters);
+
+ ///
+ /// Specifies a stream processor that will process the that results
+ /// from a query, and return the value that will be given as the result of the query.
+ ///
+ /// An asynchronous method that will process the supplied asynchronous
+ /// stream of records.
+ /// The type of the return value from .
+ /// The same instance which can only be used to execute the query.
+ IReducedExecutableQuery WithStreamProcessor(
+ Func, Task> streamProcessor);
+}
+
+///
+/// A query that can no longer be configured.
+///
+/// The type of items that will be input into this instance.
+/// The type of items that will be output from this instance.
+public interface IConfiguredQuery
+{
+ ///
+ /// Specifies a filter that will be applied to all results in the query. If the specified delegate returns
+ /// true, the row will be included in the results. If it returns false, it will be excluded.
+ ///
+ /// The predicate that will decide whether an item is present in the results.
+ /// The same instance for method chaining.
+ IConfiguredQuery WithFilter(Func filter);
+
+ ///
+ /// Specifies a mapping that will be used to turn items of type into items of type
+ /// .
+ ///
+ /// The mapping function.
+ /// The output type of the mapping function.
+ /// A new instance whose input type is and whose input type is
+ /// .
+ ///
+ IConfiguredQuery WithMap(Func map);
+
+ ///
+ /// Specifies a method of reducing many items of type into one instance of type
+ /// .
+ ///
+ /// The initial value of the resulting value.
+ /// A method that will accumulate each value of type into
+ /// the result value.
+ /// The type of the reduced result.
+ /// The same instance which can only be used to execute the query.
+ IReducedExecutableQuery WithReduce(
+ Func seed,
+ Func accumulate);
+
+ ///
+ /// Specifies a method of reducing many items of type into one instance of type
+ /// , using one method to accumulate the values into a value of type
+ /// and another to turn the accumulated value into a result.
+ ///
+ /// The initial value of the accumulating value.
+ /// A method that will accumulate each value of type into
+ /// the accumulated value.
+ /// A method that will turn the accumulated value into a value of type
+ /// .
+ ///
+ /// The type of the reduced result.
+ /// The same instance which can only be used to execute the query.
+ IReducedExecutableQuery WithReduce(
+ Func seed,
+ Func accumulate,
+ Func selectResult);
+
+ ///
+ /// Executes the query.
+ ///
+ /// A cancellation token that can be used to cancel the operation.
+ /// An That contains the result of the query and
+ /// information about the execution.
+ Task>> ExecuteAsync(CancellationToken token = default);
+}
+
+///
+/// A query that has been configured fully and now can only be executed.
+///
+/// The type of result that the returned
+/// from the will contain.
+public interface IReducedExecutableQuery
+{
+ ///
+ /// Executes the query.
+ ///
+ /// A cancellation token that can be used to cancel the operation.
+ /// An That contains the result of the query and
+ /// information about the execution.
+ Task> ExecuteAsync(CancellationToken token = default);
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/QueryConfig.cs b/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/QueryConfig.cs
similarity index 99%
rename from Neo4j.Driver/Neo4j.Driver/Preview/QueryConfig.cs
rename to Neo4j.Driver/Neo4j.Driver/ExecuteQuery/QueryConfig.cs
index ed0d0908f..2faf5c952 100644
--- a/Neo4j.Driver/Neo4j.Driver/Preview/QueryConfig.cs
+++ b/Neo4j.Driver/Neo4j.Driver/ExecuteQuery/QueryConfig.cs
@@ -19,7 +19,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Neo4j.Driver.Preview;
+namespace Neo4j.Driver;
/// Configuration for running queries using the simplified api.
public class QueryConfig
diff --git a/Neo4j.Driver/Neo4j.Driver/GraphDatabase.cs b/Neo4j.Driver/Neo4j.Driver/GraphDatabase.cs
index 8e3096401..8e522a5b6 100644
--- a/Neo4j.Driver/Neo4j.Driver/GraphDatabase.cs
+++ b/Neo4j.Driver/Neo4j.Driver/GraphDatabase.cs
@@ -185,6 +185,14 @@ public static IDriver Driver(Uri uri, IAuthToken authToken, Action
+ /// Gets a new , which can construct a new default
+ /// instance.
+ /// The instance should be passed to
+ /// when opening a new session with .
+ ///
+ public static IBookmarkManagerFactory BookmarkManagerFactory => new BookmarkManagerFactory();
+
internal static IDriver CreateDriver(
Uri uri,
Config config,
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/IBookmarkManager.cs b/Neo4j.Driver/Neo4j.Driver/IBookmarkManager.cs
similarity index 87%
rename from Neo4j.Driver/Neo4j.Driver/Preview/IBookmarkManager.cs
rename to Neo4j.Driver/Neo4j.Driver/IBookmarkManager.cs
index f9982f1c3..57f43503e 100644
--- a/Neo4j.Driver/Neo4j.Driver/Preview/IBookmarkManager.cs
+++ b/Neo4j.Driver/Neo4j.Driver/IBookmarkManager.cs
@@ -18,14 +18,13 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Neo4j.Driver.Preview;
+namespace Neo4j.Driver;
///
-/// Preview: Subject to change.
The interface is intended for
-/// implementation by classes that provide convenient interfacing with in both the driver and user
-/// code.
+/// The interface is intended for implementation by classes that provide convenient
+/// interfacing with in both the driver and user code.
///
-///
+///
public interface IBookmarkManager
{
///
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/IBookmarkManagerFactory.cs b/Neo4j.Driver/Neo4j.Driver/IBookmarkManagerFactory.cs
similarity index 90%
rename from Neo4j.Driver/Neo4j.Driver/Preview/IBookmarkManagerFactory.cs
rename to Neo4j.Driver/Neo4j.Driver/IBookmarkManagerFactory.cs
index d93fee1e8..ffaab3106 100644
--- a/Neo4j.Driver/Neo4j.Driver/Preview/IBookmarkManagerFactory.cs
+++ b/Neo4j.Driver/Neo4j.Driver/IBookmarkManagerFactory.cs
@@ -15,10 +15,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-namespace Neo4j.Driver.Preview;
+namespace Neo4j.Driver;
///
-/// Preview: Subject to change.
The interface is intended for
+/// The interface is intended for
/// classes that construct instances of an implementation.
///
public interface IBookmarkManagerFactory
diff --git a/Neo4j.Driver/Neo4j.Driver/IDriver.cs b/Neo4j.Driver/Neo4j.Driver/IDriver.cs
index 9c4ad1c60..06e711c4b 100644
--- a/Neo4j.Driver/Neo4j.Driver/IDriver.cs
+++ b/Neo4j.Driver/Neo4j.Driver/IDriver.cs
@@ -16,6 +16,7 @@
// limitations under the License.
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
namespace Neo4j.Driver;
@@ -93,4 +94,15 @@ public interface IDriver : IDisposable, IAsyncDisposable
/// cluster support multi-databases, otherwise false.
///
Task SupportsMultiDbAsync();
+
+ ///
+ /// Gets an that can be used to configure and execute a query
+ /// using fluent method chaining.
+ ///
+ /// The cypher of the query.
+ ///
+ /// An that can be used to configure and execute a query using
+ /// fluent method chaining.
+ ///
+ IExecutableQuery ExecutableQuery(string cypher);
}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs b/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs
index a679dccd3..1eaf200a0 100644
--- a/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs
@@ -20,7 +20,6 @@
using System.Linq;
using System.Threading.Tasks;
using Neo4j.Driver.Internal.Connector;
-using Neo4j.Driver.Preview;
using static Neo4j.Driver.Internal.Logging.DriverLoggerUtil;
using static Neo4j.Driver.Internal.Util.ConfigBuilders;
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/BookmarkManagerFactory.cs b/Neo4j.Driver/Neo4j.Driver/Internal/BookmarkManagerFactory.cs
index 64aaeded1..19da92e19 100644
--- a/Neo4j.Driver/Neo4j.Driver/Internal/BookmarkManagerFactory.cs
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/BookmarkManagerFactory.cs
@@ -14,9 +14,6 @@
// 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 Neo4j.Driver.Preview;
-
namespace Neo4j.Driver.Internal;
internal class BookmarkManagerFactory : IBookmarkManagerFactory
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/DefaultBookmarkManager.cs b/Neo4j.Driver/Neo4j.Driver/Internal/DefaultBookmarkManager.cs
index 8089d82bb..d8eb1ff6f 100644
--- a/Neo4j.Driver/Neo4j.Driver/Internal/DefaultBookmarkManager.cs
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/DefaultBookmarkManager.cs
@@ -20,7 +20,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Neo4j.Driver.Preview;
namespace Neo4j.Driver.Internal;
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs
index 06fe81cdd..972486863 100644
--- a/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs
@@ -22,7 +22,6 @@
using Neo4j.Driver.Internal.Metrics;
using Neo4j.Driver.Internal.Routing;
using Neo4j.Driver.Internal.Util;
-using Neo4j.Driver.Preview;
namespace Neo4j.Driver.Internal;
@@ -97,6 +96,15 @@ public IInternalAsyncSession Session(Action action, bool r
return session;
}
+ public Task> ExecuteQueryAsync(
+ Query query,
+ Func, Task> streamProcessor,
+ QueryConfig config = null,
+ CancellationToken cancellationToken = default)
+ {
+ return ExecuteQueryAsyncInternal(query, config, cancellationToken, TransformCursor(streamProcessor));
+ }
+
public Task CloseAsync()
{
return Interlocked.CompareExchange(ref _closedMarker, 1, 0) == 0
@@ -133,6 +141,12 @@ public Task SupportsMultiDbAsync()
return _connectionProvider.SupportsMultiDbAsync();
}
+ //Non public facing api. Used for testing with testkit only
+ public IRoutingTable GetRoutingTable(string database)
+ {
+ return _connectionProvider.GetRoutingTable(database);
+ }
+
public void Dispose()
{
Dispose(true);
@@ -145,17 +159,31 @@ public ValueTask DisposeAsync()
: new ValueTask(Task.CompletedTask);
}
- public Task> ExecuteQueryAsync(
+ public async Task GetRowsAsync(
Query query,
- Func, ValueTask> streamProcessor,
- QueryConfig config = null,
- CancellationToken cancellationToken = default)
+ QueryConfig config,
+ Action streamProcessor,
+ CancellationToken cancellationToken
+ )
{
- return ExecuteQueryAsyncInternal(
- query,
- config,
- cancellationToken,
- TransformCursor(streamProcessor));
+ async Task Process(IAsyncEnumerable records)
+ {
+ await foreach (var record in records.ConfigureAwait(false))
+ {
+ streamProcessor(record);
+ }
+
+ return 0;
+ }
+
+ var eagerResult = await ExecuteQueryAsyncInternal(
+ query,
+ config,
+ cancellationToken,
+ TransformCursor(Process))
+ .ConfigureAwait(false);
+
+ return new ExecutionSummary(eagerResult.Summary, eagerResult.Keys);
}
private void Close()
@@ -163,12 +191,6 @@ private void Close()
CloseAsync().GetAwaiter().GetResult();
}
- //Non public facing api. Used for testing with testkit only
- public IRoutingTable GetRoutingTable(string database)
- {
- return _connectionProvider.GetRoutingTable(database);
- }
-
private void Dispose(bool disposing)
{
if (IsClosed)
@@ -223,16 +245,21 @@ private async Task> ExecuteQueryAsyncInternal(
}
}
+ public IExecutableQuery ExecutableQuery(string cypher)
+ {
+ return new ExecutableQuery(new DriverRowSource(this, cypher), x => x);
+ }
+
private static Func>> TransformCursor(
- Func, ValueTask> streamProcessor)
+ Func, Task> streamProcessor)
{
async Task> TransformCursorImpl(
IResultCursor cursor,
CancellationToken cancellationToken)
{
- var processedStream = await streamProcessor(cursor);
+ var processedStream = await streamProcessor(cursor).ConfigureAwait(false);
var summary = await cursor.ConsumeAsync().ConfigureAwait(false);
- var keys = await cursor.KeysAsync();
+ var keys = await cursor.KeysAsync().ConfigureAwait(false);
return new EagerResult(processedStream, summary, keys);
}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/DriverRowSource.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/DriverRowSource.cs
new file mode 100644
index 000000000..d639ba27a
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/DriverRowSource.cs
@@ -0,0 +1,76 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Neo4j.Driver.Internal;
+
+namespace Neo4j.Driver;
+
+internal interface IDriverRowSource : IQueryRowSource
+{
+ void SetConfig(QueryConfig config);
+ void SetParameters(Dictionary parameters);
+ void SetParameters(object parameters);
+ Task> ProcessStreamAsync(
+ Func, Task> streamProcessor,
+ CancellationToken cancellationToken = default);
+}
+
+internal class DriverRowSource : IDriverRowSource
+{
+ private readonly IInternalDriver _driver;
+ private Query _query;
+ private QueryConfig _queryConfig;
+
+ internal DriverRowSource(IInternalDriver driver, string cypher)
+ {
+ _driver = driver;
+ _query = new Query(cypher);
+ }
+
+ public Task GetRowsAsync(
+ Action rowProcessor,
+ CancellationToken cancellationToken = default)
+ {
+ return _driver.GetRowsAsync(_query, _queryConfig, rowProcessor, cancellationToken);
+ }
+
+ public Task> ProcessStreamAsync(
+ Func, Task> streamProcessor,
+ CancellationToken cancellationToken = default)
+ {
+ return _driver.ExecuteQueryAsync(_query, streamProcessor, _queryConfig, cancellationToken);
+ }
+
+ public void SetConfig(QueryConfig config)
+ {
+ _queryConfig = config;
+ }
+
+ public void SetParameters(Dictionary parameters)
+ {
+ _query = new Query(_query.Text, parameters);
+ }
+
+ public void SetParameters(object parameters)
+ {
+ _query = new Query(_query.Text, parameters);
+ }
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ExecutableQuery.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ExecutableQuery.cs
new file mode 100644
index 000000000..ca2e549f3
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ExecutableQuery.cs
@@ -0,0 +1,133 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo4j.Driver;
+
+internal class ExecutableQuery : IExecutableQuery, IQueryRowSource
+{
+ private readonly IQueryRowSource _rowSource;
+ private readonly Func _mapper;
+ private readonly List> _filters = new();
+ private Func _reduceSeed;
+ private Action _accumulateValue;
+
+ internal ExecutableQuery(
+ IQueryRowSource rowSource,
+ Func mapper)
+ {
+ _rowSource = rowSource;
+ _mapper = mapper;
+ }
+
+ public IExecutableQuery WithConfig(QueryConfig config)
+ {
+ if (_rowSource is IDriverRowSource driverRowSource)
+ {
+ driverRowSource.SetConfig(config);
+ }
+
+ return this;
+ }
+
+ public IExecutableQuery WithParameters(object parameters)
+ {
+ if (_rowSource is IDriverRowSource driverRowSource)
+ {
+ driverRowSource.SetParameters(parameters);
+ }
+
+ return this;
+ }
+
+ public IExecutableQuery WithParameters(Dictionary parameters)
+ {
+ if (_rowSource is IDriverRowSource driverRowSource)
+ {
+ driverRowSource.SetParameters(parameters);
+ }
+
+ return this;
+ }
+
+ ///
+ public IReducedExecutableQuery WithStreamProcessor(
+ Func, Task> streamProcessor)
+ {
+ if (_rowSource is IDriverRowSource driverRowSource)
+ {
+ return new StreamProcessorExecutableQuery(driverRowSource, streamProcessor);
+ }
+
+ // this can't actually happen, throwing to satisfy the compiler and for safety
+ throw new InvalidOperationException("WithStreamProcessor cannot be called on nested queries");
+ }
+
+ public IConfiguredQuery WithFilter(Func filter)
+ {
+ _filters.Add(filter);
+ return this;
+ }
+
+ public IConfiguredQuery WithMap(
+ Func map)
+ {
+ return new ExecutableQuery(this, map);
+ }
+
+ public IReducedExecutableQuery WithReduce(
+ Func seed,
+ Func accumulate,
+ Func selectResult)
+ {
+ return new ReducedExecutableQuery(this, seed, accumulate, selectResult);
+ }
+
+ public IReducedExecutableQuery WithReduce(
+ Func seed,
+ Func accumulate)
+ {
+ return new ReducedExecutableQuery(this, seed, accumulate, x => x);
+ }
+
+ public Task>> ExecuteAsync(CancellationToken token = default)
+ {
+ return WithReduce(ReduceToList.Seed, ReduceToList.Accumulate, ReduceToList.SelectResult)
+ .ExecuteAsync(token);
+ }
+
+ public Task GetRowsAsync(
+ Action rowProcessor,
+ CancellationToken cancellationToken = default)
+ {
+ void ProcessRow(TIn rowItem)
+ {
+ var mapped = _mapper(rowItem);
+ if (_filters.All(f => f(mapped)))
+ {
+ rowProcessor(mapped);
+ }
+ }
+
+ return _rowSource.GetRowsAsync(ProcessRow, cancellationToken);
+ }
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/IQueryRowSource.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/IQueryRowSource.cs
new file mode 100644
index 000000000..65a7befc1
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/IQueryRowSource.cs
@@ -0,0 +1,29 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Threading;
+using System.Threading.Tasks;
+
+namespace Neo4j.Driver;
+
+internal record ExecutionSummary(IResultSummary Summary, string[] Keys);
+
+internal interface IQueryRowSource
+{
+ Task GetRowsAsync(Action rowProcessor, CancellationToken cancellationToken = default);
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReduceToList.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReduceToList.cs
new file mode 100644
index 000000000..bf2a38c1a
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReduceToList.cs
@@ -0,0 +1,36 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Collections.Generic;
+
+namespace Neo4j.Driver;
+
+internal static class ReduceToList
+{
+ public static List Seed() => new();
+
+ public static List Accumulate(List list, T item)
+ {
+ list.Add(item);
+ return list;
+ }
+
+ public static IReadOnlyList SelectResult(List accumulation)
+ {
+ return accumulation;
+ }
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReducedExecutableQuery.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReducedExecutableQuery.cs
new file mode 100644
index 000000000..581dae5e9
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/ReducedExecutableQuery.cs
@@ -0,0 +1,54 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Threading;
+using System.Threading.Tasks;
+
+namespace Neo4j.Driver;
+
+internal class ReducedExecutableQuery : IReducedExecutableQuery
+{
+ private readonly Query _query;
+ private readonly IQueryRowSource _rowSource;
+ private readonly QueryConfig _queryConfig;
+ private readonly Func _seed;
+ private readonly Func _accumulate;
+ private readonly Func _selectResult;
+
+ internal ReducedExecutableQuery(
+ IQueryRowSource rowSource,
+ Func seed,
+ Func accumulate,
+ Func selectResult)
+ {
+ _rowSource = rowSource;
+ _seed = seed;
+ _accumulate = accumulate;
+ _selectResult = selectResult;
+ }
+
+ public async Task> ExecuteAsync(CancellationToken token = default)
+ {
+ var accumulator = _seed();
+ var executionSummary = await _rowSource.GetRowsAsync(
+ item => accumulator = _accumulate(accumulator, item),
+ token);
+
+ return new EagerResult(_selectResult(accumulator), executionSummary.Summary, executionSummary.Keys);
+ }
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/StreamProcessorExecutableQuery.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/StreamProcessorExecutableQuery.cs
new file mode 100644
index 000000000..42ee6b736
--- /dev/null
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExecuteQuery/StreamProcessorExecutableQuery.cs
@@ -0,0 +1,42 @@
+// Copyright (c) "Neo4j"
+// Neo4j Sweden AB [http://neo4j.com]
+//
+// This file is part of Neo4j.
+//
+// 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.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo4j.Driver;
+
+internal class StreamProcessorExecutableQuery : IReducedExecutableQuery
+{
+ private readonly IDriverRowSource _driverRowSource;
+ private readonly Func, Task> _streamProcessor;
+
+ public StreamProcessorExecutableQuery(
+ IDriverRowSource driverRowSource,
+ Func,Task> streamProcessor)
+ {
+ _driverRowSource = driverRowSource;
+ _streamProcessor = streamProcessor;
+ }
+
+ public Task> ExecuteAsync(CancellationToken token = default)
+ {
+ return _driverRowSource.ProcessStreamAsync(_streamProcessor, token);
+ }
+}
diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IInternalDriver.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IInternalDriver.cs
index 42a45e1d7..fd354e858 100644
--- a/Neo4j.Driver/Neo4j.Driver/Internal/IInternalDriver.cs
+++ b/Neo4j.Driver/Neo4j.Driver/Internal/IInternalDriver.cs
@@ -19,7 +19,6 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using Neo4j.Driver.Preview;
namespace Neo4j.Driver.Internal;
@@ -29,7 +28,14 @@ internal interface IInternalDriver : IDriver
Task> ExecuteQueryAsync(
Query query,
- Func, ValueTask> streamProcessor,
+ Func, Task> streamProcessor,
QueryConfig config = null,
CancellationToken cancellationToken = default);
+
+ Task GetRowsAsync(
+ Query query,
+ QueryConfig config,
+ Action streamProcessor,
+ CancellationToken cancellationToken
+ );
}
diff --git a/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj b/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj
index 6838d26a3..cfab22afd 100644
--- a/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj
+++ b/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj
@@ -56,4 +56,7 @@
+
+
+
diff --git a/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj.DotSettings b/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj.DotSettings
index af250cf20..4d1da9fe5 100644
--- a/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj.DotSettings
+++ b/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj.DotSettings
@@ -1,19 +1,14 @@
-
+
Library
- True
+ True
+ True
True
+ True
+ True
+ True
- True
- True
- True
- True
- True
+ True
+ True
+ True
+ True
+ True
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/ExecutableQuery.cs b/Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/ExecutableQuery.cs
deleted file mode 100644
index afa030a3d..000000000
--- a/Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/ExecutableQuery.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) "Neo4j"
-// Neo4j Sweden AB [http://neo4j.com]
-//
-// This file is part of Neo4j.
-//
-// 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.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Neo4j.Driver.Internal;
-
-namespace Neo4j.Driver.Preview.FluentQueries;
-
-internal class ExecutableQuery : IExecutableQuery
-{
- private readonly IInternalDriver _driver;
- private Query _query;
- private QueryConfig _queryConfig;
- private readonly Func, ValueTask> _streamProcessor;
-
- private ExecutableQuery(
- Query query,
- IInternalDriver driver,
- QueryConfig queryConfig,
- Func, ValueTask> streamProcessor)
- {
- _query = query;
- _driver = driver;
- _queryConfig = queryConfig;
- _streamProcessor = streamProcessor;
- }
-
- public IExecutableQuery WithConfig(QueryConfig config)
- {
- _queryConfig = config;
- return this;
- }
-
- public IExecutableQuery WithParameters(object parameters)
- {
- _query = new Query(_query.Text, parameters);
- return this;
- }
-
- public IExecutableQuery WithParameters(Dictionary parameters)
- {
- _query = new Query(_query.Text, parameters);
- return this;
- }
-
- public IExecutableQuery WithStreamProcessor(
- Func, ValueTask> streamProcessor)
- {
- return new ExecutableQuery(_query, _driver, _queryConfig, streamProcessor);
- }
-
- // removing since behaviour is different to WithParameters, pending discussion
- // public IExecutableQuery WithParameter(string name, object value)
- // {
- // _query.Parameters[name] = value;
- // return this;
- // }
-
- public Task> ExecuteAsync(CancellationToken cancellationToken = default)
- {
- return _driver.ExecuteQueryAsync(
- _query,
- _streamProcessor,
- _queryConfig,
- cancellationToken);
- }
-
- public static ExecutableQuery> GetDefault(IInternalDriver driver, string cypher)
- {
- return new ExecutableQuery>(new Query(cypher), driver, null, ToListAsync);
- }
-
- private static async ValueTask> ToListAsync(IAsyncEnumerable enumerable)
- {
- var result = new List();
- await foreach (var item in enumerable)
- {
- result.Add(item);
- }
-
- return result;
- }
-}
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/IExecutableQuery.cs b/Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/IExecutableQuery.cs
deleted file mode 100644
index 84e516505..000000000
--- a/Neo4j.Driver/Neo4j.Driver/Preview/FluentQueries/IExecutableQuery.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) "Neo4j"
-// Neo4j Sweden AB [http://neo4j.com]
-//
-// This file is part of Neo4j.
-//
-// 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.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Neo4j.Driver.Preview.FluentQueries;
-
-/// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
-public interface IExecutableQuery
-{
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- /// Adds the specified config to the executable query.
- ///
- /// The query config to use.
- /// The executable query object allowing method chaining.
- IExecutableQuery WithConfig(QueryConfig config);
-
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- /// Sets the named parameters on the query.
- ///
- /// The query parameters, specified as an object which is then converted into key-value pairs.
- /// The executable query object allowing method chaining.
- IExecutableQuery WithParameters(object parameters);
-
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- /// Sets the named parameters on the query.
- ///
- ///
- /// The query's parameters, whose values should not be changed while the query is used in a
- /// session/transaction.
- ///
- /// The executable query object allowing method chaining.
- IExecutableQuery WithParameters(Dictionary parameters);
-
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- /// Adds a stream processor function that will be called, passing the of records
- /// returned from the query. The value returned from this callback property will be present in the
- /// property of the result returned from executing the query.
- ///
- /// The stream processor function.
- /// The executable query object allowing method chaining.
- IExecutableQuery WithStreamProcessor(
- Func, ValueTask> streamProcessor);
-
- /// Executes the query as configured and returns the results, fully materialised.
- /// A cancellation token that can be used to cancel the asynchronous operation.
- /// An containing the results of the query.
- Task> ExecuteAsync(CancellationToken cancellationToken = default);
-}
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/GraphDatabase.cs b/Neo4j.Driver/Neo4j.Driver/Preview/GraphDatabase.cs
deleted file mode 100644
index 324514742..000000000
--- a/Neo4j.Driver/Neo4j.Driver/Preview/GraphDatabase.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) "Neo4j"
-// Neo4j Sweden AB [http://neo4j.com]
-//
-// This file is part of Neo4j.
-//
-// 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 Neo4j.Driver.Internal;
-
-namespace Neo4j.Driver.Preview;
-
-///
-/// Methods being considered for moving to the Neo4j.Driver. There is no guarantee that anything in
-/// Neo4j.Driver.Preview namespace will be in a next minor version.
-///
-public static class GraphDatabase
-{
- ///
- /// Preview: Bookmark Manager API is still under consideration.
There is no guarantee that anything in
- /// Neo4j.Driver.Preview namespace will be in a next minor version.
Gets a new
- /// , which can construct a new default instance.
- /// The instance should be passed to when opening a new
- /// session with .
- ///
- public static IBookmarkManagerFactory BookmarkManagerFactory => new BookmarkManagerFactory();
-}
diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/PreviewExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Preview/PreviewExtensions.cs
deleted file mode 100644
index 5253cc501..000000000
--- a/Neo4j.Driver/Neo4j.Driver/Preview/PreviewExtensions.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) "Neo4j"
-// Neo4j Sweden AB [http://neo4j.com]
-//
-// This file is part of Neo4j.
-//
-// 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.Collections.Generic;
-using Neo4j.Driver.Internal;
-using Neo4j.Driver.Preview.FluentQueries;
-
-namespace Neo4j.Driver.Preview;
-
-///
-/// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
-///
This class provides access to preview APIs on existing non-static classes.
-///
-public static class PreviewExtensions
-{
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- ///
Sets the for maintaining bookmarks for the lifetime of the session.
- ///
- /// This instance.
- /// An instance of to use in the session.
- /// this instance.
- public static SessionConfigBuilder WithBookmarkManager(
- this SessionConfigBuilder builder,
- IBookmarkManager bookmarkManager)
- {
- return builder.WithBookmarkManager(bookmarkManager);
- }
-
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- /// Gets an that can be used to configure and execute a query using fluent
- /// method chaining.
- ///
- ///
- /// The following example configures and executes a simple query, then iterates over the results.
- ///
- /// var eagerResult = await driver
- /// .ExecutableQuery("MATCH (m:Movie) WHERE m.released > $releaseYear RETURN m.title AS title")
- /// .WithParameters(new { releaseYear = 2005 })
- /// .ExecuteAsync();
- ///
- /// foreach(var record in eagerResult.Result)
- /// {
- /// Console.WriteLine(record["title"].As<string>());
- /// }
- ///
- ///
- /// The following example gets a single scalar value from a query.
- ///
- /// var born = await driver
- /// .ExecutableQuery("MATCH (p:Person WHERE p.name = $name) RETURN p.born AS born")
- /// .WithStreamProcessor(async stream => (await stream.Where(_ => true).FirstAsync())["born"].As<int>())
- /// .WithParameters(new Dictionary<string, object> { ["name"] = "Tom Hanks" })
- /// .ExecuteAsync();
- ///
- /// Console.WriteLine($"Tom Hanks born {born.Result}");
- ///
- ///
- /// The driver.
- /// The cypher of the query.
- ///
- /// An that can be used to configure and execute a query using
- /// fluent method chaining.
- ///
- public static IExecutableQuery> ExecutableQuery(this IDriver driver, string cypher)
- {
- return ExecutableQuery>.GetDefault((IInternalDriver)driver, cypher);
- }
-
- ///
- /// There is no guarantee that anything in Neo4j.Driver.Preview namespace will be in a next minor version.
- ///
Preview: This method will be removed and replaced with a readonly property "BookmarkManager" on the
- /// class.
Gets the configured preview bookmark manager from this
- /// instance.
- ///
- ///
- /// This instance.
- /// This 's configured instance.
- public static IBookmarkManager GetBookmarkManager(this SessionConfig config)
- {
- return config.BookmarkManager;
- }
-}
diff --git a/Neo4j.Driver/Neo4j.Driver/SessionConfig.cs b/Neo4j.Driver/Neo4j.Driver/SessionConfig.cs
index 48753b8b3..171c74a96 100644
--- a/Neo4j.Driver/Neo4j.Driver/SessionConfig.cs
+++ b/Neo4j.Driver/Neo4j.Driver/SessionConfig.cs
@@ -20,7 +20,6 @@
using System.Linq;
using Neo4j.Driver.Internal;
using Neo4j.Driver.Internal.Types;
-using Neo4j.Driver.Preview;
namespace Neo4j.Driver;
@@ -281,8 +280,12 @@ internal SessionConfig Build()
return _config;
}
- /// marked as internal until API is solidified.
- internal SessionConfigBuilder WithBookmarkManager(IBookmarkManager bookmarkManager)
+ ///
+ /// Sets the for maintaining bookmarks for the lifetime of the session.
+ ///
+ /// An instance of to use in the session.
+ /// this instance.
+ public SessionConfigBuilder WithBookmarkManager(IBookmarkManager bookmarkManager)
{
_config.BookmarkManager = bookmarkManager;
return this;
diff --git a/README.md b/README.md
index dfd1b3e70..f77a18796 100644
--- a/README.md
+++ b/README.md
@@ -61,53 +61,72 @@ PM> Install-Package Neo4j.Driver.Signed
Connect to a Neo4j database
```csharp
-IDriver driver = GraphDatabase.Driver("neo4j://localhost:7687", AuthTokens.Basic("username", "pasSW0rd"));
-IAsyncSession session = driver.AsyncSession(o => o.WithDatabase("neo4j"));
-try
-{
- IResultCursor cursor = await session.RunAsync("CREATE (n) RETURN n");
- await cursor.ConsumeAsync();
-}
-finally
-{
- await session.CloseAsync();
-}
-
-...
-await driver.CloseAsync();
+using var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "password"));
+var queryOperation = await driver.ExecutableQuery("CREATE (n) RETURN n").ExecuteAsync();
```
There are a few points that need to be highlighted when adding this driver into your project:
-* Each `IDriver` instance maintains a pool of connections inside, as a result, it is recommended to only use **one
- driver per application**.
-* It is considerably cheap to create new sessions and transactions, as sessions and transactions do not create new
- connections as long as there are free connections available in the connection pool.
+* Each `IDriver` instance maintains a pool of connections inside; as a result, it is recommended that you use **only one driver per application**.
+* It is considerably cheap to create new sessions and transactions, as sessions and transactions do not create new connections as long as there are free connections available in the connection pool.
* The driver is thread-safe, while the session or the transaction is not thread-safe.
### Parsing Result Values
-#### Record Stream
+#### Query Execution Result
-A cypher execution result is comprised of a stream records followed by a result summary.
-The records inside the result are accessible via `FetchAsync` and `Current` methods on `IResultCursor`.
-Our recommended way to access these result records is to make use of methods provided by `ResultCursorExtensions` such
-as `SingleAsync`, `ToListAsync`, and `ForEachAsync`.
+The result of executing a query in the way shown above is an `EagerResult`, where `T` is the type of data returned from the query. In the simplest case, this will be an `IReadOnlyList`, which is a fully materialized list of the records returned by the query. Other types of results can be used by using the fluent API exposed by the `IExecutableQuery` type.
-Process result records using `ResultCursorExtensions`:
+##### Examples
```csharp
-IResultCursor cursor = await session.RunAsync("MATCH (a:Person) RETURN a.name as name");
-List people = await cursor.ToListAsync(record => record["name"].As());
+var queryOp = await driver
+ .ExecutableQuery("MATCH (person:Person) RETURN person")
+ .WithMap(r => r["person"].As()["name"].As())
+ .ExecuteAsync();
+// queryOp.Result is IReadOnlyList
+
+var queryOp = await driver
+ .ExecutableQuery("MATCH (person:Person) RETURN person")
+ .WithMap(r => r["person"].As()["name"].As())
+ .WithFilter(s => s.StartsWith("A"))
+ .ExecuteAsync();
+// queryOp.Result is IReadOnlyList - note that this type of filtering
+// is done on the client and is not a substitute for filtering in the Cypher
+
+var queryOp = await driver
+ .ExecutableQuery("MATCH (person:Person) RETURN person")
+ .WithMap(r => r["person"].As()["age"].As())
+ .WithReduce(() => 0, (r, n) => r + n)
+ .ExecuteAsync();
+// queryOp.Result is `int`, the sum of all the ages
+
+var queryOp = await driver
+ .ExecutableQuery("MATCH (person:Person) RETURN person")
+ .WithStreamProcessor(
+ async stream =>
+ {
+ double total = 0;
+ int count = 0;
+
+ await foreach(var record in stream)
+ {
+ var ages = record["person"].As()["age"].As();
+ total += age;
+ count++;
+ }
+
+ return total / count;
+ })
+ .ExecuteAsync();
+// queryOp.Result is `double`, the average of all the ages
```
-The records are exposed as a record stream in the sense that:
+#### Record Stream
-* A record is accessible once it is received by the client. It is not needed for the whole result set to be received
- before it can be visited.
-* Each record can only be visited (a.k.a. consumed) once.
+A cypher execution result is comprised of a stream records followed by a result summary. The stream can be accessed directly by using the `WithStreamProcessor` method as shown above. The stream implements `IAsyncEnumerable` and as such can be accessed with normal Linq methods by adding the `System.Linq.Async` package to your project. The `ExecuteAsync` method will return an `EagerResult`, where `T` is the type of value returned from the method passed to `WithStreamProcessor`. This value will be stored in the `Result` property of the `EagerResult`, with the `Summary` and `Keys` property containing further information about the execution of the query.
-Records on a result cannot be accessed if the session or transaction where the result is created has been closed.
+Process result records using `ResultCursorExtensions`:
#### Value Types