Skip to content

Commit c1922c3

Browse files
Fluent API for executing queries (#672)
* add driver query methods * Add some cancellation support * default to writes and add xml comment blocks * set reminder * update comments * use cancellation token when reading results * add null config support * preliminary testkit backend implementation * tidying * add driver query methods * Add some cancellation support * default to writes and add xml comment blocks * set reminder * update comments * use cancellation token when reading results * add null config support * preliminary testkit backend implementation * tidying * enable! * Fluent query for async result working (experimental only) * Remove WithParameter method * Add XML documentation * Fix merge with 5.0 * Update versions to 5.5 * Change IAsyncEnumerator implementation * Remove ``unnecessary `new` keyword. * review notes --------- Co-authored-by: grant lodge <[email protected]>
1 parent 532dc0e commit c1922c3

29 files changed

+1154
-383
lines changed

Neo4j.Driver/Neo4j.Driver.Reactive/Neo4j.Driver.Reactive.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<Configurations>Debug;Release;ReleaseSigned;DebugDelaySigned</Configurations>
2323
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\Neo4j.Driver.Reactive.xml</DocumentationFile>
2424
<RootNamespace>Neo4j.Driver</RootNamespace>
25-
<Version>5.1.0</Version>
25+
<Version>5.5.0</Version>
2626
</PropertyGroup>
2727
<ItemGroup>
2828
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />

Neo4j.Driver/Neo4j.Driver.Simple/Neo4j.Driver.Simple.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<Configurations>Debug;Release;ReleaseSigned;DebugDelaySigned</Configurations>
1919
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\Neo4j.Driver.Simple.xml</DocumentationFile>
2020
<RootNamespace>Neo4j.Driver</RootNamespace>
21-
<Version>5.1.0</Version>
21+
<Version>5.5.0</Version>
2222
</PropertyGroup>
2323
<ItemGroup>
2424
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Neo4j.Driver.Experimental;
6+
using Newtonsoft.Json;
7+
8+
namespace Neo4j.Driver.Tests.TestBackend;
9+
10+
internal class ExecuteQuery : IProtocolObject
11+
{
12+
public ExecuteQueryDto data { get; set; }
13+
[JsonIgnore]
14+
public EagerResult Result { get; set; }
15+
16+
public class ExecuteQueryDto
17+
{
18+
public string driverId { get; set; }
19+
public string cypher { get; set; }
20+
[JsonProperty("params")]
21+
[JsonConverter(typeof(FullQueryParameterConverter))]
22+
public Dictionary<string, object> parameters { get; set; }
23+
public ExecuteQueryConfigDto config { get; set; }
24+
}
25+
26+
public class ExecuteQueryConfigDto
27+
{
28+
public string routing { get; set; }
29+
public string database { get; set; }
30+
public string impersonatedUser { get; set; }
31+
public string bookmarkManagerId { get; set; }
32+
}
33+
34+
public override async Task Process()
35+
{
36+
var driver = ObjManager.GetObject<NewDriver>(data.driverId).Driver;
37+
var queryConfig = BuildConfig();
38+
39+
Result = await driver
40+
.ExecutableQuery(data.cypher)
41+
.WithParameters(data.parameters)
42+
.WithConfig(queryConfig)
43+
.ExecuteAsync();
44+
}
45+
46+
private QueryConfig BuildConfig()
47+
{
48+
if (data.config == null)
49+
return null;
50+
51+
var routingControl = data.config.routing?.Equals("w", StringComparison.OrdinalIgnoreCase) ?? true
52+
? RoutingControl.Writers
53+
: RoutingControl.Readers;
54+
55+
var bookmarkManager = default(IBookmarkManager);
56+
var enableBookmarkManager = true;
57+
58+
if (!string.IsNullOrEmpty(data.config.bookmarkManagerId))
59+
{
60+
if (data.config.bookmarkManagerId == "-1")
61+
enableBookmarkManager = false;
62+
else
63+
bookmarkManager = ObjManager.GetObject<NewBookmarkManager>(data.config.bookmarkManagerId).BookmarkManager;
64+
}
65+
66+
return new QueryConfig(
67+
routingControl,
68+
data.config.database,
69+
data.config.impersonatedUser,
70+
bookmarkManager,
71+
enableBookmarkManager);
72+
}
73+
74+
public override string Respond()
75+
{
76+
var mappedList = Result.Records
77+
.Select(x => new
78+
{
79+
values = x.Values
80+
.Select(y => NativeToCypher.Convert(y.Value))
81+
.ToList()
82+
})
83+
.ToList();
84+
85+
return new ProtocolResponse("EagerResult", new
86+
{
87+
keys = Result.Keys,
88+
records = mappedList,
89+
summary = SummaryJsonSerializer.SerializeToRaw(Result.Summary)
90+
}).Encode();
91+
}
92+
}

Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/JsonConverters/QueryParameterConverter.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,34 @@ public override Dictionary<string, CypherToNativeObject> ReadJson(JsonReader rea
3636
return JsonCypherParameterParser.ParseParameters(token);
3737
}
3838
}
39+
40+
internal class FullQueryParameterConverter : JsonConverter<Dictionary<string, object>>
41+
{
42+
public override void WriteJson(JsonWriter writer, Dictionary<string, object> value, JsonSerializer serializer)
43+
{
44+
throw new NotImplementedException();
45+
}
46+
47+
public override Dictionary<string, object> ReadJson(JsonReader reader, Type objectType, Dictionary<string, object> existingValue, bool hasExistingValue,
48+
JsonSerializer serializer)
49+
{
50+
var token = JObject.Load(reader);
51+
var parameters = JsonCypherParameterParser.ParseParameters(token);
52+
return ConvertParameters(parameters);
53+
}
54+
55+
public static Dictionary<string, object> ConvertParameters(Dictionary<string, CypherToNativeObject> source)
56+
{
57+
if (source == null) return null;
58+
Dictionary<string, object> newParams = new Dictionary<string, object>();
59+
60+
foreach (KeyValuePair<string, CypherToNativeObject> element in source)
61+
{
62+
newParams.Add(element.Key, CypherToNative.Convert(element.Value));
63+
}
64+
65+
return newParams;
66+
}
67+
68+
}
3969
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public static class Protocol
6464
typeof(BookmarkManagerSupplierRequest),
6565
typeof(BookmarksConsumerCompleted),
6666
typeof(BookmarksSupplierCompleted),
67+
typeof(ExecuteQuery)
6768
};
6869

6970

Lines changed: 22 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -1,189 +1,28 @@
1-
using System;
2-
using System.Linq;
3-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
42
using Newtonsoft.Json;
53

6-
namespace Neo4j.Driver.Tests.TestBackend
7-
{
8-
internal class ResultConsume : IProtocolObject
9-
{
10-
public ResultConsumeType data { get; set; } = new ResultConsumeType();
11-
[JsonIgnore]
12-
public IRecord Records { get; set; }
13-
[JsonIgnore]
14-
public IResultSummary Summary { get; set; }
15-
16-
public class ResultConsumeType
17-
{
18-
public string resultId { get; set; }
19-
}
20-
21-
public override async Task Process()
22-
{
23-
Summary = await ((Result)ObjManager.GetObject(data.resultId)).ConsumeResults().ConfigureAwait(false);
24-
}
25-
26-
public override string Respond()
27-
{
28-
return new ProtocolResponse("Summary", new
29-
{
30-
query = GetQuery(Summary),
31-
queryType = GetQueryTypeAsStringCode(Summary),
32-
plan = GetPlan(Summary),
33-
notifications = CreateNotificationList(),
34-
database = Summary.Database?.Name,
35-
serverInfo = GetServerInfo(Summary),
36-
counters = GetCountersFromSummary(Summary),
37-
profile = MapToProfilePlan(Summary.Profile),
38-
resultAvailableAfter = GetTotalMilliseconds(Summary.ResultAvailableAfter),
39-
resultConsumedAfter = GetTotalMilliseconds(Summary.ResultConsumedAfter)
40-
}).Encode();
41-
}
42-
43-
private static long? GetTotalMilliseconds(TimeSpan timespan)
44-
{
45-
return timespan.TotalMilliseconds >= 0L
46-
? (long)timespan.TotalMilliseconds
47-
: default(long?);
48-
}
49-
50-
private static object GetQuery(IResultSummary summary)
51-
{
52-
return summary?.Query == null
53-
? null
54-
: new
55-
{
56-
text = summary.Query.Text,
57-
parameters = summary.Query.Parameters
58-
.Select(x => new { x.Key, Value = NativeToCypher.Convert(x.Value) })
59-
.ToDictionary(x => x.Key, x => x.Value)
60-
};
61-
}
62-
63-
private static string GetQueryTypeAsStringCode(IResultSummary summary)
64-
{
65-
return summary?.QueryType switch
66-
{
67-
QueryType.ReadOnly => "r",
68-
QueryType.ReadWrite => "rw",
69-
QueryType.WriteOnly => "w",
70-
QueryType.SchemaWrite => "s",
71-
QueryType.Unknown => null,
72-
_ => throw new ArgumentOutOfRangeException()
73-
};
74-
}
75-
76-
private static object GetPlan(IResultSummary summary)
77-
{
78-
return summary?.Plan == null
79-
? null
80-
: MapToPlanJson(summary.Plan);
81-
}
4+
namespace Neo4j.Driver.Tests.TestBackend;
825

83-
private static object GetServerInfo(IResultSummary summary)
84-
{
85-
return summary?.Server == null
86-
? null
87-
: new
88-
{
89-
protocolVersion = summary.Server.ProtocolVersion,
90-
agent = summary.Server.Agent
91-
};
92-
}
93-
94-
private static object GetCountersFromSummary(IResultSummary summary)
95-
{
96-
return new
97-
{
98-
constraintsAdded = summary.Counters.ConstraintsAdded,
99-
constraintsRemoved = summary.Counters.ConstraintsRemoved,
100-
nodesCreated = summary.Counters.NodesCreated,
101-
nodesDeleted = summary.Counters.NodesDeleted,
102-
relationshipsCreated = summary.Counters.RelationshipsCreated,
103-
relationshipsDeleted = summary.Counters.RelationshipsDeleted,
104-
propertiesSet = summary.Counters.PropertiesSet,
105-
labelsAdded = summary.Counters.LabelsAdded,
106-
labelsRemoved = summary.Counters.LabelsRemoved,
107-
indexesAdded = summary.Counters.IndexesAdded,
108-
indexesRemoved = summary.Counters.IndexesRemoved,
109-
systemUpdates = summary.Counters.SystemUpdates,
110-
containsUpdates = summary.Counters.ContainsUpdates,
111-
containsSystemUpdates = summary.Counters.ContainsSystemUpdates,
112-
};
113-
}
114-
115-
private static object MapToProfilePlan(IProfiledPlan plan)
116-
{
117-
if (plan == null)
118-
return null;
119-
120-
if (plan.HasPageCacheStats)
121-
return new
122-
{
123-
args = plan.Arguments,
124-
operatorType = plan.OperatorType,
125-
children = plan.Children.Select(MapToProfilePlan).ToList(),
126-
identifiers = plan.Identifiers,
127-
time = plan.Time,
128-
pageCacheHitRatio = plan.PageCacheHitRatio,
129-
pageCacheMisses = plan.PageCacheMisses,
130-
pageCacheHits = plan.PageCacheHits,
131-
rows = plan.Records,
132-
dbHits = plan.DbHits,
133-
};
134-
135-
return new
136-
{
137-
args = plan.Arguments,
138-
operatorType = plan.OperatorType,
139-
children = plan.Children.Select(MapToProfilePlan).ToList(),
140-
identifiers = plan.Identifiers,
141-
rows = plan.Records,
142-
dbHits = plan.DbHits,
143-
};
144-
}
145-
146-
private static object MapToPlanJson(IPlan plan)
147-
{
148-
return new
149-
{
150-
args = plan.Arguments,
151-
operatorType = plan.OperatorType,
152-
children = plan.Children.Select(MapToPlanJson).ToList(),
153-
identifiers = plan.Identifiers
154-
};
155-
}
6+
internal class ResultConsume : IProtocolObject
7+
{
8+
public ResultConsume.ResultConsumeType data { get; set; } = new ResultConsume.ResultConsumeType();
9+
[JsonIgnore]
10+
public IRecord Records { get; set; }
11+
[JsonIgnore]
12+
public IResultSummary Summary { get; set; }
13+
14+
public class ResultConsumeType
15+
{
16+
public string resultId { get; set; }
17+
}
15618

157-
private object CreateNotificationList()
158-
{
159-
if (Summary?.Notifications == null)
160-
return null;
161-
if (Summary?.Notifications?.All(x => x.Position == null) ?? false)
162-
{
163-
return Summary?.Notifications.Select(x => new
164-
{
165-
severity = x.Severity,
166-
description = x.Description,
167-
code = x.Code,
168-
title = x.Title,
169-
}).ToList();
170-
}
19+
public override async Task Process()
20+
{
21+
Summary = await ((Result)ObjManager.GetObject(data.resultId)).ConsumeResults().ConfigureAwait(false);
22+
}
17123

172-
return Summary?.Notifications.Select(x => new
173-
{
174-
severity = x.Severity,
175-
description = x.Description,
176-
code = x.Code,
177-
title = x.Title,
178-
position = x.Position == null
179-
? null
180-
: new
181-
{
182-
column = x.Position.Column,
183-
offset = x.Position.Offset,
184-
line = x.Position.Line
185-
}
186-
}).ToList();
187-
}
24+
public override string Respond()
25+
{
26+
return new ProtocolResponse("Summary", SummaryJsonSerializer.SerializeToRaw(Summary)).Encode();
18827
}
189-
}
28+
}

0 commit comments

Comments
 (0)