Skip to content

Commit 9e44868

Browse files
Expose IASyncEnumerable in callback to allow async linq to be used (#676)
1 parent c1922c3 commit 9e44868

File tree

9 files changed

+180
-209
lines changed

9 files changed

+180
-209
lines changed

Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal class ExecuteQuery : IProtocolObject
1111
{
1212
public ExecuteQueryDto data { get; set; }
1313
[JsonIgnore]
14-
public EagerResult Result { get; set; }
14+
public EagerResult<IReadOnlyList<IRecord>> Result { get; set; }
1515

1616
public class ExecuteQueryDto
1717
{
@@ -36,11 +36,13 @@ public override async Task Process()
3636
var driver = ObjManager.GetObject<NewDriver>(data.driverId).Driver;
3737
var queryConfig = BuildConfig();
3838

39-
Result = await driver
39+
var queryResult = await driver
4040
.ExecutableQuery(data.cypher)
4141
.WithParameters(data.parameters)
4242
.WithConfig(queryConfig)
4343
.ExecuteAsync();
44+
45+
Result = queryResult;
4446
}
4547

4648
private QueryConfig BuildConfig()
@@ -73,7 +75,7 @@ private QueryConfig BuildConfig()
7375

7476
public override string Respond()
7577
{
76-
var mappedList = Result.Records
78+
var mappedList = Result.Result
7779
.Select(x => new
7880
{
7981
values = x.Values
Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,70 @@
1-
// Copyright (c) 2002-2022 "Neo4j,"
1+
// Copyright (c) "Neo4j"
22
// Neo4j Sweden AB [http://neo4j.com]
3-
//
3+
//
44
// This file is part of Neo4j.
5-
//
6-
// Licensed under the Apache License, Version 2.0 (the "License");
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License"):
77
// you may not use this file except in compliance with the License.
88
// You may obtain a copy of the License at
9-
//
9+
//
1010
// http://www.apache.org/licenses/LICENSE-2.0
11-
//
11+
//
1212
// Unless required by applicable law or agreed to in writing, software
1313
// distributed under the License is distributed on an "AS IS" BASIS,
1414
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515
// See the License for the specific language governing permissions and
1616
// limitations under the License.
1717

18-
using System.Collections;
19-
using System.Collections.Generic;
20-
2118
namespace Neo4j.Driver.Experimental;
2219

2320
/// <summary>
2421
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
2522
/// Complete result from a cypher query.
2623
/// </summary>
27-
public sealed class EagerResult : IReadOnlyList<IRecord>
24+
public sealed class EagerResult<T>
2825
{
26+
internal EagerResult(T result, IResultSummary summary, string[] keys)
27+
{
28+
Result = result;
29+
Summary = summary;
30+
Keys = keys;
31+
}
32+
2933
/// <summary>
3034
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
31-
/// Least common set of fields in <see cref="Records"/>.
35+
/// Least common set of fields in <see cref="Result" />.
3236
/// </summary>
3337
public string[] Keys { get; init; }
3438

3539
/// <summary>
3640
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
3741
/// All Records from query.
3842
/// </summary>
39-
public IRecord[] Records { get; init; }
43+
public T Result { get; init; }
4044

4145
/// <summary>
4246
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
4347
/// Query summary.
4448
/// </summary>
4549
public IResultSummary Summary { get; init; }
4650

47-
/// <inheritdoc />
48-
public IEnumerator<IRecord> GetEnumerator()
51+
/// <summary>Deconstructs the result into its constituent parts.</summary>
52+
/// <param name="result">The result returned from the query.</param>
53+
/// <param name="summary">The summary of the result.</param>
54+
/// <param name="keys">The keys present in the result.</param>
55+
public void Deconstruct(out T result, out IResultSummary summary, out string[] keys)
4956
{
50-
return ((IEnumerable<IRecord>)Records).GetEnumerator();
57+
keys = Keys;
58+
result = Result;
59+
summary = Summary;
5160
}
5261

53-
/// <inheritdoc />
54-
IEnumerator IEnumerable.GetEnumerator()
62+
/// <summary>Deconstructs the result into its constituent parts.</summary>
63+
/// <param name="result">The result returned from the query.</param>
64+
/// <param name="summary">The summary of the result.</param>
65+
public void Deconstruct(out T result, out IResultSummary summary)
5566
{
56-
return Records.GetEnumerator();
67+
result = Result;
68+
summary = Summary;
5769
}
58-
59-
/// <inheritdoc />
60-
public int Count => Records.Length;
61-
62-
/// <inheritdoc />
63-
public IRecord this[int index] => Records[index];
6470
}

Neo4j.Driver/Neo4j.Driver/Experimental/ExperimentalExtensions.cs

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// Copyright (c) 2002-2022 "Neo4j,"
1+
// Copyright (c) "Neo4j"
22
// Neo4j Sweden AB [http://neo4j.com]
33
//
44
// This file is part of Neo4j.
55
//
6-
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// Licensed under the Apache License, Version 2.0 (the "License"):
77
// you may not use this file except in compliance with the License.
88
// You may obtain a copy of the License at
99
//
@@ -15,66 +15,82 @@
1515
// See the License for the specific language governing permissions and
1616
// limitations under the License.
1717

18+
using System.Collections.Generic;
1819
using Neo4j.Driver.Experimental.FluentQueries;
1920
using Neo4j.Driver.Internal;
2021

2122
namespace Neo4j.Driver.Experimental;
2223

2324
/// <summary>
24-
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.<br/>
25-
/// This class provides access to experimental APIs on existing non-static classes.
25+
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
26+
/// <br /> This class provides access to experimental APIs on existing non-static classes.
2627
/// </summary>
2728
public static class ExperimentalExtensions
2829
{
2930
/// <summary>
30-
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.<br/>
31-
/// Sets the <see cref="IBookmarkManager"/> for maintaining bookmarks for the lifetime of the session.
31+
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
32+
/// <br /> Sets the <see cref="IBookmarkManager" /> for maintaining bookmarks for the lifetime of the session.
3233
/// </summary>
33-
/// <param name="builder">This <see cref="SessionConfigBuilder"/> instance.</param>
34-
/// <param name="bookmarkManager">An instance of <see cref="IBookmarkManager"/> to use in the session.</param>
35-
/// <returns>this <see cref="SessionConfigBuilder"/> instance.</returns>
36-
public static SessionConfigBuilder WithBookmarkManager(this SessionConfigBuilder builder, IBookmarkManager bookmarkManager)
34+
/// <param name="builder">This <see cref="SessionConfigBuilder" /> instance.</param>
35+
/// <param name="bookmarkManager">An instance of <see cref="IBookmarkManager" /> to use in the session.</param>
36+
/// <returns>this <see cref="SessionConfigBuilder" /> instance.</returns>
37+
public static SessionConfigBuilder WithBookmarkManager(
38+
this SessionConfigBuilder builder,
39+
IBookmarkManager bookmarkManager)
3740
{
3841
return builder.WithBookmarkManager(bookmarkManager);
3942
}
4043

4144
/// <summary>
4245
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
43-
/// Gets an <see cref="IExecutableQuery"/> that can be used to configure and execute a query using
44-
/// fluent method chaining.
46+
/// Gets an <see cref="IExecutableQuery&lt;IRecord&gt;" /> that can be used to configure and execute a query using fluent
47+
/// method chaining.
4548
/// </summary>
4649
/// <example>
4750
/// The following example configures and executes a simple query, then iterates over the results.
4851
/// <code language="cs">
49-
/// var queryResult = await driver
50-
/// .ExecutableQuery("MATCH (m:Movie) WHERE m.released > $releaseYear RETURN m.title AS title")
51-
/// .WithParameters(new { releaseYear = 2005 })
52-
/// .ExecuteAsync();
53-
///
54-
/// foreach(var record in queryResult)
55-
/// {
56-
/// Console.WriteLine(record["title"].As&lt;string&gt;());
57-
/// }
58-
/// </code>
52+
/// var eagerResult = await driver
53+
/// .ExecutableQuery("MATCH (m:Movie) WHERE m.released > $releaseYear RETURN m.title AS title")
54+
/// .WithParameters(new { releaseYear = 2005 })
55+
/// .ExecuteAsync();
56+
/// <para></para>
57+
/// foreach(var record in eagerResult.Result)
58+
/// {
59+
/// Console.WriteLine(record["title"].As&lt;string&gt;());
60+
/// }
61+
/// </code>
62+
/// <para></para>
63+
/// The following example gets a single scalar value from a query.
64+
/// <code>
65+
/// var born = await driver
66+
/// .ExecutableQuery("MATCH (p:Person WHERE p.name = $name) RETURN p.born AS born")
67+
/// .WithStreamProcessor(async stream => (await stream.Where(_ => true).FirstAsync())["born"].As&lt;int&gt;())
68+
/// .WithParameters(new Dictionary&lt;string, object&gt; { ["name"] = "Tom Hanks" })
69+
/// .ExecuteAsync();
70+
/// <para></para>
71+
/// Console.WriteLine($"Tom Hanks born {born.Result}");
72+
/// </code>
5973
/// </example>
6074
/// <param name="driver">The driver.</param>
6175
/// <param name="cypher">The cypher of the query.</param>
62-
/// <returns>An <see cref="IExecutableQuery"/> that can be used to configure and execute a query using
63-
/// fluent method chaining.</returns>
64-
public static IExecutableQuery ExecutableQuery(this IDriver driver, string cypher)
76+
/// <returns>
77+
/// An <see cref="IExecutableQuery&lt;IRecord&gt;" /> that can be used to configure and execute a query using
78+
/// fluent method chaining.
79+
/// </returns>
80+
public static IExecutableQuery<IReadOnlyList<IRecord>> ExecutableQuery(this IDriver driver, string cypher)
6581
{
66-
return new ExecutableQuery((IInternalDriver)driver, cypher);
82+
return ExecutableQuery<IReadOnlyList<IRecord>>.GetDefault((IInternalDriver)driver, cypher);
6783
}
68-
84+
6985
/// <summary>
70-
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.<br/>
71-
/// Experimental: This method will be removed and replaced with a readonly property "BookmarkManager" on the
72-
/// <see cref="SessionConfig"/> class.<br/>
73-
/// Gets the configured experimental bookmark manager from this <see cref="SessionConfig"/> instance.
86+
/// There is no guarantee that anything in Neo4j.Driver.Experimental namespace will be in a next minor version.
87+
/// <br /> Experimental: This method will be removed and replaced with a readonly property "BookmarkManager" on the
88+
/// <see cref="SessionConfig" /> class.<br /> Gets the configured experimental bookmark manager from this
89+
/// <see cref="SessionConfig" /> instance.
7490
/// </summary>
75-
/// <seealso cref="WithBookmarkManager"/>
76-
/// <param name="config">This <see cref="SessionConfig"/> instance.</param>
77-
/// <returns>This <see cref="SessionConfig"/>'s configured <see cref="IBookmarkManager"/> instance.</returns>
91+
/// <seealso cref="WithBookmarkManager" />
92+
/// <param name="config">This <see cref="SessionConfig" /> instance.</param>
93+
/// <returns>This <see cref="SessionConfig" />'s configured <see cref="IBookmarkManager" /> instance.</returns>
7894
public static IBookmarkManager GetBookmarkManager(this SessionConfig config)
7995
{
8096
return config.BookmarkManager;

Neo4j.Driver/Neo4j.Driver/Experimental/FluentQueries/ExecutableQuery.cs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,55 +23,77 @@
2323

2424
namespace Neo4j.Driver.Experimental.FluentQueries;
2525

26-
internal class ExecutableQuery : IExecutableQuery
26+
internal class ExecutableQuery<T> : IExecutableQuery<T>
2727
{
2828
private IInternalDriver _driver;
29+
private Func<IAsyncEnumerable<IRecord>, ValueTask<T>> _streamProcessor;
2930
private Query _query;
3031
private QueryConfig _queryConfig;
3132

32-
public ExecutableQuery(IInternalDriver driver, string cypher)
33+
public static ExecutableQuery<IReadOnlyList<IRecord>> GetDefault(IInternalDriver driver, string cypher)
3334
{
35+
return new ExecutableQuery<IReadOnlyList<IRecord>>(new Query(cypher), driver, null, ToListAsync);
36+
}
37+
38+
private ExecutableQuery(
39+
Query query,
40+
IInternalDriver driver,
41+
QueryConfig queryConfig,
42+
Func<IAsyncEnumerable<IRecord>, ValueTask<T>> streamProcessor)
43+
{
44+
_query = query;
3445
_driver = driver;
35-
_query = new Query(cypher, new {});
46+
_queryConfig = queryConfig;
47+
_streamProcessor = streamProcessor;
48+
}
49+
50+
private static async ValueTask<IReadOnlyList<T>> ToListAsync<T>(IAsyncEnumerable<T> enumerable)
51+
{
52+
var result = new List<T>();
53+
await foreach (var item in enumerable)
54+
{
55+
result.Add(item);
56+
}
57+
58+
return result;
3659
}
3760

38-
public IExecutableQuery WithConfig(QueryConfig config)
61+
public IExecutableQuery<T> WithConfig(QueryConfig config)
3962
{
4063
_queryConfig = config;
4164
return this;
4265
}
4366

44-
public IExecutableQuery WithParameters(object parameters)
67+
public IExecutableQuery<T> WithParameters(object parameters)
4568
{
4669
_query = new Query(_query.Text, parameters);
4770
return this;
4871
}
4972

50-
public IExecutableQuery WithParameters(Dictionary<string, object> parameters)
73+
public IExecutableQuery<T> WithParameters(Dictionary<string, object> parameters)
5174
{
5275
_query = new Query(_query.Text, parameters);
5376
return this;
5477
}
5578

79+
public IExecutableQuery<TResult> WithStreamProcessor<TResult>(
80+
Func<IAsyncEnumerable<IRecord>, ValueTask<TResult>> streamProcessor)
81+
{
82+
return new ExecutableQuery<TResult>(_query, _driver, _queryConfig, streamProcessor);
83+
}
84+
5685
// removing since behaviour is different to WithParameters, pending discussion
5786
// public IExecutableQuery WithParameter(string name, object value)
5887
// {
5988
// _query.Parameters[name] = value;
6089
// return this;
6190
// }
6291

63-
public Task<EagerResult> ExecuteAsync(CancellationToken cancellationToken = default)
64-
{
65-
return _driver.ExecuteQueryAsync(_query, _queryConfig, cancellationToken);
66-
}
67-
68-
private Task<IReadOnlyList<T>> TransformRecordsAsync<T>(
69-
Func<IRecord, T> transform,
70-
CancellationToken cancellationToken = default)
92+
public Task<EagerResult<T>> ExecuteAsync(CancellationToken cancellationToken = default)
7193
{
7294
return _driver.ExecuteQueryAsync(
7395
_query,
74-
MapTransformer<T>.GetFactoryMethod(transform),
96+
_streamProcessor,
7597
_queryConfig,
7698
cancellationToken);
7799
}

0 commit comments

Comments
 (0)