diff --git a/Neo4j.Driver/.editorconfig b/Neo4j.Driver/.editorconfig index 41d5d4871..a5fcf58e7 100644 --- a/Neo4j.Driver/.editorconfig +++ b/Neo4j.Driver/.editorconfig @@ -369,11 +369,11 @@ dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_required_modifiers = instance dotnet_separate_import_directive_groups = false @@ -663,8 +663,8 @@ resharper_line_break_before_requires_clause = do_not_change resharper_linkage_specification_braces = end_of_line resharper_linkage_specification_indentation = none resharper_local_function_body = block_body -resharper_macro_block_begin = -resharper_macro_block_end = +resharper_macro_block_begin = +resharper_macro_block_end = resharper_max_array_initializer_elements_on_line = 10000 resharper_max_attribute_length_for_same_line = 38 resharper_max_enum_members_on_line = 1 @@ -734,7 +734,7 @@ resharper_resx_attribute_indent = single_indent resharper_resx_blank_line_after_pi = true resharper_resx_indent_text = OneIndent resharper_resx_keep_user_linebreaks = true -resharper_resx_linebreak_before_elements = +resharper_resx_linebreak_before_elements = resharper_resx_max_blank_lines_between_tags = 0 resharper_resx_pi_attribute_style = do_not_touch resharper_resx_space_before_self_closing = false @@ -963,7 +963,7 @@ resharper_xml_attribute_indent = align_by_first_attribute resharper_xml_blank_line_after_pi = true resharper_xml_indent_text = OneIndent resharper_xml_keep_user_linebreaks = true -resharper_xml_linebreak_before_elements = +resharper_xml_linebreak_before_elements = resharper_xml_max_blank_lines_between_tags = 2 resharper_xml_pi_attribute_style = do_not_touch resharper_xml_space_before_self_closing = true diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutor.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutor.cs index 629fd30ff..90a887af5 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutor.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutor.cs @@ -17,14 +17,10 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Abstractions; -/// -/// Defines methods for executing a workload. -/// +/// Defines methods for executing a workload. public interface IWorkloadExecutor { - /// - /// Execute a workload. - /// + /// Execute a workload. /// The workload to execute. /// A task that completes when the workload has been executed. Task ExecuteWorkloadAsync(Workload workload); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutorSelector.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutorSelector.cs index 5be58a471..4fad7f104 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutorSelector.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadExecutorSelector.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -17,14 +17,10 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Abstractions; -/// -/// Methods for forwarding a workload to the correct executor. -/// +/// Methods for forwarding a workload to the correct executor. public interface IWorkloadExecutorSelector { - /// - /// Gets the correct executor for the workload. - /// + /// Gets the correct executor for the workload. /// The workload to execute. public IWorkloadExecutor GetExecutor(Workload workload); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadStore.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadStore.cs index 75cb10749..479d7c767 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadStore.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Abstractions/IWorkloadStore.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -17,41 +17,29 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Abstractions; -/// -/// Represents a store for managing workloads. -/// +/// Represents a store for managing workloads. public interface IWorkloadStore { - /// - /// Creates a new workload in the store. - /// + /// Creates a new workload in the store. /// The workload to create. /// The ID of the created workload. string CreateWorkload(Workload workload); - /// - /// Retrieves a workload from the store. - /// + /// Retrieves a workload from the store. /// The ID of the workload to retrieve. /// The retrieved workload. Workload GetWorkload(string id); - /// - /// Updates a workload in the store. - /// + /// Updates a workload in the store. /// The ID of the workload to update. /// The updated workload. /// The updated workload. Workload UpdateWorkload(string id, Workload workload); - /// - /// Deletes a workload from the store. - /// + /// Deletes a workload from the store. /// The ID of the workload to delete. void DeleteWorkload(string id); - /// - /// Retrieves the full list of workloads from the store. - /// + /// Retrieves the full list of workloads from the store. IDictionary GetAllWorkloads(); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/ReadyController.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/ReadyController.cs index 4521a58ab..ea2298ac4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/ReadyController.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/ReadyController.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -19,9 +19,7 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Controllers; using ILogger = Microsoft.Extensions.Logging.ILogger; -/// -/// Check if the service is ready. -/// +/// Check if the service is ready. [ApiController] [Route("[controller]")] public class ReadyController( @@ -30,12 +28,10 @@ public class ReadyController( : ControllerBase { // GET - /// - /// Check if the service is ready. - /// + /// Check if the service is ready. /// - /// This endpoint can be used to check if the service is ready to receive requests. This obviously includes - /// the web server, but also whether the backend can successfully connect to the DBMS. + /// This endpoint can be used to check if the service is ready to receive requests. This obviously includes the + /// web server, but also whether the backend can successfully connect to the DBMS. /// [HttpGet] [ProducesResponseType(StatusCodes.Status204NoContent)] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/WorkloadController.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/WorkloadController.cs index 2b1c852aa..82b1eef42 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/WorkloadController.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Controllers/WorkloadController.cs @@ -21,16 +21,14 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Controllers; using ILogger = Microsoft.Extensions.Logging.ILogger; -/// -/// Define and run workloads in the Neo4j driver. -/// +/// Define and run workloads in the Neo4j driver. [ApiController] [Route("[controller]")] public class WorkloadController( - IWorkloadStore workloadStore, - IWorkloadExecutorSelector workloadExecutorSelector, - ILogger logger, - LinkGenerator linkGenerator) + IWorkloadStore workloadStore, + IWorkloadExecutorSelector workloadExecutorSelector, + ILogger logger, + LinkGenerator linkGenerator) : ControllerBase { // GET @@ -95,8 +93,7 @@ public async Task ExecuteEphemeral([FromBody] Workload workload) // DELETE /// Deletes a driver workload. - /// This endpoint deletes the workload from memory. Ongoing executions will not be - /// canceled or stopped. + /// This endpoint deletes the workload from memory. Ongoing executions will not be canceled or stopped. [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteQueryWorkloadExecutor.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteQueryWorkloadExecutor.cs index e966e69a9..91ab6b30a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteQueryWorkloadExecutor.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteQueryWorkloadExecutor.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -21,9 +21,9 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Implementations; using ILogger = Microsoft.Extensions.Logging.ILogger; internal class ExecuteQueryWorkloadExecutor( - IDriver driver, - IRecordConsumer recordConsumer, - ILogger logger) + IDriver driver, + IRecordConsumer recordConsumer, + ILogger logger) : IWorkloadExecutor { public async Task ExecuteWorkloadAsync(Workload workload) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteReadWriteWorkloadExecutor.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteReadWriteWorkloadExecutor.cs index b72bf1da9..ce0a2b805 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteReadWriteWorkloadExecutor.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/ExecuteReadWriteWorkloadExecutor.cs @@ -21,10 +21,10 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Implementations; using ILogger = Microsoft.Extensions.Logging.ILogger; internal class ExecuteReadWriteWorkloadExecutor( - IDriver driver, - IRecordConsumer recordConsumer, - IWorkloadSessionBuilder sessionBuilder, - ILogger logger) + IDriver driver, + IRecordConsumer recordConsumer, + IWorkloadSessionBuilder sessionBuilder, + ILogger logger) : IWorkloadExecutor { public async Task ExecuteWorkloadAsync(Workload workload) @@ -145,7 +145,7 @@ await execute( return 0; }, - _ => { }); + _ => {}); logger.LogDebug("Workload completed"); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/RecordConsumer.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/RecordConsumer.cs index 52ffb1cfe..5d9e711b1 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/RecordConsumer.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/RecordConsumer.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -20,7 +20,7 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Implementations; using ILogger = Microsoft.Extensions.Logging.ILogger; internal class RecordConsumer( - ILogger logger) + ILogger logger) : IRecordConsumer { public void ConsumeRecords(IEnumerable records) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/SessionRunWorkloadExecutor.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/SessionRunWorkloadExecutor.cs index 2c80c893c..23698df91 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/SessionRunWorkloadExecutor.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/SessionRunWorkloadExecutor.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -21,10 +21,10 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Implementations; using ILogger = Microsoft.Extensions.Logging.ILogger; internal class SessionRunWorkloadExecutor( - IDriver driver, - IRecordConsumer recordConsumer, - IWorkloadSessionBuilder sessionBuilder, - ILogger logger) + IDriver driver, + IRecordConsumer recordConsumer, + IWorkloadSessionBuilder sessionBuilder, + ILogger logger) : IWorkloadExecutor { public async Task ExecuteWorkloadAsync(Workload workload) @@ -47,17 +47,18 @@ private async Task ExecuteInParallelSessionsAsync(Workload workload) { var queryToRun = new Query(query.Text, query.Parameters); logger.LogDebug("Starting query {Query} in parallel session", queryToRun.Text); - tasks.Add(Task.Run( - async () => - { - // create a new session in parallel for each query - await using var session = sessionBuilder.BuildSession(driver, workload); - - var resultCursor = await session.RunAsync(queryToRun); - var records = await resultCursor.ToListAsync(); - logger.LogDebug("Received {RecordCount} records", records.Count); - recordConsumer.ConsumeRecords(records); - })); + tasks.Add( + Task.Run( + async () => + { + // create a new session in parallel for each query + await using var session = sessionBuilder.BuildSession(driver, workload); + + var resultCursor = await session.RunAsync(queryToRun); + var records = await resultCursor.ToListAsync(); + logger.LogDebug("Received {RecordCount} records", records.Count); + recordConsumer.ConsumeRecords(records); + })); } logger.LogDebug("Waiting for {TaskCount} parallel tasks to complete", tasks.Count); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadExecutorSelector.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadExecutorSelector.cs index 7e09f4326..408dff224 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadExecutorSelector.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadExecutorSelector.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -18,11 +18,12 @@ using Neo4j.Driver.Tests.BenchkitBackend.Types; namespace Neo4j.Driver.Tests.BenchkitBackend.Implementations; + using ILogger = Microsoft.Extensions.Logging.ILogger; internal class WorkloadExecutorSelector( - IIndex workloadExecutors, - ILogger logger) + IIndex workloadExecutors, + ILogger logger) : IWorkloadExecutorSelector { public IWorkloadExecutor GetExecutor(Workload workload) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadSessionBuilder.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadSessionBuilder.cs index aea2577f3..12a1cc560 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadSessionBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Implementations/WorkloadSessionBuilder.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -20,7 +20,7 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Implementations; internal class WorkloadSessionBuilder : IWorkloadSessionBuilder { - /// + /// public IAsyncSession BuildSession(IDriver driver, Workload workload) { return driver.AsyncSession( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/JsonConverters/ObjectToPrimitiveConverter.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/JsonConverters/ObjectToPrimitiveConverter.cs index 4c456ebf1..e50eb94d7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/JsonConverters/ObjectToPrimitiveConverter.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/JsonConverters/ObjectToPrimitiveConverter.cs @@ -20,8 +20,8 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.InfrastructureExtensions; /// /// The purpose of this class is to ensure that when the destination type is "object" that we don't deserialize a -/// primitive into a JsonElement of some kind, which causes issues when trying to write the value to a PackStream, -/// but instead deserialize it into a primitive type. +/// primitive into a JsonElement of some kind, which causes issues when trying to write the value to a PackStream, but +/// instead deserialize it into a primitive type. /// internal class ObjectToPrimitiveConverter : JsonConverter { @@ -36,9 +36,9 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS { JsonTokenType.True => true, JsonTokenType.False => false, - JsonTokenType.Number when reader.TryGetInt64(out long l) => l, + JsonTokenType.Number when reader.TryGetInt64(out var l) => l, JsonTokenType.Number => reader.GetDouble(), - JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, + JsonTokenType.String when reader.TryGetDateTime(out var datetime) => datetime, JsonTokenType.String => reader.GetString()!, _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() }; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/LogEnrichers/ClassNameEnricher.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/LogEnrichers/ClassNameEnricher.cs index 71eb41277..bad3544e2 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/LogEnrichers/ClassNameEnricher.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/LogEnrichers/ClassNameEnricher.cs @@ -22,7 +22,7 @@ internal class ClassNameEnricher : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { - if (logEvent.Properties.TryGetValue("SourceContext", out LogEventPropertyValue? value) && + if (logEvent.Properties.TryGetValue("SourceContext", out var value) && value is ScalarValue { Value: string sourceContext }) { var className = sourceContext.Split('.').Last(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/WebExtensions/ConfigurationBuilderExtensions.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/WebExtensions/ConfigurationBuilderExtensions.cs index 127cb22d8..78c0b7650 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/WebExtensions/ConfigurationBuilderExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/InfrastructureExtensions/WebExtensions/ConfigurationBuilderExtensions.cs @@ -17,9 +17,7 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.InfrastructureExtensions; internal static class ConfigurationBuilderExtensions { - /// - /// Override the configuration with values from environment variables. - /// + /// Override the configuration with values from environment variables. /// The configuration builder. /// The configuration builder for method chaining. public static IConfigurationBuilder OverrideFromBenchkitEnvironmentVars(this IConfigurationBuilder builder) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Neo4j.Driver.Tests.BenchkitBackend.csproj b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Neo4j.Driver.Tests.BenchkitBackend.csproj index 5af42ed5e..13e2b8142 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Neo4j.Driver.Tests.BenchkitBackend.csproj +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Neo4j.Driver.Tests.BenchkitBackend.csproj @@ -8,14 +8,14 @@ - 1701;1702;1591;CS1591 + 1701;1702;1591;CS1591 - - + + - + @@ -23,14 +23,13 @@ - + - + - diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Program.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Program.cs index 621873784..5df5ce130 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Program.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Program.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -40,11 +40,12 @@ // wire up autofac builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureContainer(b => - { - b.RegisterInstance(benchkitBackendConfiguration).SingleInstance(); - b.RegisterModule(); - }); + .ConfigureContainer( + b => + { + b.RegisterInstance(benchkitBackendConfiguration).SingleInstance(); + b.RegisterModule(); + }); builder.Services .AddNeo4jDriver(benchkitBackendConfiguration) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Properties/launchSettings.json b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Properties/launchSettings.json index 9096d050a..9c132bb94 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Properties/launchSettings.json +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Properties/launchSettings.json @@ -1,14 +1,14 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "kestrel": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:9000/swagger/index.html", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "kestrel": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:9000/swagger/index.html", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } } - } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Method.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Method.cs index c3562f1ac..800232aeb 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Method.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Method.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -15,28 +15,18 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Types; -/// -/// Different methods in the driver that can be used to execute a query. -/// +/// Different methods in the driver that can be used to execute a query. public enum Method { - /// - /// Use the driver-level ExecuteQuery method. - /// + /// Use the driver-level ExecuteQuery method. ExecuteQuery, - /// - /// Use Session.Run to execute the query. - /// + /// Use Session.Run to execute the query. SessionRun, - /// - /// Use Session.ExecuteRead to execute the query. - /// + /// Use Session.ExecuteRead to execute the query. ExecuteRead, - /// - /// Use Session.ExecuteWrite to execute the query. - /// + /// Use Session.ExecuteWrite to execute the query. ExecuteWrite } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Mode.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Mode.cs index 9ef2eb005..348d23e03 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Mode.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Mode.cs @@ -15,28 +15,18 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Types; -/// -/// Defines the parallel/sequential mode in which the queries should be executed. -/// +/// Defines the parallel/sequential mode in which the queries should be executed. public enum Mode { - /// - /// Execute the queries sequentially in a single transaction. - /// + /// Execute the queries sequentially in a single transaction. SequentialQueries, - /// - /// Execute each query in a separate transaction sequentially in the same session. - /// + /// Execute each query in a separate transaction sequentially in the same session. SequentialTransactions, - /// - /// Execute each query in a separate session sequentially. - /// + /// Execute each query in a separate session sequentially. SequentialSessions, - /// - /// Execute the queries in parallel in a session per query. - /// + /// Execute the queries in parallel in a session per query. ParallelSessions } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Routing.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Routing.cs index eeb17cefe..0769e4bf4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Routing.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Routing.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -15,19 +15,13 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Types; -/// -/// Defines the routing mode in which the queries should be executed. -/// +/// Defines the routing mode in which the queries should be executed. public enum Routing { - /// - /// Write routing. - /// + /// Write routing. Write, - /// - /// Read routing. - /// + /// Read routing. Read } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Workload.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Workload.cs index c73f0295f..8e75d8ac8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Workload.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/Workload.cs @@ -15,33 +15,21 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Types; -/// -/// Describes a driver workload. -/// +/// Describes a driver workload. public class Workload { - /// - /// The method to use for executing the workload. - /// + /// The method to use for executing the workload. public Method Method { get; set; } - /// - /// The database to use for the workload. - /// + /// The database to use for the workload. public string? Database { get; set; } = ""; - /// - /// The routing method to use for the workload. - /// + /// The routing method to use for the workload. public Routing Routing { get; set; } = Routing.Write; - /// - /// The series/parallel mode to use for the workload. - /// + /// The series/parallel mode to use for the workload. public Mode Mode { get; set; } = Mode.SequentialSessions; - /// - /// A list of individual queries to execute as part of the workload. - /// + /// A list of individual queries to execute as part of the workload. public List Queries { get; set; } = new(); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/WorkloadQuery.cs b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/WorkloadQuery.cs index 7ee776746..63f577157 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/WorkloadQuery.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/Types/WorkloadQuery.cs @@ -15,18 +15,12 @@ namespace Neo4j.Driver.Tests.BenchkitBackend.Types; -/// -/// A query that is part of a workload. -/// +/// A query that is part of a workload. public class WorkloadQuery { - /// - /// The query text. - /// + /// The query text. public string Text { get; set; } = ""; - /// - /// The parameters to use when executing the query. - /// + /// The parameters to use when executing the query. public Dictionary Parameters { get; set; } = new(); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/appsettings.json b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/appsettings.json index d7aa689e7..1a1a21bc0 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/appsettings.json +++ b/Neo4j.Driver/Neo4j.Driver.Tests.BenchkitBackend/appsettings.json @@ -1,16 +1,16 @@ { - "Serilog": { - "MinimumLevel": { - "Default": "Debug" + "Serilog": { + "MinimumLevel": { + "Default": "Debug" + } + }, + "AllowedHosts": "*", + "BenchkitBackend": { + "BackendPort": 9000, + "Neo4jScheme": "neo4j", + "Neo4jHost": "localhost", + "Neo4jPort": 7687, + "Neo4jUser": "neo4j", + "Neo4jPassword": "password" } - }, - "AllowedHosts": "*", - "BenchkitBackend": { - "BackendPort": 9000, - "Neo4jScheme": "neo4j", - "Neo4jHost": "localhost", - "Neo4jPort": 7687, - "Neo4jUser": "neo4j", - "Neo4jPassword": "password" - } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/CertificateTrustIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/CertificateTrustIT.cs index 414fede8e..456846707 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/CertificateTrustIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/CertificateTrustIT.cs @@ -164,14 +164,16 @@ private async Task TestConnectivity(Uri target, Config config) private IDriver SetupWithCustomResolver(Uri overridenUri, Config config) { - var resolver = new CustomHostResolver(Server.BoltUri, + var resolver = new CustomHostResolver( + Server.BoltUri, new SystemNetCoreHostResolver(new SystemHostResolver())); + var driverContext = new DriverContext( overridenUri, AuthTokenManagers.Static(Server.AuthToken), config, resolver); - + var connectionFactory = new PooledConnectionFactory(driverContext); return GraphDatabase.CreateDriver(connectionFactory, driverContext); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/DirectDriverTestBase.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/DirectDriverTestBase.cs index a4ef80738..08d16b14d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/DirectDriverTestBase.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/DirectDriverTestBase.cs @@ -43,6 +43,7 @@ public void Dispose() switch (_disposed) { case true: return; + case false: { using var session = Server.Driver.Session(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/EncryptionIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/EncryptionIT.cs index e508f58ff..3e0b3d770 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/EncryptionIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Direct/EncryptionIT.cs @@ -53,9 +53,9 @@ private static async Task VerifyConnectivity(IDriver driver) { await using var session = driver.AsyncSession(); - var cursor = await session.RunAsync("RETURN 2 as Number"); - var records = await cursor.ToListAsync(r => r["Number"].As()); + var cursor = await session.RunAsync("RETURN 2 as Number"); + var records = await cursor.ToListAsync(r => r["Number"].As()); - records.Should().BeEquivalentTo(2); + records.Should().BeEquivalentTo(2); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Examples.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Examples.cs index 2f7d17536..9f2caa2a8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Examples.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Examples.cs @@ -474,6 +474,11 @@ public HelloWorldExample(string uri, string user, string password) _driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password)); } + public void Dispose() + { + _driver?.Dispose(); + } + public void PrintGreeting(string message) { using var session = _driver.Session(); @@ -492,11 +497,6 @@ public void PrintGreeting(string message) Console.WriteLine(greeting); } - public void Dispose() - { - _driver?.Dispose(); - } - public static void Main() { using var greeter = new HelloWorldExample("bolt://localhost:7687", "neo4j", "password"); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/ExamplesAsync.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/ExamplesAsync.cs index 78d74f4b4..b4e244743 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/ExamplesAsync.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/ExamplesAsync.cs @@ -538,6 +538,7 @@ protected virtual void Dispose(bool disposing) } } } + public class HelloWorldExampleTest : BaseAsyncExample { public HelloWorldExampleTest(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) @@ -553,7 +554,7 @@ public async Task TestHelloWorldExample() // When & Then await example.PrintGreetingAsync("Hello, world"); } - + // tag::async-hello-world[] public class HelloWorldExample : IDisposable { @@ -564,6 +565,11 @@ public HelloWorldExample(string uri, string user, string password) _driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password)); } + public void Dispose() + { + _driver?.Dispose(); + } + public async Task PrintGreetingAsync(string message) { await using var session = _driver.AsyncSession(); @@ -582,11 +588,6 @@ public async Task PrintGreetingAsync(string message) Console.WriteLine(greeting); } - - public void Dispose() - { - _driver?.Dispose(); - } } // end::async-hello-world[] } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CausalClusterIntegrationTestFixture.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CausalClusterIntegrationTestFixture.cs index 511d057a7..b7896e18b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CausalClusterIntegrationTestFixture.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CausalClusterIntegrationTestFixture.cs @@ -54,6 +54,7 @@ public void Dispose() switch (_disposed) { case true: return; + case false: Cluster?.Dispose(); break; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CertificateUtils.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CertificateUtils.cs index 9c5b2cf23..5f3dca2d1 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CertificateUtils.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/CertificateUtils.cs @@ -69,12 +69,10 @@ public static Pkcs12Store CreateCert( if (altNames.Any() || addressAltNames.Any()) { var alternativeNames = new List(); - - alternativeNames.AddRange( - altNames.Select(name => new GeneralName(GeneralName.DnsName, name))); - alternativeNames.AddRange( - addressAltNames.Select(ip => new GeneralName(GeneralName.IPAddress, ip))); + alternativeNames.AddRange(altNames.Select(name => new GeneralName(GeneralName.DnsName, name))); + + alternativeNames.AddRange(addressAltNames.Select(ip => new GeneralName(GeneralName.IPAddress, ip))); certGenerator.AddExtension( X509Extensions.SubjectAlternativeName, diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/SingleInstance.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/SingleInstance.cs index 5173935fb..349fe81a0 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/SingleInstance.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/Cluster/SingleInstance.cs @@ -28,7 +28,7 @@ public SingleInstance(string httpUri, string boltUri, string homePath, string pa HttpUri = new Uri(httpUri); BoltUri = new Uri(boltUri); BoltRoutingUri = new Uri($"{BoltRoutingScheme}{BoltUri.Host}:{BoltUri.Port}"); - HomePath = homePath == null + HomePath = homePath == null ? "UNKNOWN" : new DirectoryInfo(homePath).FullName; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/IntegrationTestAttribute.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/IntegrationTestAttribute.cs index f768675a1..907c1040b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/IntegrationTestAttribute.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/IntegrationTestAttribute.cs @@ -231,7 +231,9 @@ public RequireServerFactAttribute( VersionComparison versionComparison) { if (versionComparison != VersionComparison.Between) + { throw new ArgumentException(nameof(versionComparison)); + } _versionComparison = versionComparison; var skipText = new StringBuilder(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/ProcessBasedCommandRunner.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/ProcessBasedCommandRunner.cs index 186684645..1e6727f84 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/ProcessBasedCommandRunner.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/ProcessBasedCommandRunner.cs @@ -30,7 +30,7 @@ public sealed class ProcessBasedCommandRunner : ShellCommandRunner, IDisposable private List _stdOut; public void Dispose() - { + { _process?.Dispose(); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/DefaultInstallation.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/DefaultInstallation.cs index 503e23e80..2519b5d56 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/DefaultInstallation.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/DefaultInstallation.cs @@ -21,11 +21,11 @@ namespace Neo4j.Driver.IntegrationTests.Internals; public static class DefaultInstallation { - public static readonly string User = "neo4j"; - public static readonly string Password = "neo4j"; public const string HttpUri = "http://127.0.0.1:7474"; public const string BoltHeader = "bolt://"; + public static readonly string User = "neo4j"; + public static readonly string Password = "neo4j"; public static readonly string BoltHost = "127.0.0.1"; public static readonly string BoltPort = "7687"; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/SettingsHelper.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/SettingsHelper.cs index f1fbdb645..c036757c3 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/SettingsHelper.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Internals/StandAlone/SettingsHelper.cs @@ -39,7 +39,7 @@ public static void UpdateSettings(string location, IDictionary k using var writer = new StreamWriter(new FileStream(configFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite)); - while (reader.ReadLine() is { } line) + while (reader.ReadLine() is {} line) { if (line.Trim() == string.Empty || line.Trim().StartsWith("#")) { 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 82a41340e..932c262f1 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 @@ -1,5 +1,5 @@  - + net6.0 false @@ -18,38 +18,38 @@ Neo4j.Driver.IntegrationTests - - - - - - - - - - - + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - - - - - - - + + + + + + + - + @@ -151,7 +151,7 @@ Always - + Always @@ -172,9 +172,9 @@ - + - + diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/AbstractRxIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/AbstractRxIT.cs index c9ff35978..1de00b774 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/AbstractRxIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/AbstractRxIT.cs @@ -28,7 +28,6 @@ namespace Neo4j.Driver.IntegrationTests.Reactive; public abstract class AbstractRxIT : AbstractRxTest, IDisposable { private readonly List _sessions = new(); - protected bool IsDispose { get; private set; } protected AbstractRxIT(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) : base(output) @@ -36,6 +35,8 @@ protected AbstractRxIT(ITestOutputHelper output, StandAloneIntegrationTestFixtur Server = fixture.StandAloneSharedInstance; } + protected bool IsDispose { get; private set; } + protected IStandAlone Server { get; } public virtual void Dispose() @@ -43,6 +44,7 @@ public virtual void Dispose() switch (IsDispose) { case true: return; + case false: _sessions.ForEach(x => x.Close().WaitForCompletion()); _sessions.Clear(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/NavigationIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/NavigationIT.cs index a2d5cfccb..c24aa5992 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/NavigationIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/NavigationIT.cs @@ -431,6 +431,7 @@ public override void Dispose() _rxTransaction.Commit().WaitForCompletion(); _rxSession.Close().WaitForCompletion(); } + base.Dispose(); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/SummaryIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/SummaryIT.cs index 0b3a0f28f..6e2300c62 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/SummaryIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Reactive/SummaryIT.cs @@ -221,7 +221,7 @@ public void ShouldNotReturnPlanAndProfile() HasPlan = false, Plan = default(IPlan), HasProfile = false, Profile = default(IProfiledPlan) })); } - + [RequireServerFact("4.0.0", GreaterThanOrEqualTo, Skip = "Broken with servers 5.6+")] public void ShouldReturnPlanButNoProfile() { @@ -276,7 +276,6 @@ public void ShouldReturnNotifications() .Excluding(x => x.SelectedMemberPath == "Notifications[0].Description"))); } - [RequireServerFact("5.7.0", GreaterThanOrEqualTo)] public void ShouldReturnNotificationsWithCategory() { @@ -286,7 +285,7 @@ public void ShouldReturnNotificationsWithCategory() MatchesSummary( new { - Notifications = new [] + Notifications = new[] { new Notification( "Neo.ClientNotification.Statement.UnknownLabelWarning", diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingReadCommandTxFunc.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingReadCommandTxFunc.cs index b8a838feb..38ebd9ad7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingReadCommandTxFunc.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingReadCommandTxFunc.cs @@ -18,7 +18,7 @@ namespace Neo4j.Driver.IntegrationTests.Stress; -public sealed class BlockingReadCommandTxFunc: BlockingCommand +public sealed class BlockingReadCommandTxFunc : BlockingCommand { public BlockingReadCommandTxFunc(IDriver driver, bool useBookmark) : base(driver, useBookmark) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingWriteCommandUsingReadSessionTxFunc.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingWriteCommandUsingReadSessionTxFunc.cs index 26adf20fb..a39279538 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingWriteCommandUsingReadSessionTxFunc.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Blocking/BlockingWriteCommandUsingReadSessionTxFunc.cs @@ -28,7 +28,7 @@ public BlockingWriteCommandUsingReadSessionTxFunc(IDriver driver, bool useBookma public override void Execute(StressTestContext context) { using var session = NewSession(AccessMode.Read, context); - + try { var succeeded = session.ExecuteRead( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/CausalClusterStressTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/CausalClusterStressTests.cs index 899818273..564292b87 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/CausalClusterStressTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/CausalClusterStressTests.cs @@ -86,6 +86,7 @@ protected override void VerifyReadQueryDistribution(StressTestContext context) { throw new Exception("Context of wrong type"); } + VerifyServedReadQueries(clusterContext, clusterAddresses); VerifyServedSimilarAmountOfReadQueries(clusterContext, clusterAddresses); } @@ -103,6 +104,7 @@ public override bool HandleWriteFailure(Exception error, StressTestContext conte { throw new Exception("Context of wrong type"); } + clusterContext.LeaderSwitched(); return true; } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/IRxCommand.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/IRxCommand.cs index cc0a82e4c..2a82285b1 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/IRxCommand.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/IRxCommand.cs @@ -15,6 +15,6 @@ namespace Neo4j.Driver.IntegrationTests.Stress; -public interface IRxCommand: IAsyncCommand +public interface IRxCommand : IAsyncCommand { } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Reactive/RxFailingCommandInTx.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Reactive/RxFailingCommandInTx.cs index 7b5befa41..0f5871ecd 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Reactive/RxFailingCommandInTx.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/Reactive/RxFailingCommandInTx.cs @@ -22,7 +22,7 @@ namespace Neo4j.Driver.IntegrationTests.Stress; -public sealed class RxFailingCommandInTx: RxCommand +public sealed class RxFailingCommandInTx : RxCommand { public RxFailingCommandInTx(IDriver driver) : base(driver, false) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/StressTest.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/StressTest.cs index 8856f110e..996170fdb 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/StressTest.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stress/StressTest.cs @@ -29,18 +29,8 @@ namespace Neo4j.Driver.IntegrationTests.Stress; -public abstract class StressTest: IDisposable +public abstract class StressTest : IDisposable { - private enum StressTestMinLogLevel - { - Trace, - Debug, - Info, - Warn, - Error, - None - } - private const int DefaultExecutionTime = 30; private const int StressTestThreadCount = 8; @@ -109,6 +99,16 @@ protected StressTest( Dispose(false); } + private enum StressTestMinLogLevel + { + Trace, + Debug, + Info, + Warn, + Error, + None + } + private class StressTestLogger : ILogger { private readonly StressTestMinLogLevel _minLevel; @@ -272,7 +272,7 @@ public async Task Async() private IList CreateAsyncCommands() { - /* + /* Optional tests that can be run. Currenlty only want to run the transaction functions as these are what are used with Aura AsyncReadCommand AsyncReadCommandInTx @@ -407,7 +407,7 @@ await session.ExecuteWriteAsync( async tx => { // 1-500, 501-1000 - var batches = Enumerable.Range(batchIndex * batchSize + 1, batchSize).Batch(queryBatchSize); + var batches = Enumerable.Range(batchIndex * batchSize + 1, batchSize).Batch(queryBatchSize); foreach (var batch in batches) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/RoutingDriverTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/RoutingDriverTests.cs index e3c52c3c8..ecd9223ed 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/RoutingDriverTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/RoutingDriverTests.cs @@ -40,12 +40,12 @@ public async Task SendRoutingContextToServer(string boltVersion) var uri = new Uri("neo4j://127.0.0.1:9001/?policy=my_policy®ion=china"); await using var driver = GraphDatabase.Driver(uri, SetupConfig); await using var session = driver.AsyncSession(); - var cursor = await session.RunAsync("MATCH (n) RETURN n.name AS name"); - var records = await cursor.ToListAsync(); + var cursor = await session.RunAsync("MATCH (n) RETURN n.name AS name"); + var records = await cursor.ToListAsync(); - records.Count.Should().Be(2); - records[0]["name"].As().Should().Be("Alice"); - records[1]["name"].As().Should().Be("Bob"); + records.Count.Should().Be(2); + records[0]["name"].As().Should().Be("Alice"); + records[1]["name"].As().Should().Be("Bob"); } [RequireBoltStubServerTheory] @@ -57,13 +57,13 @@ public async Task InvokeProcedureGetRoutingTableWhenServerVersionPermits(string var uri = new Uri("neo4j://127.0.0.1:9001"); await using var driver = GraphDatabase.Driver(uri, SetupConfig); await using var session = driver.AsyncSession(); - var cursor = await session.RunAsync("MATCH (n) RETURN n.name AS name"); - var records = await cursor.ToListAsync(); + var cursor = await session.RunAsync("MATCH (n) RETURN n.name AS name"); + var records = await cursor.ToListAsync(); - records.Count.Should().Be(3); - records[0]["name"].As().Should().Be("Alice"); - records[1]["name"].As().Should().Be("Bob"); - records[2]["name"].As().Should().Be("Eve"); + records.Count.Should().Be(3); + records[0]["name"].As().Should().Be("Alice"); + records[1]["name"].As().Should().Be("Bob"); + records[2]["name"].As().Should().Be("Eve"); } [RequireBoltStubServerTheory] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/TransactionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/TransactionTests.cs index c7c64a7c7..f51d80ba8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/TransactionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Stub/TransactionTests.cs @@ -31,7 +31,7 @@ public static bool HasCause(this Exception exception) { T => true, AggregateException aggregate => aggregate.InnerExceptions.Any(x => x.HasCause()), - var _ => exception.InnerException?.HasCause() ?? false + _ => exception.InnerException?.HasCause() ?? false }; } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Types/PointsIT.cs b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Types/PointsIT.cs index f2c049ab0..1b5bd53c5 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Types/PointsIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.Integration/Types/PointsIT.cs @@ -169,7 +169,7 @@ private Point GenerateRandomPoint(int sequence) 1 => new Point(Wgs843DSrId, GenerateRandomX(), GenerateRandomY(), GenerateRandomZ()), 2 => new Point(CartesianSrId, GenerateRandomX(), GenerateRandomY()), 3 => new Point(Cartesian3DSrId, GenerateRandomX(), GenerateRandomY(), GenerateRandomZ()), - var _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException() }; } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Exceptions/ExceptionManager.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Exceptions/ExceptionManager.cs index 3a66d3b57..1f33fa97a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Exceptions/ExceptionManager.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Exceptions/ExceptionManager.cs @@ -14,9 +14,9 @@ // limitations under the License. using System; -using System.Linq; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; using Neo4j.Driver.Preview.GqlErrors; @@ -130,7 +130,9 @@ private static Dictionary CreateExceptionDictionary( { var ne = ex as Neo4jException; var gqlError = ne?.GetGqlErrorPreview(); - var diagnosticRecord = gqlError?.GqlDiagnosticRecord?.ToDictionary(y => y.Key, y => NativeToCypher.Convert(y.Value)); + var diagnosticRecord = + gqlError?.GqlDiagnosticRecord?.ToDictionary(y => y.Key, y => NativeToCypher.Convert(y.Value)); + var data = new Dictionary(); if (!isCause) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/IO/RequestReader.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/IO/RequestReader.cs index 3e82f19f7..b6a0326a9 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/IO/RequestReader.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/IO/RequestReader.cs @@ -46,7 +46,7 @@ public async Task ParseNextRequest() { } - Trace.WriteLine($"\nRequest received: {CurrentObjectData}"); + Trace.WriteLine($"Request received: {CurrentObjectData}"); return !string.IsNullOrEmpty(CurrentObjectData); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Neo4j.Driver.Tests.TestBackend.csproj b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Neo4j.Driver.Tests.TestBackend.csproj index b1364bb0f..b1a9af383 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Neo4j.Driver.Tests.TestBackend.csproj +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Neo4j.Driver.Tests.TestBackend.csproj @@ -1,5 +1,5 @@  - + false Exe @@ -10,12 +10,12 @@ - - + + - + diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Program.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Program.cs index 70480423e..6fc665be6 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Program.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Program.cs @@ -18,6 +18,7 @@ using System.IO; using System.Net; using Neo4j.Driver.Tests.TestBackend.IO; +using Neo4j.Driver.Tests.TestBackend.Protocol.UI; namespace Neo4j.Driver.Tests.TestBackend; @@ -28,7 +29,7 @@ public class Program private static void Main(string[] args) { - var consoleTraceListener = new TextWriterTraceListener(Console.Out); + var consoleTraceListener = new TextWriterTraceListener(new ConsoleTextWriter()); Trace.Listeners.Add(consoleTraceListener); try diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerGetAuthCompleted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerGetAuthCompleted.cs index d65b84fca..4441eb710 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerGetAuthCompleted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerGetAuthCompleted.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerHandleSecurityExceptionCompleted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerHandleSecurityExceptionCompleted.cs index 1f6fdcd58..d6b26b4fa 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerHandleSecurityExceptionCompleted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthTokenManagerHandleSecurityExceptionCompleted.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthorizationToken.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthorizationToken.cs index dbe9a6f03..eb5cacbcf 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthorizationToken.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/AuthorizationToken.cs @@ -21,15 +21,6 @@ internal class AuthorizationToken : ProtocolObject { public AuthorizationTokenType data { get; set; } = new(); - public class AuthorizationTokenType - { - public string scheme { get; set; } - public string principal { get; set; } - public string credentials { get; set; } - public string realm { get; set; } - public Dictionary parameters { get; set; } - } - public IAuthToken AsToken() { var authTokenData = data; @@ -45,4 +36,13 @@ public IAuthToken AsToken() authTokenData.parameters) }; } + + public class AuthorizationTokenType + { + public string scheme { get; set; } + public string principal { get; set; } + public string credentials { get; set; } + public string realm { get; set; } + public Dictionary parameters { get; set; } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BasicAuthTokenProviderCompleted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BasicAuthTokenProviderCompleted.cs index 6ee139904..f774d7dd3 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BasicAuthTokenProviderCompleted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BasicAuthTokenProviderCompleted.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BearerAuthTokenProviderCompleted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BearerAuthTokenProviderCompleted.cs index f9b778c5f..3690778ee 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BearerAuthTokenProviderCompleted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/BearerAuthTokenProviderCompleted.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/CheckSessionAuthSupport.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/CheckSessionAuthSupport.cs index b8bb9b52e..58c1e702d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/CheckSessionAuthSupport.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/CheckSessionAuthSupport.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/ClientCertificate.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/ClientCertificate.cs index 092d1ee6a..2966845ae 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/ClientCertificate.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/ClientCertificate.cs @@ -18,26 +18,26 @@ using System.Security.Cryptography.X509Certificates; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Security; using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; using X509Certificate = Org.BouncyCastle.X509.X509Certificate; namespace Neo4j.Driver.Tests.TestBackend.Protocol.Auth; internal class ClientCertificate : ProtocolObject { - public ClientCertificateType data { get; set; } = new(); - - private Lazy _certificate; - public X509Certificate2 Certificate => _certificate.Value; + private readonly Lazy _certificate; - /// + /// public ClientCertificate() { _certificate = new Lazy( () => ClientCertificateLoader.GetCertificate(data.certfile, data.keyfile, data.password)); } + public ClientCertificateType data { get; set; } = new(); + public X509Certificate2 Certificate => _certificate.Value; + public class ClientCertificateType { public string certfile { get; set; } = ""; @@ -79,6 +79,9 @@ public PasswordProvider(string password) _password = password; } - public char[] GetPassword() => _password.ToCharArray(); + public char[] GetPassword() + { + return _password.ToCharArray(); + } } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewAuthTokenManager.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewAuthTokenManager.cs index a999e3f4c..099328ee0 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewAuthTokenManager.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewAuthTokenManager.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBasicAuthTokenManager.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBasicAuthTokenManager.cs index 2b363ed47..f414db873 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBasicAuthTokenManager.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBasicAuthTokenManager.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -43,7 +41,7 @@ internal class NewNeo4jAuthTokenManager : ProtocolObject internal class NewBasicAuthTokenManager : NewNeo4jAuthTokenManager { public object data { get; set; } - + public override Task Process(Controller controller) { _controller = controller; @@ -71,7 +69,7 @@ public override string Respond() { return new ProtocolResponse("BasicAuthTokenManager", uniqueId).Encode(); } - + protected string GetAuthRequest(string requestId) { return new ProtocolResponse( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBearerAuthTokenManager.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBearerAuthTokenManager.cs index 5c460c8b7..e9ff373b4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBearerAuthTokenManager.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewBearerAuthTokenManager.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -26,7 +24,7 @@ namespace Neo4j.Driver.Tests.TestBackend.Protocol.Auth; internal class NewBearerAuthTokenManager : NewNeo4jAuthTokenManager { public object data { get; set; } - + public override Task Process(Controller controller) { _controller = controller; @@ -59,7 +57,7 @@ public override string Respond() { return new ProtocolResponse("BearerAuthTokenManager", uniqueId).Encode(); } - + protected string GetAuthRequest(string requestId) { return new ProtocolResponse( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewClientCertificateProvider.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewClientCertificateProvider.cs index bd949eee6..b2cd0c3c2 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewClientCertificateProvider.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/NewClientCertificateProvider.cs @@ -23,14 +23,8 @@ internal class NewClientCertificateProvider : ProtocolObject, IClientCertificate { private Controller _controller; public object data { get; set; } = new(); - - public override Task Process(Controller controller) - { - _controller = controller; - return Task.CompletedTask; - } - /// + /// public async ValueTask GetCertificateAsync() { var requestId = Guid.NewGuid().ToString(); @@ -38,6 +32,7 @@ public async ValueTask GetCertificateAsync() "ClientCertificateProviderRequest", new { clientCertificateProviderId = uniqueId, id = requestId }) .Encode(); + await _controller.SendResponse(request).ConfigureAwait(false); var result = await _controller.TryConsumeStreamObjectOfType() .ConfigureAwait(false); @@ -53,6 +48,12 @@ public async ValueTask GetCertificateAsync() throw new Exception("GetCertificateAsync: request IDs did not match"); } + public override Task Process(Controller controller) + { + _controller = controller; + return Task.CompletedTask; + } + public override string Respond() { return new ProtocolResponse("ClientCertificateProvider", uniqueId).Encode(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/TemporalAuthTokenProviderCompleted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/TemporalAuthTokenProviderCompleted.cs index dc8daabc0..e5c9eb063 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/TemporalAuthTokenProviderCompleted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/TemporalAuthTokenProviderCompleted.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/VerifyAuthentication.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/VerifyAuthentication.cs index 9b5319291..60063e4a7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/VerifyAuthentication.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Auth/VerifyAuthentication.cs @@ -22,6 +22,7 @@ namespace Neo4j.Driver.Tests.TestBackend.Protocol.Auth; internal class VerifyAuthentication : ProtocolObject { public VerifyAuthenticationDTO data { get; set; } = null!; + [JsonIgnore] public bool Authenticated { get; set; } 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 b284eed13..96f7877fe 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/BookmarkManager/NewBookmarkManager.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/BookmarkManager/NewBookmarkManager.cs @@ -24,7 +24,8 @@ internal class NewBookmarkManager : ProtocolObject { public NewBookmarkManagerDto data { get; set; } = new(); - [JsonIgnore] public IBookmarkManager BookmarkManager { get; set; } + [JsonIgnore] + public IBookmarkManager BookmarkManager { get; set; } public override Task Process(Controller controller) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/GetConnectionPoolMetrics.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/GetConnectionPoolMetrics.cs index 0c7002182..511e190e2 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/GetConnectionPoolMetrics.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/GetConnectionPoolMetrics.cs @@ -28,7 +28,7 @@ public override string Respond() { throw new Exception("The driver is not an internal driver"); } - + var metrics = driver.Context.Metrics.ConnectionPoolMetrics; var address = metrics.Where(x => x.Value.Id.Contains(data.address, StringComparison.OrdinalIgnoreCase)) .Select(x => x.Value) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriver.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriver.cs index 0fee2eef0..8a3daff8b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriver.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriver.cs @@ -170,7 +170,7 @@ List GetPaths() ClientCertificateProviders.Static(data.clientCertificate.Certificate)); } - if(data.clientCertificateProviderId != null) + if (data.clientCertificateProviderId != null) { var provider = (NewClientCertificateProvider)ObjManager.GetObject(data.clientCertificateProviderId); configBuilder.WithClientCertificateProvider(provider); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriverConverter.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriverConverter.cs index 2617c99cf..50d81b6c4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriverConverter.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/NewDriverConverter.cs @@ -65,7 +65,9 @@ public override NewDriver.NewDriverType ReadJson( } if (jsonObj.TryGetValue("notificationsDisabledCategories", out token)) + { newDriverRequest.notificationsDisabledCategories = token.ToObject(); + } return newDriverRequest; } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/SimpleLogger.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/SimpleLogger.cs index 635e63bb7..20311ea92 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/SimpleLogger.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Driver/SimpleLogger.cs @@ -19,21 +19,31 @@ namespace Neo4j.Driver.Tests.TestBackend.Protocol.Driver; internal class SimpleLogger : ILogger { - private string Now => DateTime.UtcNow.ToString("O"); + private string Now => DateTime.UtcNow.ToString("HH:mm:ss"); public void Debug(string message, params object[] args) { - Console.WriteLine($"[DRIVER-DEBUG][{Now}]{message}", args); + + Console.ForegroundColor = message[0] == '[' + ? ConsoleColor.DarkMagenta + : ConsoleColor.DarkGreen; + + Console.WriteLine($"{Now} DBG: {message}", args); + Console.ResetColor(); } public void Error(Exception error, string message, params object[] args) { - Console.WriteLine($"[DRIVER-ERROR][{Now}]{message}", args); + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine($"{Now} ERR: {message}", args); + Console.ResetColor(); } public void Info(string message, params object[] args) { - Console.WriteLine($"[DRIVER-INFO] [{Now}]{message}", args); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine($"{Now} INF: {message}", args); + Console.ResetColor(); } public bool IsDebugEnabled() @@ -48,11 +58,15 @@ public bool IsTraceEnabled() public void Trace(string message, params object[] args) { - Console.WriteLine($"[DRIVER-TRACE][{Now}]{message}", args); + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine($"{Now} TRC: {message}", args); + Console.ResetColor(); } public void Warn(Exception error, string message, params object[] args) { - Console.WriteLine($"[DRIVER-WARN] [{Now}]{message}", args); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"{Now} WRN: {message}", args); + Console.ResetColor(); } } 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 ca98fdf51..0d3b3c856 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/DriverQuery/ExecuteQuery.cs @@ -78,7 +78,9 @@ private QueryConfig BuildConfig() var transactionConfig = new TransactionConfig { Timeout = data.config.timeout.HasValue ? TimeSpan.FromMilliseconds(data.config.timeout.Value) : null, - Metadata = data.config.txMeta != null ? CypherToNativeObject.ConvertDictionaryToNative(data.config.txMeta) : new Dictionary() + Metadata = data.config.txMeta != null + ? CypherToNativeObject.ConvertDictionaryToNative(data.config.txMeta) + : new Dictionary() }; var authToken = data.config.authorizationToken switch @@ -138,8 +140,10 @@ public class ExecuteQueryConfigDto public string impersonatedUser { get; set; } public string bookmarkManagerId { get; set; } public int? timeout { get; set; } + [JsonConverter(typeof(QueryParameterConverter))] public Dictionary txMeta { get; set; } + public AuthorizationToken authorizationToken { get; set; } } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Protocol.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Protocol.cs index b1da138c2..6be035ccc 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Protocol.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Protocol.cs @@ -77,7 +77,8 @@ public string internal set; } //Only exposes the get option so that the serializer will output it. Don't want to read in on deserialization. - [JsonIgnore] protected ProtocolObjectManager ObjManager { get; set; } + [JsonIgnore] + protected ProtocolObjectManager ObjManager { get; set; } public event EventHandler ProtocolEvent; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolException.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolException.cs index 6e9aac16d..ee395f75e 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolException.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolException.cs @@ -23,7 +23,8 @@ internal class ProtocolException : ProtocolObject { public ProtocolExceptionType data { get; set; } = new(); - [JsonIgnore] public Exception ExceptionObj { get; set; } + [JsonIgnore] + public Exception ExceptionObj { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolResponse.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolResponse.cs index 12ebcdfa8..81dddb8b4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolResponse.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/ProtocolResponse.cs @@ -19,9 +19,6 @@ namespace Neo4j.Driver.Tests.TestBackend.Protocol; internal class ProtocolResponse { - public string name { get; } - public object data { get; set; } - public ProtocolResponse(string newName, string newId) { data = new ResponseType(); @@ -41,6 +38,9 @@ public ProtocolResponse(string newName) data = null; } + public string name { get; } + public object data { get; set; } + public string Encode() { return JsonConvert.SerializeObject(this); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/CypherTypeField.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/CypherTypeField.cs index a5aaf52ab..bbe267d2b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/CypherTypeField.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/CypherTypeField.cs @@ -26,9 +26,11 @@ namespace Neo4j.Driver.Tests.TestBackend.Protocol.Result; internal class CypherTypeField : ProtocolObject { - [JsonProperty("data")] public CypherTypeFieldRequest RequestData { get; set; } = new(); + [JsonProperty("data")] + public CypherTypeFieldRequest RequestData { get; set; } = new(); - [JsonIgnore] public object Field { get; set; } + [JsonIgnore] + public object Field { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/Result.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/Result.cs index af3d01701..67888ce30 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/Result.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/Result.cs @@ -21,7 +21,8 @@ namespace Neo4j.Driver.Tests.TestBackend.Protocol.Result; internal class Result : ProtocolObject { - [JsonIgnore] public IResultCursor ResultCursor { get; set; } + [JsonIgnore] + public IResultCursor ResultCursor { get; set; } public ResultType data { get; set; } = new(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultConsume.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultConsume.cs index c1585605d..6e796fba5 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultConsume.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultConsume.cs @@ -22,9 +22,11 @@ internal class ResultConsume : ProtocolObject { public ResultConsumeType data { get; set; } = new(); - [JsonIgnore] public IRecord Records { get; set; } + [JsonIgnore] + public IRecord Records { get; set; } - [JsonIgnore] public IResultSummary Summary { get; set; } + [JsonIgnore] + public IResultSummary Summary { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultList.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultList.cs index 55a95ae9a..1d9df2a78 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultList.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultList.cs @@ -25,7 +25,8 @@ internal class ResultList : ProtocolObject { public ResultListType data { get; set; } = new(); - [JsonIgnore] public List Records { get; set; } + [JsonIgnore] + public List Records { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultNext.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultNext.cs index 8c03d0550..d248a1feb 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultNext.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/ResultNext.cs @@ -26,7 +26,8 @@ internal class ResultNext : ProtocolObject { public ResultNextType data { get; set; } = new(); - [JsonIgnore] public IRecord Records { get; set; } + [JsonIgnore] + public IRecord Records { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/SummaryJsonSerializer.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/SummaryJsonSerializer.cs index 6700fe3c6..eb17026c0 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/SummaryJsonSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Result/SummaryJsonSerializer.cs @@ -43,8 +43,7 @@ public static JRaw SerializeToRaw(IResultSummary summary) profile = MapToProfilePlan(summary.Profile), resultAvailableAfter = GetTotalMilliseconds(summary.ResultAvailableAfter), resultConsumedAfter = GetTotalMilliseconds(summary.ResultConsumedAfter), - gqlStatusObjects = MapGqlStatusObjects( - summary.GqlStatusObjects) + gqlStatusObjects = MapGqlStatusObjects(summary.GqlStatusObjects) })); } @@ -225,7 +224,9 @@ private static object MapGqlStatusObjects(IList statusObjects) { ["gqlStatus"] = x.GqlStatus, ["statusDescription"] = x.StatusDescription, - ["diagnosticRecord"] = x.DiagnosticRecord.ToDictionary(y => y.Key, y => NativeToCypher.Convert(y.Value)), + ["diagnosticRecord"] = x.DiagnosticRecord.ToDictionary( + y => y.Key, + y => NativeToCypher.Convert(y.Value)), ["classification"] = x.Classification.ToString().ToUpper(), ["rawClassification"] = x.RawClassification, ["rawSeverity"] = x.RawSeverity, diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/BaseSessionType.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/BaseSessionType.cs index 279df65a6..aba54c98b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/BaseSessionType.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/BaseSessionType.cs @@ -33,7 +33,8 @@ internal abstract class BaseSessionType [JsonProperty(Required = Required.AllowNull)] public int? timeout { get; set; } - [JsonIgnore] public bool TimeoutSet { get; set; } + [JsonIgnore] + public bool TimeoutSet { get; set; } public TransactionConfigBuilder ConfigureTxTimeout(TransactionConfigBuilder configBuilder) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/NewSession.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/NewSession.cs index d833b4316..6b0b9c061 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/NewSession.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/NewSession.cs @@ -35,11 +35,16 @@ public enum SessionState RetryAbleNegative } - [JsonIgnore] public SessionState RetryState { get; private set; } = SessionState.RetryAbleNothing; - [JsonIgnore] public string RetryableErrorId { get; private set; } + [JsonIgnore] + public SessionState RetryState { get; private set; } = SessionState.RetryAbleNothing; + + [JsonIgnore] + public string RetryableErrorId { get; private set; } public NewSessionType data { get; set; } = new(); - [JsonIgnore] public IAsyncSession Session { get; set; } + + [JsonIgnore] + public IAsyncSession Session { get; set; } [JsonIgnore] public AccessMode GetAccessMode @@ -55,7 +60,8 @@ public AccessMode GetAccessMode } } - [JsonIgnore] public List SessionTransactions { get; } = new(); + [JsonIgnore] + public List SessionTransactions { get; } = new(); private void SessionConfig(SessionConfigBuilder configBuilder) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionBeginTransaction.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionBeginTransaction.cs index 553f679d8..60adaa17f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionBeginTransaction.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionBeginTransaction.cs @@ -24,7 +24,8 @@ internal class SessionBeginTransaction : ProtocolObject { public SessionBeginTransactionType data { get; set; } = new(); - [JsonIgnore] public string TransactionId { get; set; } + [JsonIgnore] + public string TransactionId { get; set; } public override async Task Process(Controller controller) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionReadTransaction.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionReadTransaction.cs index b828efc33..2c0f05620 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionReadTransaction.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionReadTransaction.cs @@ -26,7 +26,8 @@ internal class SessionReadTransaction : ProtocolObject { public SessionReadTransactionType data { get; set; } = new(); - [JsonIgnore] private string TransactionId { get; set; } + [JsonIgnore] + private string TransactionId { get; set; } public override async Task Process(Controller controller) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionRun.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionRun.cs index 1cd7d2339..058800f3f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionRun.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionRun.cs @@ -25,7 +25,8 @@ internal class SessionRun : ProtocolObject { public SessionRunType data { get; set; } = new(); - [JsonIgnore] private string ResultId { get; set; } + [JsonIgnore] + private string ResultId { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionWriteTransaction.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionWriteTransaction.cs index 258f363d5..a75e3058a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionWriteTransaction.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Session/SessionWriteTransaction.cs @@ -26,7 +26,8 @@ internal class SessionWriteTransaction : ProtocolObject { public SessionWriteTransactionType data { get; set; } = new(); - [JsonIgnore] public string TransactionId { get; set; } + [JsonIgnore] + public string TransactionId { get; set; } public override async Task Process(Controller controller) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckDriverIsEncrypted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckDriverIsEncrypted.cs index c39507f82..4e5ee6008 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckDriverIsEncrypted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckDriverIsEncrypted.cs @@ -23,7 +23,8 @@ internal class CheckDriverIsEncrypted : ProtocolObject { public DriverIsEncryptedType data { get; set; } = new(); - [JsonIgnore] private bool Encrypted { get; set; } + [JsonIgnore] + private bool Encrypted { get; set; } public override Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckMultiDBSupport.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckMultiDBSupport.cs index 944cb07b0..52d58fc17 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckMultiDBSupport.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/CheckMultiDBSupport.cs @@ -23,7 +23,8 @@ internal class CheckMultiDBSupport : ProtocolObject { public CheckMultiDBSupportType data { get; set; } = new(); - [JsonIgnore] private bool MutlitDBSupportAvailable { get; set; } + [JsonIgnore] + private bool MutlitDBSupportAvailable { get; set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/GetRoutingTable.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/GetRoutingTable.cs index d77686ec3..f1746578b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/GetRoutingTable.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/GetRoutingTable.cs @@ -25,7 +25,8 @@ internal class GetRoutingTable : ProtocolObject { public GetRoutingTableDataType data { get; set; } = new(); - [JsonIgnore] public IRoutingTable RoutingTable { get; set; } + [JsonIgnore] + public IRoutingTable RoutingTable { get; set; } public override async Task Process(Controller controller) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/ResolverResolutionCompleted.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/ResolverResolutionCompleted.cs index 78b05f696..55bcc942f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/ResolverResolutionCompleted.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/SupportFunctions/ResolverResolutionCompleted.cs @@ -24,7 +24,8 @@ internal class ResolverResolutionCompleted : ProtocolObject { public ResolverResolutionCompletedType data { get; set; } = new(); - [JsonIgnore] public ListAddressResolver Resolver { get; private set; } + [JsonIgnore] + public ListAddressResolver Resolver { get; private set; } public override async Task Process() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Time/FakeTime.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Time/FakeTime.cs index 1ce75ee1d..a5774eee4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Time/FakeTime.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Time/FakeTime.cs @@ -82,9 +82,9 @@ public override string Respond() internal class FakeTime : IDateTimeProvider { public static readonly FakeTime Instance = new(); + private readonly List _timers = new(); private DateTime? _frozenTime; - private readonly List _timers = new(); public DateTime Now() { @@ -121,21 +121,19 @@ public void Uninstall() internal class FakeTimer : ITimer { - private long _advanced; - - public void Advance(int milliseconds) - { - _advanced += milliseconds; - } + public long ElapsedMilliseconds { get; private set; } - public long ElapsedMilliseconds => _advanced; - public void Reset() { - _advanced = 0; + ElapsedMilliseconds = 0; } public void Start() { } + + public void Advance(int milliseconds) + { + ElapsedMilliseconds += milliseconds; + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Transaction/TransactionRun.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Transaction/TransactionRun.cs index 31ffb7668..f8f575846 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Transaction/TransactionRun.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/Transaction/TransactionRun.cs @@ -27,7 +27,8 @@ internal class TransactionRun : ProtocolObject { public TransactionRunType data { get; set; } = new(); - [JsonIgnore] private string ResultId { get; set; } + [JsonIgnore] + private string ResultId { get; set; } public override async Task Process(Controller controller) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/UI/ConsoleTextWriter.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/UI/ConsoleTextWriter.cs new file mode 100644 index 000000000..ff29902b8 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/Protocol/UI/ConsoleTextWriter.cs @@ -0,0 +1,66 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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.IO; +using System.Text; + +namespace Neo4j.Driver.Tests.TestBackend.Protocol.UI; + +public class ConsoleTextWriter : TextWriter +{ + public override Encoding Encoding => Console.Out.Encoding; + private string Now => DateTime.UtcNow.ToString("HH:mm:ss"); + private const ConsoleColor WriteColor = ConsoleColor.DarkCyan; + + private void DoInColor(Action action) + { + var oldColor = Console.ForegroundColor; + Console.ForegroundColor = WriteColor; + Console.Out.Write($"{Now} TKB: "); + action(Console.Out); + Console.ForegroundColor = oldColor; + } + + public override void Write(char value) + { + DoInColor(writer => writer.Write(value)); + } + + public override void Write(string value) + { + DoInColor(writer => writer.Write(value)); + } + + public override void WriteLine(string value) + { + DoInColor(writer => writer.WriteLine(value)); + } + + public override void Write(char[] buffer, int index, int count) + { + DoInColor(writer => writer.Write(buffer, index, count)); + } + + public override void WriteLine(char[] buffer, int index, int count) + { + DoInColor(writer => writer.WriteLine(buffer, index, count)); + } + + public override void Flush() + { + Console.Out.Flush(); + } +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/SupportedFeatures.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/SupportedFeatures.cs index 766906eb9..30ac2c27e 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/SupportedFeatures.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/SupportedFeatures.cs @@ -67,6 +67,7 @@ static SupportedFeatures() "Feature:Bolt:5.5", "Feature:Bolt:5.6", "Feature:Bolt:5.7", + "Feature:Bolt:5.8", "Feature:Bolt:Patch:UTC", "Feature:Bolt:HandshakeManifestV1", "Feature:Impersonation", @@ -80,6 +81,8 @@ static SupportedFeatures() //"Optimization:MinimalResets", "Optimization:AuthPipelining", "Optimization:PullPipelining", + //"Optimization:HomeDbCacheBasicPrincipalIsImpersonatedUser", + "Optimization:HomeDatabaseCache" //"Optimization:ResultListFetchAll", }; } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/TestBlackList.cs b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/TestBlackList.cs index 0953563a2..a9c56553d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/TestBlackList.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/TestBlackList.cs @@ -120,7 +120,11 @@ private static readonly (string Name, string Reason)[] BlackListNames = ("test_temporal_types.TestDataTypes.test_date_time_cypher_created_tz_id", "No Antarctica/Troll mapping available."), ("test_temporal_types.TestDataTypes.test_should_echo_all_timezone_ids", - "EST/HST/MST not supported.") + "EST/HST/MST not supported."), + + ("test_connection_acquisition_timeout_during_fallback", + "Driver currently uses separate acquisition timeouts for the separate connections. Future behavioural " + + "fix (6.0) needed to pass test and unify with other drivers.") }; public static bool FindTest(string testName, out string reason) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/.editorconfig b/Neo4j.Driver/Neo4j.Driver.Tests/.editorconfig index fe783240e..53941ecd1 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/.editorconfig +++ b/Neo4j.Driver/Neo4j.Driver.Tests/.editorconfig @@ -369,11 +369,11 @@ dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_required_modifiers = instance dotnet_separate_import_directive_groups = false @@ -663,8 +663,8 @@ resharper_line_break_before_requires_clause = do_not_change resharper_linkage_specification_braces = end_of_line resharper_linkage_specification_indentation = none resharper_local_function_body = block_body -resharper_macro_block_begin = -resharper_macro_block_end = +resharper_macro_block_begin = +resharper_macro_block_end = resharper_max_array_initializer_elements_on_line = 10000 resharper_max_attribute_length_for_same_line = 38 resharper_max_enum_members_on_line = 1 @@ -734,7 +734,7 @@ resharper_resx_attribute_indent = single_indent resharper_resx_blank_line_after_pi = true resharper_resx_indent_text = OneIndent resharper_resx_keep_user_linebreaks = true -resharper_resx_linebreak_before_elements = +resharper_resx_linebreak_before_elements = resharper_resx_max_blank_lines_between_tags = 0 resharper_resx_pi_attribute_style = do_not_touch resharper_resx_space_before_self_closing = false @@ -963,7 +963,7 @@ resharper_xml_attribute_indent = align_by_first_attribute resharper_xml_blank_line_after_pi = true resharper_xml_indent_text = OneIndent resharper_xml_keep_user_linebreaks = true -resharper_xml_linebreak_before_elements = +resharper_xml_linebreak_before_elements = resharper_xml_max_blank_lines_between_tags = 2 resharper_xml_pi_attribute_style = do_not_touch resharper_xml_space_before_self_closing = true diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs index 2e7f00ac1..761bcbbfc 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs @@ -23,6 +23,7 @@ using Moq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.Logging; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; @@ -72,8 +73,7 @@ internal static Mock MockedConnectionWithSuccessResponse(IBoltProto It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask) - .Callback( - (IRequestMessage _, IResponseHandler h1) => { h1.OnSuccess(new Dictionary()); }); + .Callback((IRequestMessage _, IResponseHandler h1) => { h1.OnSuccess(new Dictionary()); }); if (protocol == null) { @@ -100,7 +100,8 @@ public async Task ShouldDelegateToProtocolRunAutoCommitTxAsync(bool reactive) mockConn.Verify( x => x.RunInAutoCommitTransactionAsync( It.IsAny(), - It.IsAny()), + It.IsAny(), + It.IsAny()), Times.Once); } } @@ -178,7 +179,10 @@ public async void ShouldDefaultToBlockingTransactionStart() x => x.BeginTransactionAsync( It.IsAny(), - It.Is(y => y.TransactionInfo.AwaitBegin == true)), + It.Is(y => y.TransactionInfo.AwaitBegin == true), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); } @@ -227,7 +231,10 @@ public async void ShouldCloseConnectionOnRunIfBeginTxFailed() x => x.BeginTransactionAsync( It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .Throws(new IOException("Triggered an error when beginTx")); var session = NewSession(mockConn.Object); @@ -252,7 +259,10 @@ public async void ShouldCloseConnectionOnNewBeginTxIfBeginTxFailed() x => x.BeginTransactionAsync( It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .Returns(Task.CompletedTask) .Callback( () => @@ -292,20 +302,25 @@ public async void PipelinedShouldBeginWithoutBlocking() var session = new AsyncSession( new TestConnectionProvider(mockConn.Object), - null, + NullLogger.Instance, new AsyncRetryLogic(TimeSpan.Zero, null), 0, new Driver.SessionConfig(), false, false); - await session.PipelinedExecuteReadAsync(_ => Task.FromResult(null as EagerResult), new TransactionConfig()); + await session.PipelinedExecuteReadAsync( + _ => Task.FromResult(null as EagerResult), + new TransactionConfig()); mockProtocol.Verify( x => x.BeginTransactionAsync( It.IsAny(), - It.Is(y => y.TransactionInfo.AwaitBegin == false)), + It.Is(y => y.TransactionInfo.AwaitBegin == false), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); } } @@ -321,7 +336,10 @@ public async void ShouldCloseConnectionIfBeginTxFailed() x => x.BeginTransactionAsync( It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .Throws(new IOException("Triggered an error when beginTx")); var session = NewSession(mockConn.Object); @@ -428,6 +446,9 @@ public Task VerifyConnectivityAndGetInfoAsync() throw new NotSupportedException(); } + /// + public bool IsDirectDriver => false; + public DriverContext DriverContext => new( new Uri("neo4j://myTest.org"), AuthTokenManagers.Static(AuthTokens.None), @@ -481,7 +502,14 @@ public void ShouldSyncBookmarksOnUpdateBookmarks() .WithBookmarkManager(bookmarkManager.Object) .Build(); - using (var session = new AsyncSession(null, null, null, 0, cfg, false, false)) + using (var session = new AsyncSession( + null, + null, + null, + 0, + cfg, + false, + false)) { session.UpdateBookmarks(new InternalBookmarks("a")); bookmarkManager.Verify( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Auth/Neo4jAuthTokenManagerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Auth/Neo4jAuthTokenManagerTests.cs index eea931c15..dc4ad0db7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Auth/Neo4jAuthTokenManagerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Auth/Neo4jAuthTokenManagerTests.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -44,7 +42,8 @@ public async Task ShouldCacheToken() { var (authData, _) = GetTwoAuthTokens(); - int callCount = 0; + var callCount = 0; + ValueTask TokenProvider() { callCount++; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/BookmarkTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/BookmarkTests.cs index 95c3d0b8a..cf672adb2 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/BookmarkTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/BookmarkTests.cs @@ -98,4 +98,4 @@ public void ShouldUnionValues(string[] values1, string[] values2, string[] value } } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ConfigTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ConfigTests.cs index 8e3a36da0..bf8cc40df 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/ConfigTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/ConfigTests.cs @@ -265,7 +265,7 @@ public void WithNotifications_ShouldSetCategoryWithClassification( config .Which .DisabledCategories.Should() - .BeEquivalentTo([category]); + .BeEquivalentTo(category); config .Which @@ -320,7 +320,7 @@ public void WithNotifications_ShouldSetMultipleCategories() config .Which .DisabledCategories.Should() - .BeEquivalentTo([Category.Deprecation, Category.Hint]); + .BeEquivalentTo(Category.Deprecation, Category.Hint); config .Which @@ -344,7 +344,7 @@ public void WithNotifications_ShouldSetMultipleClassifications() config .Which .DisabledCategories.Should() - .BeEquivalentTo([Category.Deprecation, Category.Hint]); + .BeEquivalentTo(Category.Deprecation, Category.Hint); config .Which diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs index 4543ed91a..2a3e7f68a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs @@ -24,8 +24,8 @@ using Moq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; -using Neo4j.Driver.Internal.Util; using Neo4j.Driver.Internal.Protocol; +using Neo4j.Driver.Internal.Util; using Neo4j.Driver.Tests.TestUtil; using Xunit; using Xunit.Abstractions; @@ -291,6 +291,7 @@ private Mock MockValidator(Action x.GetConnectionLifetimeStatus(It.IsAny())) .Returns(AcquireStatus.Healthy); } + return validator; } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionValidatorTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionValidatorTests.cs index b15dc0323..2f2ff44d7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionValidatorTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionValidatorTests.cs @@ -30,8 +30,23 @@ private static ConnectionValidator NewConnectionValidator( TimeSpan? maxConnLifetime = null, TimeSpan? livelinessCheckTimeout = null) { - return new ConnectionValidator(connIdleTimeout ?? Config.InfiniteInterval, - maxConnLifetime ?? Config.InfiniteInterval, livelinessCheckTimeout); + return new ConnectionValidator( + connIdleTimeout ?? Config.InfiniteInterval, + maxConnLifetime ?? Config.InfiniteInterval, + livelinessCheckTimeout); + } + + private static (Mock conn, Mock idle, Mock life) Mock() + { + var conn = new Mock(); + conn.Setup(x => x.Version).Returns(BoltProtocolVersion.V5_1); + + var idleTimer = new Mock(); + var lifeTimer = new Mock(); + + conn.Setup(x => x.IdleTimer).Returns(idleTimer.Object); + conn.Setup(x => x.LifetimeTimer).Returns(lifeTimer.Object); + return (conn, idleTimer, lifeTimer); } public class IsConnectionReusableTests @@ -131,17 +146,4 @@ public void ShouldRequireLiveness() idleTimer.Verify(x => x.Reset(), Times.Once); } } - - private static (Mock conn, Mock idle, Mock life) Mock() - { - var conn = new Mock(); - conn.Setup(x => x.Version).Returns(BoltProtocolVersion.V5_1); - - var idleTimer = new Mock(); - var lifeTimer = new Mock(); - - conn.Setup(x => x.IdleTimer).Returns(idleTimer.Object); - conn.Setup(x => x.LifetimeTimer).Returns(lifeTimer.Object); - return (conn, idleTimer, lifeTimer); - } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MockedMessagingClient.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MockedMessagingClient.cs index d76eeebb4..d9d3874ec 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MockedMessagingClient.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MockedMessagingClient.cs @@ -171,4 +171,4 @@ internal static SuccessMessage SuccessMessage(IDictionary fields ? new SuccessMessage(new Dictionary()) : new SuccessMessage(fields); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs index a0a35a84e..65d47ae90 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs @@ -75,7 +75,7 @@ private static (Mock, Mock) CreateMockIo mockIoFactory .Setup(x => x.TcpSocketClient(It.IsAny(), It.IsAny())) .Returns(connMock.Object); - + configureFactory?.Invoke(mockIoFactory); return (connMock, mockIoFactory); @@ -127,8 +127,7 @@ public async void ShouldNotCatchHandshakeFailuresOrConstructIoTypes() var client = NewClient(io, null, mockHandshaker); - var ex = await Record.ExceptionAsync( - () => client.ConnectAsync(CancellationToken.None)); + var ex = await Record.ExceptionAsync(() => client.ConnectAsync(CancellationToken.None)); mockHandshaker.Verify( x => x.DoHandshakeAsync( @@ -337,4 +336,4 @@ public async Task ShouldCallDisconnectAsyncOnTheTcpSocketClientWhenStoppedAsync( client.IsOpen.Should().BeFalse(); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs index 4766c3cf8..fb7f11999 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs @@ -74,11 +74,13 @@ public async Task ShouldConnectClient() var bpFactory = new Mock(); bpFactory.Setup(x => x.ForVersion(BoltProtocolVersion.V3_0)).Returns(protocolMock.Object); - var conn = NewSocketConnection(mockClient.Object, boltProtocolFactory: bpFactory.Object, + var conn = NewSocketConnection( + mockClient.Object, + boltProtocolFactory: bpFactory.Object, context: new DriverContext(new Uri("bolt://localhost:7687"), AuthTokenManagers.None, new Config())); // When - await conn.InitAsync(null); + await conn.InitAsync(); // Then mockClient.Verify(c => c.ConnectAsync(CancellationToken.None), Times.Once); @@ -102,7 +104,7 @@ public async Task ShouldThrowClientErrorIfFailedToConnectToServerWithinTimeout() // ReSharper disable once ObjectCreationAsStatement var conn = new SocketConnection(mockClient.Object, AuthToken, Logger, Server); // When - var error = await Record.ExceptionAsync(() => conn.InitAsync(null)); + var error = await Record.ExceptionAsync(() => conn.InitAsync()); // Then error.Should().BeOfType(); error.Message.Should().Be("I will stop socket conn from initialization"); @@ -227,7 +229,7 @@ public async Task ShouldEnqueueBoth() var h1 = new Mock(); var m2 = new Mock(); var h2 = new Mock(); - + await con.EnqueueAsync(m1.Object, h1.Object, m2.Object, h2.Object); con.Messages[0].Should().Be(m1.Object); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/DriverTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/DriverTests.cs index b1fc3a30f..adc4c9b91 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/DriverTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/DriverTests.cs @@ -19,7 +19,6 @@ using Moq; using Neo4j.Driver.Internal; using Xunit; - using InternalDriver = Neo4j.Driver.Internal.Driver; #pragma warning disable CS0618 @@ -71,8 +70,7 @@ public void ShouldErrorIfUriWrongFormat() [Fact] public void ShouldErrorIfBoltSchemeWithRoutingContext() { - var exception = Record.Exception( - () => GraphDatabase.Driver("bolt://localhost/?name=molly&age=1&color=white")); + var exception = Record.Exception(() => GraphDatabase.Driver("bolt://localhost/?name=molly&age=1&color=white")); exception.Should().BeOfType(); exception.Message.Should().Contain("Routing context are not supported with scheme 'bolt'"); @@ -156,10 +154,12 @@ public async void ShouldTryVerifyConnection() mock.Setup(x => x.VerifyConnectivityAndGetInfoAsync()) .Returns(Task.FromResult(new Mock().Object)); - var driver = (IDriver)new InternalDriver(new Uri("bolt://localhost"), + var driver = (IDriver)new InternalDriver( + new Uri("bolt://localhost"), mock.Object, null, TestDriverContext.MockContext); + var connects = await driver.TryVerifyConnectivityAsync(); connects.Should().BeTrue(); @@ -172,10 +172,12 @@ public async void ShouldCatchInTryVerifyConnection() mock.Setup(x => x.VerifyConnectivityAndGetInfoAsync()) .ThrowsAsync(new Exception("broken")); - var driver = (IDriver)new InternalDriver(new Uri("bolt://localhost"), + var driver = (IDriver)new InternalDriver( + new Uri("bolt://localhost"), mock.Object, null, TestDriverContext.MockContext); + var connects = await driver.TryVerifyConnectivityAsync(); connects.Should().BeFalse(); @@ -189,7 +191,8 @@ public async void ShouldGetInfoConnection() mock.Setup(x => x.VerifyConnectivityAndGetInfoAsync()) .Returns(Task.FromResult(mockServerInfo)); - var driver = new InternalDriver(new Uri("bolt://localhost"), + var driver = new InternalDriver( + new Uri("bolt://localhost"), mock.Object, null, TestDriverContext.MockContext); @@ -205,10 +208,12 @@ public async void ShouldTestSupportMultiDb() { var mock = new Mock(); mock.Setup(x => x.SupportsMultiDbAsync()).Returns(Task.FromResult(true)); - var driver = new InternalDriver(new Uri("bolt://localhost"), + var driver = new InternalDriver( + new Uri("bolt://localhost"), mock.Object, null, TestDriverContext.MockContext); + await driver.SupportsMultiDbAsync(); mock.Verify(x => x.SupportsMultiDbAsync(), Times.Once); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Exceptions/Neo4jExceptionFactoryTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Exceptions/Neo4jExceptionFactoryTests.cs index ddbd8c08c..cc13cda3d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Exceptions/Neo4jExceptionFactoryTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Exceptions/Neo4jExceptionFactoryTests.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -16,7 +14,6 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using FluentAssertions; using Neo4j.Driver.Internal.ExceptionHandling; @@ -46,7 +43,8 @@ public class Neo4jExceptionFactoryTests ["Neo.TransientError.TemporaryDisabled", typeof(TransientException)] ]; - [Theory, MemberData(nameof(CodeToTypeMapping))] + [Theory] + [MemberData(nameof(CodeToTypeMapping))] public void ShouldCreateCorrectExceptionType(string code, Type exceptionType) { var subject = new Neo4jExceptionFactory(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs index 4ab4fc271..c7977789e 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/ExecutableQuery/ExecutableQueryTests.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -275,7 +273,7 @@ public async Task ShouldReturnMappedTransformedReducedValue() var result = await subject .WithMap(i => i * 10) - .WithReduce(() => 0, (x, y) => x + y, i => $"<{(i * 2)}>") + .WithReduce(() => 0, (x, y) => x + y, i => $"<{i * 2}>") .ExecuteAsync(); result.Result.Should().Be("<900>"); @@ -352,7 +350,7 @@ public async Task ShouldUseStreamProcessor() async IAsyncEnumerable GetInts(int start, int count) { - foreach(var i in Enumerable.Range(start, count)) + foreach (var i in Enumerable.Range(start, count)) { await Task.Yield(); yield return i; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Extensions/StreamExtensionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Extensions/StreamExtensionTests.cs index 864f50538..500f190c3 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Extensions/StreamExtensionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Extensions/StreamExtensionTests.cs @@ -44,8 +44,7 @@ void Callback(Memory _, CancellationToken token) const int timeout = 100; - var ex = await Record.ExceptionAsync( - () => streamMock.Object.ReadWithTimeoutAsync(new byte[1], 0, 1, timeout)); + var ex = await Record.ExceptionAsync(() => streamMock.Object.ReadWithTimeoutAsync(new byte[1], 0, 1, timeout)); ex.Should() .BeOfType() diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Filters/OSFilters.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Filters/OSFilters.cs index 0b929829a..d36612f4a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Filters/OSFilters.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Filters/OSFilters.cs @@ -100,4 +100,4 @@ public class UnixTheoryAttribute : OSTheoryAttribute public UnixTheoryAttribute() : base(OSPlatform.Linux, OSPlatform.OSX) { } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Filters/RuntimeFilters.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Filters/RuntimeFilters.cs index 825fe90da..c16372b6e 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Filters/RuntimeFilters.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Filters/RuntimeFilters.cs @@ -73,4 +73,4 @@ public DotnetCoreTheoryAttribute() Skip = "Test is supposed to be run only on .net core runtimes"; } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/GqlCompliance/GqlErrorTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/GqlCompliance/GqlErrorTests.cs index b61835885..fbe15a7dd 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/GqlCompliance/GqlErrorTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/GqlCompliance/GqlErrorTests.cs @@ -45,7 +45,7 @@ public void BuildFailureMessage_ShouldDeserializeCorrectly() } }; - int majorVersion = 4; + var majorVersion = 4; // Act var result = FailureMessageSerializer.BuildFailureMessage(values, majorVersion); @@ -73,7 +73,7 @@ public void BuildFailureMessage_ShouldDeserializeCorrectly() result.GqlClassification.Should().Be("UNKNOWN"); } - [Fact] + [Fact] public void GetException_ShouldHandleNestedFailureMessages() { // Arrange diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkReaderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkReaderTests.cs index 381874a26..0e96d8889 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkReaderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkReaderTests.cs @@ -280,7 +280,11 @@ private static byte[] GenerateMessageChunk(int messageSize) { var buffer = Enumerable.Range(0, messageSize).Select(i => i % byte.MaxValue).Select(i => (byte)i).ToArray(); var stream = new MemoryStream(); - var cs = new DriverContext(new Uri("bolt://localhost:7687"), new StaticAuthTokenManager(AuthTokens.None), new Config()); + var cs = new DriverContext( + new Uri("bolt://localhost:7687"), + new StaticAuthTokenManager(AuthTokens.None), + new Config()); + var writer = new ChunkWriter(stream, cs, new Mock().Object); writer.OpenChunk(); @@ -314,4 +318,4 @@ private static byte[] GenerateMessages(int messageSizePerChunk, int maxBytes) return stream.ToArray(); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkWriterTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkWriterTests.cs index eda98c059..07f3db5a9 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkWriterTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ChunkWriterTests.cs @@ -29,6 +29,7 @@ namespace Neo4j.Driver.Tests.Internal.IO; public class ChunkWriterTests { private readonly Mock _logger = new(); + private static DriverContext TestContext(int defaultRead, int defaultWrite, int maxRead, int maxWrite) { return TestDriverContext.With( @@ -354,4 +355,4 @@ private static async Task ConstructMessage(byte[] buffer) return stream.ToArray(); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/IgnoredMessageSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/IgnoredMessageSerializerTests.cs index 8c9ec7967..1da56ed38 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/IgnoredMessageSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/IgnoredMessageSerializerTests.cs @@ -35,4 +35,4 @@ public void ShouldReturnIgnoredMessage() var message = IgnoredMessageSerializer.Instance.Deserialize(null); message.Should().Be(IgnoredMessage.Instance); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/PullAllMessageSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/PullAllMessageSerializerTests.cs index 5d74786bc..9f16ad4d6 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/PullAllMessageSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/PullAllMessageSerializerTests.cs @@ -68,4 +68,4 @@ public void ShouldSerialize(int major, int minor) bytes[0].Should().Be(0xB0); bytes[1].Should().Be(0x3F); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RecordMessageSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RecordMessageSerializerTests.cs index f5ee5ae1f..283c2dfc7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RecordMessageSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RecordMessageSerializerTests.cs @@ -59,4 +59,4 @@ public void ShouldDeserialize(int major, int minor) message.Should().BeOfType().Which.Fields.Should().BeEquivalentTo(0L, "a"); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/ResetMessageSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/ResetMessageSerializerTests.cs index b5e1c4d7b..65faf7579 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/ResetMessageSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/ResetMessageSerializerTests.cs @@ -71,4 +71,4 @@ public void ShouldSerialize(int major, int minor) // message tag headerBytes[1].Should().Be(0x0F); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RunWithMetadataMessageSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RunWithMetadataMessageSerializerTests.cs index 76684f369..ec16ba136 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RunWithMetadataMessageSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/RunWithMetadataMessageSerializerTests.cs @@ -84,4 +84,4 @@ public void ShouldSerialize(int major, int minor) var metadata = reader.ReadMap(); metadata.Should().ContainKey("db").WhichValue.Should().Be("neo4j"); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/SuccessMessageSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/SuccessMessageSerializerTests.cs index 2bde578ad..e3e725dac 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/SuccessMessageSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/MessageSerializers/SuccessMessageSerializerTests.cs @@ -67,4 +67,4 @@ public void ShouldDeserialize(int major, int minor) .WhichValue.Should() .Be(1L); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/PackStreamTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/PackStreamTests.cs index 036748eaa..fff6125a4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/PackStreamTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/PackStreamTests.cs @@ -45,8 +45,10 @@ internal virtual PackStreamReaderMachine CreateReaderMachine( internal override PackStreamWriterMachine CreateWriterMachine(BoltProtocolVersion version = null) { - return CreateWriterMachine(new MessageFormat(version ?? BoltProtocolVersion.V3_0, - TestDriverContext.MockContext).WriteStructHandlers); + return CreateWriterMachine( + new MessageFormat( + version ?? BoltProtocolVersion.V3_0, + TestDriverContext.MockContext).WriteStructHandlers); } internal override PackStreamReaderMachine CreateReaderMachine(byte[] data, BoltProtocolVersion version = null) @@ -154,9 +156,13 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val } } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/Utils/Machines.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/Utils/Machines.cs index 500844e59..5266f410f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/Utils/Machines.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/Utils/Machines.cs @@ -57,4 +57,4 @@ internal PackStreamReader Reader() { return _reader; } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementNodeSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementNodeSerializerTests.cs index 474443884..b1bf11fba 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementNodeSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementNodeSerializerTests.cs @@ -89,6 +89,7 @@ public void ShouldDeserializeSpan() { "prop2", 15 }, { "prop3", true } }); + writer.Write("1"); var readerMachine = CreateSpanReader(writerMachine.GetOutput()); @@ -96,4 +97,4 @@ public void ShouldDeserializeSpan() Validate(value); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementPathSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementPathSerializerTests.cs index 89241af1e..8d7a08f7d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementPathSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementPathSerializerTests.cs @@ -171,7 +171,6 @@ private static void ValidateAdding(object value) relationships[0].EndNodeElementId.Should().Be("2"); } - private static void ValidatePath(AndWhichConstraint path) { path.Which.Nodes.Should().AllBeOfType(); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementRelationshipSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementRelationshipSerializerTests.cs index 71dd92598..62f31d370 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementRelationshipSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementRelationshipSerializerTests.cs @@ -103,4 +103,4 @@ private static void Validate(object value) value.Should().BeOfType().Which.StartNodeElementId.Should().Be("n1"); value.Should().BeOfType().Which.EndNodeElementId.Should().Be("n2"); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementUnboundRelationshipSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementUnboundRelationshipSerializerTests.cs index 527d3dd79..cbdf59367 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementUnboundRelationshipSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/ElementUnboundRelationshipSerializerTests.cs @@ -42,6 +42,7 @@ public void ShouldDeserialize() { { "prop3", true } }); + writer.Write("r1"); var readerMachine = CreateReaderMachine(writerMachine.GetOutput()); @@ -64,6 +65,7 @@ public void ShouldDeserializeSpan() { { "prop3", true } }); + writer.Write("r1"); var readerMachine = CreateSpanReader(writerMachine.GetOutput()); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/NodeSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/NodeSerializerTests.cs index ab3c2741d..936c29a1d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/NodeSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/NodeSerializerTests.cs @@ -116,7 +116,7 @@ private static void VerifySerializedNode(object value) new KeyValuePair("prop3", true) }); } - + [Fact] public void ShouldDeserializeSpan() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PathSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PathSerializerTests.cs index 947775e7e..beac23db8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PathSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PathSerializerTests.cs @@ -59,7 +59,7 @@ public void ShouldDeserializeSpan() VerifySerializedPath(value); } - + [Fact] public void ShouldDeserializeReverse() { @@ -84,7 +84,7 @@ public void ShouldDeserializeSpanReverse() var reader = CreateSpanReader(writerMachine.GetOutput()); var value = reader.Read(); - + VerifySerializedPathReverse(value); } @@ -123,7 +123,7 @@ public void ShouldDeserializeSpanWhenInList() VerifySerializedPath(value.Should().BeAssignableTo().Which[0]); } - + [Fact] public void ShouldDeserializeWhenInMap() { @@ -171,7 +171,7 @@ public void ShouldDeserializeSpanWhenInMap() VerifySerializedPath(value.Should().BeAssignableTo().Which["x"]); } - + private static void SerializePath(PackStreamWriter writer, bool reverse = false) { writer.WriteStructHeader(3, PathSerializer.Path); @@ -282,4 +282,4 @@ private static void VerifySerializedUnboundedRelationship( .And .Contain(new KeyValuePair("rProp1", $"something{expectedId}")); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PointSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PointSerializerTests.cs index 683ab1b18..0f5afb53e 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PointSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/PointSerializerTests.cs @@ -117,7 +117,6 @@ public void ShouldDeserializeSpanPoint2D() ValidatePoint2D(value); } - [Fact] public void ShouldDeserializeSpanPoint3D() { @@ -144,7 +143,7 @@ private static void ValidatePoint2D(object value) value.Should().BeOfType().Which.Y.Should().Be(-0.105658); value.Should().BeOfType().Which.Z.Should().Be(double.NaN); } - + private static void ValidatePoint3D(object value) { value.Should().NotBeNull(); @@ -153,4 +152,4 @@ private static void ValidatePoint3D(object value) value.Should().BeOfType().Which.Y.Should().Be(-0.105658); value.Should().BeOfType().Which.Z.Should().Be(35.25); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/RelationshipSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/RelationshipSerializerTests.cs index ad372b821..9f7c7ac74 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/RelationshipSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/RelationshipSerializerTests.cs @@ -140,7 +140,7 @@ public void ShouldDeserializeSpanWhenInMap() VerifySerializedRelationship(value.Should().BeAssignableTo().Which["x"]); } - + private static void SerializeRelationship(PackStreamWriter writer) { writer.WriteStructHeader(5, RelationshipSerializer.Relationship); @@ -176,4 +176,4 @@ private static void VerifySerializedRelationship(object value) new KeyValuePair("prop3", false) }); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/DurationSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/DurationSerializerTests.cs index 41e0f3f0a..0227c9d95 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/DurationSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/DurationSerializerTests.cs @@ -89,4 +89,4 @@ private static void Validate(object value) value.Should().BeOfType().Which.Seconds.Should().Be(564L); value.Should().BeOfType().Which.Nanos.Should().Be(865); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializerTests.cs index 5c8d72e2f..c99060fd5 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializerTests.cs @@ -89,4 +89,4 @@ public void ShouldDeserializeSpanDateTime() value.Should().BeOfType().Which.Second.Should().Be(59); value.Should().BeOfType().Which.Nanosecond.Should().Be(128000987); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializerTests.cs index ae01e4017..0f0fa8f4c 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializerTests.cs @@ -89,5 +89,4 @@ private static void Validate(object value) .Which.OffsetSeconds.Should() .Be((int)TimeSpan.FromMinutes(150).TotalSeconds); } - } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/SystemDateTimeSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/SystemDateTimeSerializerTests.cs index 1888bed53..2eb3948d4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/SystemDateTimeSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/SystemDateTimeSerializerTests.cs @@ -88,4 +88,4 @@ public void ShouldSerializeDateTimeUtc() reader.Read().Should().Be(999000000L); reader.Read().Should().Be(0L); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializerTests.cs index 899780eb9..8c90b62b4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializerTests.cs @@ -377,4 +377,4 @@ public void ShouldSerializeUtcAndNonUtcSecondsToSameValue() bstSecs.Should().Equals(utcSecs); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/UnboundRelationshipSerializerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/UnboundRelationshipSerializerTests.cs index 422ce94f0..1bb04ad95 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/UnboundRelationshipSerializerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/IO/ValueSerializers/UnboundRelationshipSerializerTests.cs @@ -147,7 +147,7 @@ public void ShouldDeserializeSpanWhenInMap() VerifySerializedUnboundRelationship(value.Should().BeAssignableTo().Which["x"]); } - + private static void SerializeUnboundRelationship(PackStreamWriter writer) { writer.WriteStructHeader(3, UnboundRelationshipSerializer.UnboundRelationship); @@ -181,4 +181,4 @@ private static void VerifySerializedUnboundRelationship(object value) new KeyValuePair("prop3", false) }); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/InMemoryPipelinedMessageReaderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/InMemoryPipelinedMessageReaderTests.cs index e1e9025f5..9a916b298 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/InMemoryPipelinedMessageReaderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/InMemoryPipelinedMessageReaderTests.cs @@ -1,7 +1,5 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. +// Neo4j Sweden AB [https://neo4j.com] // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -62,8 +60,11 @@ public async Task ShouldReadMessage() var pipereader = new PipelinedMessageReader(memoryStream, TestDriverContext.MockContext, -1); var pipeline = MockPipeline(); - await pipereader.ReadAsync(pipeline.Object, new MessageFormat(BoltProtocolVersion.V5_0, - TestDriverContext.MockContext)); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat( + BoltProtocolVersion.V5_0, + TestDriverContext.MockContext)); pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Once); } @@ -86,8 +87,11 @@ public async Task ShouldReadMultiChunkMessage() memoryStream.Position = 0L; var pipereader = new PipelinedMessageReader(memoryStream, TestDriverContext.MockContext, -1); var pipeline = MockPipeline(); - await pipereader.ReadAsync(pipeline.Object, new MessageFormat(BoltProtocolVersion.V5_0, - TestDriverContext.MockContext)); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat( + BoltProtocolVersion.V5_0, + TestDriverContext.MockContext)); pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Once); } @@ -111,8 +115,11 @@ public async Task ShouldIgnoreNoOpChunk() memoryStream.Position = 0L; var pipereader = new PipelinedMessageReader(memoryStream, TestDriverContext.MockContext, -1); var pipeline = MockPipeline(); - await pipereader.ReadAsync(pipeline.Object, new MessageFormat(BoltProtocolVersion.V5_0, - TestDriverContext.MockContext)); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat( + BoltProtocolVersion.V5_0, + TestDriverContext.MockContext)); pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Once); } @@ -144,8 +151,11 @@ public async Task ShouldIgnoreAllNoOpChunks() memoryStream.Position = 0L; var pipereader = new PipelinedMessageReader(memoryStream, TestDriverContext.MockContext, -1); var pipeline = MockPipeline(); - await pipereader.ReadAsync(pipeline.Object, new MessageFormat(BoltProtocolVersion.V5_0, - TestDriverContext.MockContext)); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat( + BoltProtocolVersion.V5_0, + TestDriverContext.MockContext)); pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Once); } @@ -163,7 +173,7 @@ public async Task ShouldStopReadAllMessagesAfterAFailure() // 5 bytes 0x00, 0x00, 0x00, 0x00, // 9 bytes - PackStream.String8, 1, Encoding.UTF8.GetBytes("a")[0], PackStream.String8, 7, + PackStream.String8, 1, Encoding.UTF8.GetBytes("a")[0], PackStream.String8, 7, // 14 bytes 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 21 bytes @@ -175,14 +185,19 @@ public async Task ShouldStopReadAllMessagesAfterAFailure() PackStream.TinyStruct, MessageFormat.MsgIgnored, 0x00, 0x00 }; + Encoding.UTF8.GetBytes("code").CopyTo(message.AsSpan().Slice(2 + 5)); Encoding.UTF8.GetBytes("message").CopyTo(message.AsSpan().Slice(2 + 14)); - + var memoryStream = new MemoryStream(message); var pipereader = new PipelinedMessageReader(memoryStream, TestDriverContext.MockContext, -1); var pipeline = MockPipeline(); - await pipereader.ReadAsync(pipeline.Object, new MessageFormat(BoltProtocolVersion.V5_0, - TestDriverContext.MockContext)); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat( + BoltProtocolVersion.V5_0, + TestDriverContext.MockContext)); + pipeline.Verify(x => x.OnFailure(It.IsAny()), Times.Once); pipeline.Verify(x => x.OnIgnored(), Times.Never); } @@ -195,8 +210,11 @@ public async Task ShouldReadLargeMessages() var pipereader = new PipelinedMessageReader(memoryStream, TestDriverContext.MockContext, -1); var pipeline = MockPipeline(); - await pipereader.ReadAsync(pipeline.Object, new MessageFormat(BoltProtocolVersion.V5_0, - TestDriverContext.MockContext)); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat( + BoltProtocolVersion.V5_0, + TestDriverContext.MockContext)); pipeline.Verify( x => x.OnSuccess(It.Is>(y => y["a"].As().Length == 500_000)), diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BeginMessageTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BeginMessageTests.cs index 67e059879..91e4c0e91 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BeginMessageTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BeginMessageTests.cs @@ -136,7 +136,11 @@ public void ShouldHandleSetValuesWithNotifications(int major, int minor) message.Metadata.Should().ContainKey("tx_metadata").WhichValue.Should().BeEquivalentTo(txMeta); message.Metadata.Should().ContainKey("mode").WhichValue.Should().BeEquivalentTo("r"); message.Metadata.Should().ContainKey("db").WhichValue.Should().BeEquivalentTo("neo4j"); - message.Metadata.Should().ContainKey("notifications_minimum_severity").WhichValue.Should().BeEquivalentTo("WARNING"); + message.Metadata.Should() + .ContainKey("notifications_minimum_severity") + .WhichValue.Should() + .BeEquivalentTo("WARNING"); + message.Metadata.Should() .ContainKey("notifications_disabled_categories") .WhichValue.Should() @@ -189,7 +193,7 @@ public void ShouldHandleSetValuesWithNotificationsClassifications(int major, int .Be( "BEGIN [{bookmarks, [bm:a]}, {tx_timeout, 1000}, {tx_metadata, [{a, b}]}, {mode, r}, {db, neo4j}, {imp_user, Douglas Fir}, {notifications_minimum_severity, WARNING}, {notifications_disabled_classifications, [GENERIC]}]"); } - + [Fact] public void ShouldThrowIfBoltVersionLessThan44() { @@ -213,7 +217,6 @@ public void ShouldThrowIfBoltVersionLessThan44() .BeOfType(); } - [Theory] [InlineData(5, 2)] [InlineData(5, 3)] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BoltAgentBuilderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BoltAgentBuilderTests.cs index e8c6efacf..952bafd2e 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BoltAgentBuilderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/BoltAgentBuilderTests.cs @@ -33,4 +33,4 @@ public void ShouldReturnBoltAgent() .WhichValue.Should() .MatchRegex(@"^neo4j-dotnet/\d\.\d+\.\d+$"); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/DiscardMessageTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/DiscardMessageTests.cs index c3f4dcae3..4126f113d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/DiscardMessageTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/DiscardMessageTests.cs @@ -42,4 +42,4 @@ public void ShouldHandleValues() var message = new DiscardMessage(42, 10); message.ToString().Should().Be("DISCARD [{n, 10}, {qid, 42}]"); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/RunWithMetaDataMessageTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/RunWithMetaDataMessageTests.cs index a2463ba78..e9c0ccd3c 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/RunWithMetaDataMessageTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/RunWithMetaDataMessageTests.cs @@ -53,6 +53,7 @@ public void ShouldIncludeImpersonatedUserKeyWithBoltVersionGreaterThan44(int maj new BoltProtocolVersion(major, minor), null, sessionConfig: new SessionConfig("jeff")); + rm.Query.Should().BeNull(); rm.Metadata.Should().ContainKey("imp_user").WhichValue.Should().Be("jeff"); @@ -95,7 +96,6 @@ public void ShouldIncludeValues() "RUN `...`, [] [{bookmarks, [bm:a]}, {tx_timeout, 1000}, {tx_metadata, [{a, b}]}, {mode, r}, {db, neo4j}, {imp_user, jeff}]"); } - [Theory] [InlineData(5, 2)] [InlineData(5, 3)] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollectorTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollectorTests.cs index faeb3c7c6..00b1bc4d4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollectorTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollectorTests.cs @@ -1,4 +1,19 @@ -#pragma warning disable CS0618 // Type or member is obsolete +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. + +#pragma warning disable CS0618 // Type or member is obsolete // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] // @@ -123,6 +138,7 @@ public void ShouldCollectGqlStatuses() ["column"] = 3L } }; + first.GqlStatus.Should().Be("000000"); first.StatusDescription.Should().Be("it is a status"); first.Position.Should().Be(new InputPosition(1, 2, 3)); @@ -136,7 +152,7 @@ public void ShouldCollectGqlStatuses() first.Title.Should().Be("blah"); }); } - + [Fact] public void ShouldCollectMinimalGqlStatuses() { @@ -165,8 +181,9 @@ public void ShouldCollectMinimalGqlStatuses() { ["CURRENT_SCHEMA"] = "/", ["OPERATION"] = "", - ["OPERATION_CODE"] = "0", + ["OPERATION_CODE"] = "0" }; + first.GqlStatus.Should().Be("030000"); first.StatusDescription.Should().Be("it is a status"); first.Position.Should().BeNull(); @@ -196,8 +213,8 @@ public void ShouldMergeDiagnosticRecord() ["diagnostic_record"] = new Dictionary { ["Example"] = "blah-de-blah", - ["OPERATION"] = "OP!", - }, + ["OPERATION"] = "OP!" + } } } }); @@ -214,13 +231,13 @@ public void ShouldMergeDiagnosticRecord() ["Example"] = "blah-de-blah", ["CURRENT_SCHEMA"] = "/", ["OPERATION"] = "OP!", - ["OPERATION_CODE"] = "0", + ["OPERATION_CODE"] = "0" }; first.DiagnosticRecord.Should().BeEquivalentTo(dict); }); } - + [Fact] public void ShouldPolyfilGqlStatusesIntoNotifications() { @@ -366,9 +383,7 @@ public void ShouldCollectMinimalNotification() { ["notifications"] = new List { - new Dictionary - { - } + new Dictionary() } }); @@ -413,8 +428,9 @@ public void ShouldUpgradeMinimalNotificationToGqlStatus() { ["CURRENT_SCHEMA"] = "/", ["OPERATION"] = "", - ["OPERATION_CODE"] = "0", + ["OPERATION_CODE"] = "0" }; + var first = x.Should().BeOfType().Which; first.GqlStatus.Should().Be("03N42"); first.Title.Should().BeNull(); @@ -462,16 +478,17 @@ public void ShouldUpgradeNotificationToGqlStatus() var dict = new Dictionary { ["_severity"] = "WARNING", - ["_position"] = new Dictionary - { - ["offset"] = 1L, - ["line"] = 2L, - ["column"] = 3L - }, + ["_position"] = new Dictionary + { + ["offset"] = 1L, + ["line"] = 2L, + ["column"] = 3L + }, ["CURRENT_SCHEMA"] = "/", ["OPERATION"] = "", - ["OPERATION_CODE"] = "0", + ["OPERATION_CODE"] = "0" }; + var first = x.Should().BeOfType().Which; first.GqlStatus.Should().Be("01N42"); first.Title.Should().Be("blah"); @@ -484,7 +501,7 @@ public void ShouldUpgradeNotificationToGqlStatus() first.DiagnosticRecord.Should().BeEquivalentTo(dict); }); } - + [Fact] public void ShouldNotPolyfilWhenProvidedGqlStatus() { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsTests.cs index 6ed69f0e5..536386325 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsTests.cs @@ -57,7 +57,7 @@ public void FinalizeStatusObjectsShouldPolyfil(bool hadRecords, bool hadKeys, IG { ["CURRENT_SCHEMA"] = "/", ["OPERATION"] = "", - ["OPERATION_CODE"] = "0", + ["OPERATION_CODE"] = "0" }; first.GqlStatus.Should().Be(exp.GqlStatus); @@ -87,16 +87,16 @@ public void ShouldOrderByCodeWhenPolyfilling() { ["CURRENT_SCHEMA"] = "/", ["OPERATION"] = "", - ["OPERATION_CODE"] = "0", + ["OPERATION_CODE"] = "0" }; - var statuses = new List() + var statuses = new List { new GqlStatusObject("04-", "", null, null, null, dict, null, false), new GqlStatusObject("01-", "", null, null, null, dict, null, false), new GqlStatusObject("03-", "", null, null, null, dict, null, false), new GqlStatusObject("02-", "", null, null, null, dict, null, false), - new GqlStatusObject("1", "", null, null, null, dict, null, false), + new GqlStatusObject("1", "", null, null, null, dict, null, false) }; var objs = new GqlStatusObjectsAndNotifications(null, statuses, false); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/NetworkedPipelinedMessageReaderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/NetworkedPipelinedMessageReaderTests.cs index 8657f1c8c..59a38ee12 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/NetworkedPipelinedMessageReaderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/NetworkedPipelinedMessageReaderTests.cs @@ -1,7 +1,5 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. +// Neo4j Sweden AB [https://neo4j.com] // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -32,70 +30,71 @@ namespace Neo4j.Driver.Tests.Internal.MessageHandling; /// /// These are close to integration tests as they used tcp, but they are not integration tests as they do not -/// integrate against another process. +/// integrate against another process. /// public class NetworkedPipelinedMessageReaderTests { - const int Port = 9111; - + private const int Port = 9111; + [Fact] public async Task ShouldReadSimpleMessage() { var pipeline = MockPipeline(); using var cts = new CancellationTokenSource(5000); - + await Task.WhenAll( - Task.Run(async () => - { - var tcp = new TcpListener(IPAddress.Loopback, Port); - TcpClient stream = null; - try + Task.Run( + async () => { - tcp.Start(); - stream = await tcp.AcceptTcpClientAsync(cts.Token); - stream.GetStream() - .Write( - new byte[] - { - 0x00, 0x03, - PackStream.TinyStruct, MessageFormat.MsgSuccess, PackStream.TinyMap, - 0x00, 0x00 - }.AsSpan()); - + var tcp = new TcpListener(IPAddress.Loopback, Port); + TcpClient stream = null; try { - await Task.Delay(1000, cts.Token); + tcp.Start(); + stream = await tcp.AcceptTcpClientAsync(cts.Token); + stream.GetStream() + .Write( + new byte[] + { + 0x00, 0x03, + PackStream.TinyStruct, MessageFormat.MsgSuccess, PackStream.TinyMap, + 0x00, 0x00 + }.AsSpan()); + + try + { + await Task.Delay(1000, cts.Token); + } + catch (OperationCanceledException) + { + } } - catch (OperationCanceledException) + finally { + stream?.Close(); + tcp.Stop(); } - } - finally - { - stream?.Close(); - tcp.Stop(); - } - }), - Task.Run(async () => - { - await Task.Delay(100); - var client = new TcpClient(); - try + }), + Task.Run( + async () => { - await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); - var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); - await pipereader.ReadAsync( - pipeline.Object, - new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); - } - finally - { - client.Close(); - cts.Cancel(); + await Task.Delay(100); + var client = new TcpClient(); + try + { + await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); + var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); + } + finally + { + client.Close(); + cts.Cancel(); + } + })); - } - })); - pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Once); } @@ -104,286 +103,289 @@ public async Task ShouldTimeoutWaitingForSize() { var pipeline = MockPipeline(); using var cts = new CancellationTokenSource(5000); - + await Task.WhenAll( - Task.Run(async () => - { - var tcp = new TcpListener(IPAddress.Loopback, Port); - TcpClient stream = null; - try + Task.Run( + async () => { - tcp.Start(); - stream = await tcp.AcceptTcpClientAsync(cts.Token); - stream.GetStream() - .Write( - new byte[] - { - 0x00 - }.AsSpan()); + var tcp = new TcpListener(IPAddress.Loopback, Port); + TcpClient stream = null; try { - await Task.Delay(-1, cts.Token); + tcp.Start(); + stream = await tcp.AcceptTcpClientAsync(cts.Token); + stream.GetStream() + .Write( + new byte[] + { + 0x00 + }.AsSpan()); + + try + { + await Task.Delay(-1, cts.Token); + } + catch (OperationCanceledException) + { + } } - catch (OperationCanceledException) + finally { + stream?.Close(); + tcp.Stop(); } - } - finally + }), + Task.Run( + async () => { - stream?.Close(); - tcp.Stop(); - } - }), - Task.Run(async () => - { - - await Task.Delay(100); - var client = new TcpClient(); - try - { - await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); - var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); - pipereader.SetReadTimeoutInMs(250); - var exc = await Record.ExceptionAsync( - async () => - { - await pipereader.ReadAsync( - pipeline.Object, - new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); - }); - - exc.Should().BeOfType(); - } - finally - { - client.Close(); - cts.Cancel(); + await Task.Delay(100); + var client = new TcpClient(); + try + { + await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); + var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); + pipereader.SetReadTimeoutInMs(250); + var exc = await Record.ExceptionAsync( + async () => + { + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); + }); + + exc.Should().BeOfType(); + } + finally + { + client.Close(); + cts.Cancel(); + } + })); - } - })); - pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Never); } - + [Fact] public async Task ShouldReadSimpleMessageWithDelays() { var pipeline = MockPipeline(); using var cts = new CancellationTokenSource(5000); - + await Task.WhenAll( - Task.Run(async () => - { - var tcp = new TcpListener(IPAddress.Loopback, Port); - TcpClient stream = null; - try + Task.Run( + async () => { - tcp.Start(); - stream = await tcp.AcceptTcpClientAsync(cts.Token); - stream.GetStream() - .Write( - new byte[] - { - 0x00 - }.AsSpan()); + var tcp = new TcpListener(IPAddress.Loopback, Port); + TcpClient stream = null; + try + { + tcp.Start(); + stream = await tcp.AcceptTcpClientAsync(cts.Token); + stream.GetStream() + .Write( + new byte[] + { + 0x00 + }.AsSpan()); - await Task.Delay(500); - - stream.GetStream() - .Write( - new byte[] - { - 0x03, - PackStream.TinyStruct, MessageFormat.MsgSuccess, PackStream.TinyMap, - }.AsSpan()); + await Task.Delay(500); - await Task.Delay(500); - stream.GetStream() - .Write( - new byte[] - { - 0x00, 0x00 - }.AsSpan()); + stream.GetStream() + .Write( + new byte[] + { + 0x03, + PackStream.TinyStruct, MessageFormat.MsgSuccess, PackStream.TinyMap + }.AsSpan()); + + await Task.Delay(500); + stream.GetStream() + .Write( + new byte[] + { + 0x00, 0x00 + }.AsSpan()); + + try + { + await Task.Delay(1000, cts.Token); + } + catch (OperationCanceledException) + { + } + } + finally + { + stream?.Close(); + tcp.Stop(); + } + }), + Task.Run( + async () => + { + await Task.Delay(100); + var client = new TcpClient(); try { - await Task.Delay(1000, cts.Token); + await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); + var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); + pipereader.SetReadTimeoutInMs(1000); + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); } - catch (OperationCanceledException) + finally { + client.Close(); + cts.Cancel(); } - } - finally - { - stream?.Close(); - tcp.Stop(); - } - }), - Task.Run(async () => - { - await Task.Delay(100); - var client = new TcpClient(); - try - { - await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); - var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); - pipereader.SetReadTimeoutInMs(1000); - await pipereader.ReadAsync( - pipeline.Object, - new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); - } - finally - { - client.Close(); - cts.Cancel(); + })); - } - })); - pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Once); } - + [Fact] public async Task ShouldTimeoutWaitingAfterSize() { var pipeline = MockPipeline(); using var cts = new CancellationTokenSource(5000); - + await Task.WhenAll( - Task.Run(async () => - { - var tcp = new TcpListener(IPAddress.Loopback, Port); - TcpClient stream = null; - try + Task.Run( + async () => { - tcp.Start(); - stream = await tcp.AcceptTcpClientAsync(cts.Token); - stream.GetStream() - .Write( - new byte[] - { - 0x00,0x03 - }.AsSpan()); - + var tcp = new TcpListener(IPAddress.Loopback, Port); + TcpClient stream = null; try { - await Task.Delay(-1, cts.Token); + tcp.Start(); + stream = await tcp.AcceptTcpClientAsync(cts.Token); + stream.GetStream() + .Write( + new byte[] + { + 0x00, 0x03 + }.AsSpan()); + + try + { + await Task.Delay(-1, cts.Token); + } + catch (OperationCanceledException) + { + } } - catch (OperationCanceledException) + finally { + stream?.Close(); + tcp.Stop(); } - } - finally - { - stream?.Close(); - tcp.Stop(); - } - }), - Task.Run(async () => - { - - await Task.Delay(100); - var client = new TcpClient(); - try + }), + Task.Run( + async () => { - await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); - var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); - pipereader.SetReadTimeoutInMs(2000); - var exc = await Record.ExceptionAsync( - async () => - { - await pipereader.ReadAsync( - pipeline.Object, - new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); - }); - - exc.Should().BeOfType(); - } - finally - { - client.Close(); - cts.Cancel(); + await Task.Delay(100); + var client = new TcpClient(); + try + { + await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); + var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); + pipereader.SetReadTimeoutInMs(2000); + var exc = await Record.ExceptionAsync( + async () => + { + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); + }); + + exc.Should().BeOfType(); + } + finally + { + client.Close(); + cts.Cancel(); + } + })); - } - })); - pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Never); } - + [Fact] public async Task ShouldTimeoutWaitingForData() { var pipeline = MockPipeline(); using var cts = new CancellationTokenSource(5000); - + await Task.WhenAll( - Task.Run(async () => - { - var tcp = new TcpListener(IPAddress.Loopback, Port); - TcpClient stream = null; - try + Task.Run( + async () => { - tcp.Start(); - stream = await tcp.AcceptTcpClientAsync(cts.Token); - stream.GetStream() - .Write( - new byte[] - { - 0x00,0x03 - }.AsSpan()); - - await Task.Delay(1000); - stream.GetStream() - .Write( - new byte[] - { - 0x00 - }.AsSpan()); - + var tcp = new TcpListener(IPAddress.Loopback, Port); + TcpClient stream = null; try { - await Task.Delay(-1, cts.Token); + tcp.Start(); + stream = await tcp.AcceptTcpClientAsync(cts.Token); + stream.GetStream() + .Write( + new byte[] + { + 0x00, 0x03 + }.AsSpan()); + + await Task.Delay(1000); + stream.GetStream() + .Write( + new byte[] + { + 0x00 + }.AsSpan()); + + try + { + await Task.Delay(-1, cts.Token); + } + catch (OperationCanceledException) + { + } } - catch (OperationCanceledException) + finally { + stream?.Close(); + tcp.Stop(); } - } - finally - { - stream?.Close(); - tcp.Stop(); - } - }), - Task.Run(async () => - { - - await Task.Delay(100); - var client = new TcpClient(); - try + }), + Task.Run( + async () => { - await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); - var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); - pipereader.SetReadTimeoutInMs(2000); - var exc = await Record.ExceptionAsync( - async () => - { - await pipereader.ReadAsync( - pipeline.Object, - new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); - }); - - exc.Should().BeOfType(); - } - finally - { - client.Close(); - cts.Cancel(); + await Task.Delay(100); + var client = new TcpClient(); + try + { + await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token); + var pipereader = new PipelinedMessageReader(client.GetStream(), TestDriverContext.MockContext); + pipereader.SetReadTimeoutInMs(2000); + var exc = await Record.ExceptionAsync( + async () => + { + await pipereader.ReadAsync( + pipeline.Object, + new MessageFormat(BoltProtocolVersion.V5_0, TestDriverContext.MockContext)); + }); + + exc.Should().BeOfType(); + } + finally + { + client.Close(); + cts.Cancel(); + } + })); - } - })); - pipeline.Verify(x => x.OnSuccess(It.IsAny>()), Times.Never); } - + private static Mock MockPipeline() { var done = (object)false; diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolTests.cs index bf50c245d..0a580b509 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolTests.cs @@ -21,6 +21,7 @@ using Moq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.MessageHandling.V4; using Neo4j.Driver.Internal.Messaging; @@ -159,7 +160,12 @@ public class GetRoutingTableAsyncTests public async Task ShouldThrowAnExceptionWhenNullConnection() { var exception = await Record.ExceptionAsync( - () => BoltProtocol.Instance.GetRoutingTableAsync(null, null, new SessionConfig("douglas fir"), null)); + () => BoltProtocol.Instance.GetRoutingTableAsync( + null, + null, + new SessionConfig("douglas fir"), + null, + new Mock().Object)); exception.Should().BeOfType(); } @@ -179,7 +185,8 @@ public async Task ShouldThrowWhenUsingImpersonatedUserWithBoltVersionLessThan44( mockConn.Object, null, new SessionConfig("douglas fir"), - null)); + null, + new Mock().Object)); exception.Should().BeOfType(); } @@ -199,7 +206,8 @@ public async Task ShouldNotThrowWhenImpersonatingUserWithBoltVersionGreaterThan4 mockConn.Object, null, new SessionConfig("douglas fir"), - null)); + null, + new Mock().Object)); exception.Should().BeNull(); } @@ -223,8 +231,10 @@ public async Task ShouldSyncRouteMessageWhenUsingBoltVersionGreaterThan43(int ma .Returns(new RouteMessage(null, null, null, null)); var handlerFactory = new Mock(); - handlerFactory.Setup(x => x.NewRouteResponseHandler()) - .Returns(new RouteResponseHandler()); + handlerFactory.Setup( + x => x.NewRouteResponseHandler(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns(new RouteResponseHandler(HomeDbCacheKey.Default, null, null, false)); var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); @@ -233,7 +243,8 @@ await protocol.GetRoutingTableAsync( mockConn.Object, "db", new SessionConfig("dougy"), - new InternalBookmarks()); + new InternalBookmarks(), + new Mock().Object); msgFactory.Verify( x => x.NewRouteMessage( @@ -243,7 +254,13 @@ await protocol.GetRoutingTableAsync( "dougy"), Times.Once); - handlerFactory.Verify(x => x.NewRouteResponseHandler(), Times.Once); + handlerFactory.Verify( + x => x.NewRouteResponseHandler( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Once); mockConn.Verify( x => x.EnqueueAsync(It.IsNotNull(), It.IsNotNull()), @@ -268,16 +285,21 @@ public async Task ShouldSyncRouteMessageV43WhenUsingBoltVersion43() .Setup(x => x.NewRouteMessageV43(mockConn.Object, It.IsNotNull(), "db")) .Returns(new RouteMessageV43(null, null, null)); - var rrHandler = new RouteResponseHandler(); + var rrHandler = new RouteResponseHandler(HomeDbCacheKey.Default, new HomeDbCache(), SessionConfig.Default, false); rrHandler.RoutingInformation = new Dictionary(); var handlerFactory = new Mock(); - handlerFactory.Setup(x => x.NewRouteResponseHandler()) + handlerFactory.Setup(x => x.NewRouteResponseHandler(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(rrHandler); var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); - await protocol.GetRoutingTableAsync(mockConn.Object, "db", null, new InternalBookmarks()); + await protocol.GetRoutingTableAsync( + mockConn.Object, + "db", + SessionConfig.Default, + new InternalBookmarks(), + new HomeDbCache()); msgFactory.Verify( x => x.NewRouteMessageV43( @@ -286,7 +308,13 @@ public async Task ShouldSyncRouteMessageV43WhenUsingBoltVersion43() "db"), Times.Once); - handlerFactory.Verify(x => x.NewRouteResponseHandler(), Times.Once); + handlerFactory.Verify( + x => x.NewRouteResponseHandler( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Once); mockConn.Verify( x => x.EnqueueAsync(It.IsNotNull(), It.IsNotNull()), @@ -313,16 +341,31 @@ public async Task ShouldUpdate43RoutingTableResultWithDbKey() var mockRt = new Mock>(); mockRt.As>(); - var rrHandler = new RouteResponseHandler(); + var rrHandler = new RouteResponseHandler( + HomeDbCacheKey.Default, + new HomeDbCache(), + SessionConfig.Default, + false); + rrHandler.RoutingInformation = mockRt.Object; var handlerFactory = new Mock(); - handlerFactory.Setup(x => x.NewRouteResponseHandler()) + handlerFactory.Setup( + x => x.NewRouteResponseHandler( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .Returns(rrHandler); var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); - await protocol.GetRoutingTableAsync(mockConn.Object, "test", null, new InternalBookmarks()); + await protocol.GetRoutingTableAsync( + mockConn.Object, + "test", + null, + new InternalBookmarks(), + new Mock().Object); mockRt.Verify(x => x.Add("db", "test"), Times.Once); @@ -333,9 +376,18 @@ public async Task ShouldUpdate43RoutingTableResultWithDbKey() "test"), Times.Once); - handlerFactory.Verify(x => x.NewRouteResponseHandler(), Times.Once); handlerFactory.Verify( - x => x.NewRunResponseHandler(It.IsAny(), It.IsAny()), + x => x.NewRouteResponseHandler(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); + + handlerFactory.Verify( + x => x.NewRunResponseHandler( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); mockConn.Verify( @@ -417,11 +469,14 @@ public async Task ShouldUseQueryToFetchRoutingTableForBoltVersionLessThan43(int mockConn.Object, "test", null, - bm); + bm, + It.IsAny()); routingTable.Should().Contain(new KeyValuePair("db", "test")); - handlerFactory.Verify(x => x.NewRouteResponseHandler(), Times.Never); + handlerFactory.Verify( + x => x.NewRouteResponseHandler(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); msgFactory.Verify( x => @@ -510,7 +565,8 @@ await protocol.GetRoutingTableAsync( mockConn.Object, dbName, null, - null); + null, + It.IsAny()); queryParams.Should().NotBeNull(); queryParams.Query.Parameters.Should().HaveCount(2).And.ContainKeys("context", "database"); @@ -526,19 +582,24 @@ public async Task ShouldProtectAgainstEmptyDatabaseStringWhenUsingRouteMessageV4 mockConn.SetupGet(x => x.Version).Returns(new BoltProtocolVersion(4, 3)); var msgFactory = new Mock(); - var rrHandler = new RouteResponseHandler + var rrHandler = new RouteResponseHandler(HomeDbCacheKey.Default, new HomeDbCache(), SessionConfig.Default, false) { RoutingInformation = new Dictionary() }; var handlerFactory = new Mock(); - handlerFactory.Setup(x => x.NewRouteResponseHandler()) + handlerFactory.Setup(x => x.NewRouteResponseHandler(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(rrHandler); var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); - await protocol.GetRoutingTableAsync(mockConn.Object, dbName, null, null); + await protocol.GetRoutingTableAsync( + mockConn.Object, + dbName, + null, + null, + new HomeDbCache()); msgFactory.Verify( x => x.NewRouteMessageV43( @@ -558,13 +619,18 @@ public async Task ShouldProtectAgainstEmptyDatabaseStringWhenUsingRouteMessage(s var msgFactory = new Mock(); var handlerFactory = new Mock(); - handlerFactory.Setup(x => x.NewRouteResponseHandler()) - .Returns(new RouteResponseHandler()); + handlerFactory.Setup(x => x.NewRouteResponseHandler(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new RouteResponseHandler(HomeDbCacheKey.Default, new HomeDbCache(), SessionConfig.Default, false)); var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); - await protocol.GetRoutingTableAsync(mockConn.Object, dbName, null, null); + await protocol.GetRoutingTableAsync( + mockConn.Object, + dbName, + null, + null, + It.IsAny()); msgFactory.Verify( x => x.NewRouteMessage( @@ -596,7 +662,11 @@ public async Task ShouldThrowWhenUsingImpersonatedUserWithBoltVersionLessThan44( mockConn.SetupGet(x => x.SessionConfig).Returns(new SessionConfig("Douglas Fir")); var exception = await Record.ExceptionAsync( - () => BoltProtocol.Instance.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null)); + () => BoltProtocol.Instance.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny())); exception.Should().BeOfType(); } @@ -618,7 +688,11 @@ public async Task ShouldNotThrowWhenImpersonatingUserWithBoltVersionGreaterThan4 }; var exception = await Record.ExceptionAsync( - () => BoltProtocol.Instance.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null)); + () => BoltProtocol.Instance.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny())); exception.Should().BeNull(); } @@ -641,7 +715,8 @@ public async Task ShouldThrowWhenNotificationsWithBoltVersionLessThan52(int majo () => BoltProtocol.Instance.RunInAutoCommitTransactionAsync( mockConn.Object, acp, - new NotificationsDisabledConfig())); + new NotificationsDisabledConfig(), + It.IsAny())); exception.Should().BeOfType(); } @@ -664,7 +739,8 @@ public async Task ShouldNotThrowWhenNotificationsWithBoltVersionGreaterThan51(in () => BoltProtocol.Instance.RunInAutoCommitTransactionAsync( mockConn.Object, acp, - new NotificationsDisabledConfig())); + new NotificationsDisabledConfig(), + It.IsAny())); exception.Should().BeNull(); } @@ -701,11 +777,16 @@ public async Task ShouldSendPullMessageWhenNotReactive() handlerFactory.Setup( x => x.NewRunResponseHandler( resultCursorBuilderMock.Object, - It.IsNotNull())) + It.IsNotNull(), + HomeDbCacheKey.Default, + It.IsAny(), + It.IsAny(), It.IsAny())) .Returns( new RunResponseHandler( resultCursorBuilderMock.Object, - new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))))); + new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))), + HomeDbCacheKey.Default, + null, SessionConfig.Default, false)); handlerFactory .Setup( @@ -718,7 +799,8 @@ public async Task ShouldSendPullMessageWhenNotReactive() new PullResponseHandler( resultCursorBuilderMock.Object, new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))), - mockBt.Object, true)); + mockBt.Object, + true)); handlerFactory.Setup( x => x.NewResultCursorBuilder( @@ -737,7 +819,11 @@ public async Task ShouldSendPullMessageWhenNotReactive() var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); - await protocol.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null); + await protocol.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny()); msgFactory.Verify( x => x.NewRunWithMetadataMessage( @@ -762,13 +848,19 @@ public async Task ShouldSendPullMessageWhenNotReactive() Times.Once); handlerFactory.Verify( - x => x.NewRunResponseHandler(resultCursorBuilderMock.Object, It.IsNotNull()), + x => x.NewRunResponseHandler( + resultCursorBuilderMock.Object, + It.IsNotNull(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); mockConn.Verify( x => x.EnqueueAsync( It.IsNotNull(), - It.IsNotNull(), + It.IsAny(), It.IsNotNull(), It.IsNotNull()), Times.Exactly(1)); @@ -806,11 +898,16 @@ public async Task ShouldSendOnlyRunWhenReactive() handlerFactory.Setup( x => x.NewRunResponseHandler( resultCursorBuilderMock.Object, - It.IsNotNull())) + It.IsNotNull(), + HomeDbCacheKey.Default, + It.IsAny(), + It.IsAny(), It.IsAny())) .Returns( new RunResponseHandler( resultCursorBuilderMock.Object, - new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))))); + new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))), + HomeDbCacheKey.Default, + null, SessionConfig.Default, false)); handlerFactory.Setup( x => x.NewResultCursorBuilder( @@ -829,7 +926,11 @@ public async Task ShouldSendOnlyRunWhenReactive() var mockV3 = new Mock(); var protocol = new BoltProtocol(mockV3.Object, msgFactory.Object, handlerFactory.Object); - await protocol.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null); + await protocol.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny()); msgFactory.Verify( x => x.NewRunWithMetadataMessage( @@ -854,7 +955,13 @@ public async Task ShouldSendOnlyRunWhenReactive() Times.Once); handlerFactory.Verify( - x => x.NewRunResponseHandler(resultCursorBuilderMock.Object, It.IsNotNull()), + x => x.NewRunResponseHandler( + resultCursorBuilderMock.Object, + It.IsNotNull(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); mockConn.Verify( @@ -868,6 +975,7 @@ public async Task ShouldSendOnlyRunWhenReactive() mockConn.Verify(x => x.SendAsync(), Times.Once); mockConn.VerifyGet(x => x.Version); mockConn.VerifyGet(x => x.Server); + mockConn.VerifyGet(x => x.AuthToken); mockConn.VerifyGet(x => x.TelemetryEnabled); mockConn.VerifyNoOtherCalls(); @@ -896,7 +1004,10 @@ public async Task ShouldThrowWhenUsingImpersonatedUserWithBoltVersionLessThan44( TransactionConfig.Default, new SessionConfig("Douglas Fir"), null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeOfType(); } @@ -920,7 +1031,10 @@ public async Task ShouldThrowWhenNotificationsWithBoltVersionLessThan52(int majo TransactionConfig.Default, null, new NotificationsDisabledConfig(), - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeOfType(); } @@ -943,7 +1057,10 @@ public async Task ShouldNotThrowWhenNotificationsWithBoltVersionGreaterThan51(in TransactionConfig.Default, null, new NotificationsDisabledConfig(), - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeNull(); } @@ -967,7 +1084,10 @@ public async Task ShouldNotThrowWhenImpersonatingUserWithBoltVersionGreaterThan4 TransactionConfig.Default, new SessionConfig("Douglas Fir"), null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeNull(); } @@ -993,7 +1113,10 @@ await protocol.BeginTransactionAsync( config, sessionConfig, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + sessionConfig); mockV3.Verify( x => x.BeginTransactionAsync( @@ -1004,11 +1127,14 @@ await protocol.BeginTransactionAsync( config, sessionConfig, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true))), + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + sessionConfig), Times.Once); mockConn.Verify( - x => x.BeginTransactionAsync(It.IsAny()), + x => x.BeginTransactionAsync(It.IsAny(), It.IsAny()), Times.Never); } } @@ -1034,11 +1160,16 @@ public async Task ShouldSendOnlyRunWhenReactive() handlerFactory.Setup( x => x.NewRunResponseHandler( resultCursorBuilderMock.Object, - It.IsNotNull())) + It.IsNotNull(), + It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny())) .Returns( new RunResponseHandler( resultCursorBuilderMock.Object, - new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))))); + new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))), + HomeDbCacheKey.Default, + null, SessionConfig.Default, false)); handlerFactory.Setup( x => x.NewResultCursorBuilder( @@ -1062,7 +1193,10 @@ await protocol.RunInExplicitTransactionAsync( query, true, 10, - new Mock().Object); + new Mock().Object, + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default); msgFactory.Verify( x => x.NewRunWithMetadataMessage(mockConn.Object, query, null), @@ -1084,7 +1218,13 @@ await protocol.RunInExplicitTransactionAsync( Times.Once); handlerFactory.Verify( - x => x.NewRunResponseHandler(resultCursorBuilderMock.Object, It.IsNotNull()), + x => x.NewRunResponseHandler( + resultCursorBuilderMock.Object, + It.IsNotNull(), + HomeDbCacheKey.Default, + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); mockConn.Verify( @@ -1122,11 +1262,16 @@ public async Task ShouldSendPullMessageWhenNotReactive() handlerFactory.Setup( x => x.NewRunResponseHandler( resultCursorBuilderMock.Object, - It.IsNotNull())) + It.IsNotNull(), + It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny())) .Returns( new RunResponseHandler( resultCursorBuilderMock.Object, - new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))))); + new SummaryBuilder(new Query("..."), new ServerInfo(new Uri("http://0.0.0.0"))), + HomeDbCacheKey.Default, + null, SessionConfig.Default, false)); handlerFactory .Setup( @@ -1166,7 +1311,10 @@ await protocol.RunInExplicitTransactionAsync( query, false, 10, - new Mock().Object); + new Mock().Object, + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default); msgFactory.Verify( x => x.NewRunWithMetadataMessage(mockConn.Object, query, null), @@ -1188,7 +1336,13 @@ await protocol.RunInExplicitTransactionAsync( Times.Once); handlerFactory.Verify( - x => x.NewRunResponseHandler(resultCursorBuilderMock.Object, It.IsNotNull()), + x => x.NewRunResponseHandler( + resultCursorBuilderMock.Object, + It.IsNotNull(), + HomeDbCacheKey.Default, + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); mockConn.Verify( @@ -1261,8 +1415,16 @@ public void ShouldSendPullMessage() .Setup( x => x.NewRunResponseHandler( resultCursorBuilderMock.Object, - It.IsNotNull())) - .Returns(new RunResponseHandler(resultCursorBuilderMock.Object, sb)); + It.IsNotNull(), + It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns( + new RunResponseHandler( + resultCursorBuilderMock.Object, + sb, + HomeDbCacheKey.Default, + null, SessionConfig.Default, false)); handlerFactory .Setup( @@ -1327,8 +1489,16 @@ public void ShouldSendDiscardMessage() .Setup( x => x.NewRunResponseHandler( resultCursorBuilderMock.Object, - It.IsNotNull())) - .Returns(new RunResponseHandler(resultCursorBuilderMock.Object, sb)); + It.IsNotNull(), + It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns( + new RunResponseHandler( + resultCursorBuilderMock.Object, + sb, + HomeDbCacheKey.Default, + null, SessionConfig.Default, false)); handlerFactory .Setup( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolV3Tests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolV3Tests.cs index 1c9dc30f2..ef74d1d0a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolV3Tests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolV3Tests.cs @@ -21,8 +21,10 @@ using Moq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.MessageHandling.V3; +using Neo4j.Driver.Internal.MessageHandling.V4; using Neo4j.Driver.Internal.Messaging; using Neo4j.Driver.Internal.Protocol; using Neo4j.Driver.Internal.Result; @@ -118,7 +120,12 @@ public class GetRoutingTableAsyncTests public async Task ShouldThrowProtocolExceptionIfNullConnection() { var ex = await Record.ExceptionAsync( - () => BoltProtocolV3.Instance.GetRoutingTableAsync(null, "db", null, null)); + () => BoltProtocolV3.Instance.GetRoutingTableAsync( + null, + "db", + null, + null, + It.IsAny())); ex.Should().BeOfType(); } @@ -135,7 +142,8 @@ public async Task ShouldThrowIfImpersonationNotNull() mockConn.Object, "db", new SessionConfig("Douglas Fir"), - null)); + null, + It.IsAny())); ex.Should().BeOfType(); } @@ -203,11 +211,18 @@ public async Task ShouldSendRunWithMetadataMessageToGetRoutingTable() mockConn.Object, "test", null, - bm); + bm, + It.IsAny()); routingTable.Should().Contain(new KeyValuePair("db", "test")); - handlerFactory.Verify(x => x.NewRouteResponseHandler(), Times.Never); + handlerFactory.Verify( + x => x.NewRouteResponseHandler( + It.IsAny(), + It.IsAny(), + It.IsAny(), + false), + Times.Never); msgFactory.Verify( x => @@ -255,7 +270,11 @@ public async Task ShouldThrowIfImpersonatedUserIsNotNull() }; var exception = await Record.ExceptionAsync( - () => BoltProtocolV3.Instance.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null)); + () => BoltProtocolV3.Instance.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny())); exception.Should().BeOfType(); } @@ -279,7 +298,8 @@ public async Task ShouldThrowWhenNotificationsWithBoltVersionLessThan52(int majo () => BoltProtocolV3.Instance.RunInAutoCommitTransactionAsync( mockConn.Object, acp, - new NotificationsDisabledConfig())); + new NotificationsDisabledConfig(), + It.IsAny())); exception.Should().BeOfType(); } @@ -302,7 +322,8 @@ public async Task ShouldNotThrowWhenNotificationsWithBoltVersionGreaterThan51(in () => BoltProtocolV3.Instance.RunInAutoCommitTransactionAsync( mockConn.Object, acp, - new NotificationsDisabledConfig())); + new NotificationsDisabledConfig(), + It.IsAny())); exception.Should().BeNull(); } @@ -319,7 +340,11 @@ public async Task ShouldThrowIfDatabaseIsNotNull() }; var exception = await Record.ExceptionAsync( - () => BoltProtocolV3.Instance.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null)); + () => BoltProtocolV3.Instance.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny())); exception.Should().BeOfType(); } @@ -379,7 +404,11 @@ public async Task ShouldSendMessages() .Returns(resultCursorBuilderMock.Object); var protocol = new BoltProtocolV3(msgFactory.Object, handlerFactory.Object); - await protocol.RunInAutoCommitTransactionAsync(mockConn.Object, acp, null); + await protocol.RunInAutoCommitTransactionAsync( + mockConn.Object, + acp, + null, + It.IsAny()); handlerFactory.Verify( x => x.NewResultCursorBuilder( @@ -425,7 +454,10 @@ public async Task ShouldThrowIfImpersonatedUserIsNotNull() TransactionConfig.Default, null, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeOfType(); } @@ -449,7 +481,10 @@ public async Task ShouldThrowWhenNotificationsWithBoltVersionLessThan52(int majo TransactionConfig.Default, null, new NotificationsDisabledConfig(), - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeOfType(); } @@ -472,7 +507,10 @@ public async Task ShouldNotThrowWhenNotificationsWithBoltVersionGreaterThan51(in TransactionConfig.Default, null, new NotificationsDisabledConfig(), - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeNull(); } @@ -492,7 +530,10 @@ public async Task ShouldThrowIfDatabaseIsNotNull() TransactionConfig.Default, null, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeOfType(); } @@ -513,7 +554,10 @@ public async Task ShouldThrowIfConnectionModeIsNull() TransactionConfig.Default, null, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default)); exception.Should().BeOfType(); } @@ -549,14 +593,17 @@ await protocol.BeginTransactionAsync( tc, null, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, true)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default); msgFactory.Verify( x => x.NewBeginMessage(mockConn.Object, null, bookmarks, tc, AccessMode.Write, null), Times.Once); mockConn.Verify( - x => x.EnqueueAsync(fakeMessage, NoOpResponseHandler.Instance), + x => x.EnqueueAsync(fakeMessage, It.IsAny()), Times.Once); mockConn.Verify(x => x.SyncAsync(), Times.Once); @@ -594,14 +641,17 @@ await protocol.BeginTransactionAsync( tc, null, null, - new TransactionInfo(QueryApiType.UnmanagedTransaction, false, false))); + new TransactionInfo(QueryApiType.UnmanagedTransaction, false, false)), + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default); msgFactory.Verify( x => x.NewBeginMessage(mockConn.Object, null, bookmarks, tc, AccessMode.Write, null), Times.Once); mockConn.Verify( - x => x.EnqueueAsync(fakeMessage, NoOpResponseHandler.Instance), + x => x.EnqueueAsync(fakeMessage, It.IsAny()), Times.Once); mockConn.Verify(x => x.SyncAsync(), Times.Never); @@ -656,7 +706,15 @@ public async Task ShouldSendMessages() var protocol = new BoltProtocolV3(msgFactory.Object, handlerFactory.Object); var mockTransaction = new Mock(); - await protocol.RunInExplicitTransactionAsync(mockConn.Object, query, false, 0L, mockTransaction.Object); + await protocol.RunInExplicitTransactionAsync( + mockConn.Object, + query, + false, + 0L, + mockTransaction.Object, + HomeDbCacheKey.Default, + It.IsAny(), + SessionConfig.Default); handlerFactory.Verify( x => x.NewResultCursorBuilder( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/PipeReaderMemoryPoolTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/PipeReaderMemoryPoolTests.cs index 3069466b3..58cc2a2da 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/PipeReaderMemoryPoolTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/PipeReaderMemoryPoolTests.cs @@ -38,7 +38,7 @@ public void ShouldRentMemoryInPower(int size, int expectedSize, int maxReadBuffe using var memoryOwner = pool.Rent(size); memoryOwner.Memory.Length.Should().Be(expectedSize); } - + [Fact] public void ShouldRentMemoryInPowerWithDefaultSize() { @@ -46,7 +46,7 @@ public void ShouldRentMemoryInPowerWithDefaultSize() using var memoryOwner = pool.Rent(); memoryOwner.Memory.Length.Should().Be(1024); } - + [Fact] public void ShouldReusePooledObjects() { @@ -58,17 +58,17 @@ public void ShouldReusePooledObjects() length = memoryOwner.Memory.Length; memoryOwner.Memory.Span[0] = 1; } - + using (var memoryOwner = pool.Rent(4321)) { memoryOwner.Memory.Length.Should().Be(length); memoryOwner.Memory.Span[0].Should().Be(1); } } - + /// - /// This test is to verify the behaviour of the shared pool. - /// It is an unnecessary test but it proves the behaviour to validate + /// This test is to verify the behaviour of the shared pool. It is an unnecessary test but it proves the behaviour + /// to validate /// [Fact] public void SharedPoolShouldReturnSameValue() @@ -97,7 +97,7 @@ public void ShouldNotReturnSharedPoolObjects() memoryOwner.Memory.Length.Should().Be(1024); memoryOwner.Memory.Span[0] = 1; } - + pool = new PipeReaderMemoryPool(1024, 2048); using (var memoryOwner = pool.Rent(1024)) { @@ -110,7 +110,7 @@ public void ShouldNotReturnSharedPoolObjects() public void CanBorrowMaxLengthArray() { var pool = new PipeReaderMemoryPool(1024, Constants.MaxReadBufferSize); - + // 2146435071 is the max length of an array in .NET using (var memoryOwner = pool.Rent(2146435071)) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/TransactionTimeoutTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/TransactionTimeoutTests.cs index e6d6f26bc..c06dc7dad 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/TransactionTimeoutTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Internal/Util/TransactionTimeoutTests.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/AsyncEnumerableExtensionsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/AsyncEnumerableExtensionsTests.cs index a52eff4e5..69e989390 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/AsyncEnumerableExtensionsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/AsyncEnumerableExtensionsTests.cs @@ -45,10 +45,8 @@ async IAsyncEnumerable GetRecordsAsync() var result = await GetRecordsAsync().ToListAsync((string name) => new { name }); result.Should() .BeEquivalentTo( - [ new { name = "Alice" }, - new { name = "Eve" } - ]); + new { name = "Eve" }); } [Fact] @@ -472,10 +470,8 @@ async IAsyncEnumerable GetRecordsAsync() result.Should() .BeEquivalentTo( - [ new { name = "Alice", age = 25, city = "New York" }, - new { name = "Bob", age = 30, city = "Los Angeles" } - ]); + new { name = "Bob", age = 30, city = "Los Angeles" }); } [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BlueprintMappingTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BlueprintMappingTests.cs index 7a0e560d8..42fb4ba6a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BlueprintMappingTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BlueprintMappingTests.cs @@ -114,10 +114,6 @@ public void ShouldThrowMappingFailedExceptionForMismatchedTypes() act.Should().Throw(); } - private record Point( - [MappingSource("x")] int X, - [MappingSource("y")] int Y); - [Fact] public void ShouldMapPropertiesOfMappableTypes() { @@ -154,4 +150,8 @@ public void ShouldMapPropertiesOfAnonymousTypes() result.point.y.Should().Be(2); result.color.Should().Be("red"); } + + private record Point( + [MappingSource("x")] int X, + [MappingSource("y")] int Y); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BuiltMapperTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BuiltMapperTests.cs index 4cc0cc04b..7c0c4a2ee 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BuiltMapperTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/BuiltMapperTests.cs @@ -22,16 +22,6 @@ namespace Neo4j.Driver.Tests.Mapping; public class BuiltMapperTests { - private class NoParameterlessConstructor - { - public int Value { get; } - - public NoParameterlessConstructor(int value) - { - Value = value; - } - } - [Fact] public void ShouldThrowIfNoParameterlessConstructor() { @@ -51,12 +41,6 @@ public void ShouldUseConstructorWhenInstructed() result.Value.Should().Be(48); } - private class TwoPropertyClass - { - public int Value1 { get; set; } - public int Value2 { get; set; } - } - [Fact] public void ShouldMapProperties() { @@ -83,4 +67,20 @@ public void ShouldThrowWhenPropertyNotFoundInRecord() var act = () => mapper.Map(record); act.Should().Throw(); } + + private class NoParameterlessConstructor + { + public NoParameterlessConstructor(int value) + { + Value = value; + } + + public int Value { get; } + } + + private class TwoPropertyClass + { + public int Value1 { get; set; } + public int Value2 { get; set; } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DefaultMapperTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DefaultMapperTests.cs index 441fcaf43..3b1ac52db 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DefaultMapperTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DefaultMapperTests.cs @@ -25,36 +25,18 @@ namespace Neo4j.Driver.Tests.Mapping; public class DefaultMapperTests { - private class SimpleClass - { - public int Id { get; set; } - public string Name { get; set; } = null!; - } - [Fact] public void ShouldMapSimpleClass() { var mapper = DefaultMapper.Get(); - var record = TestRecord.Create(new[] { "Id", "Name" }, new object[] { 1, "Foo" }); + var record = TestRecord.Create(["Id", "Name"], [1, "Foo"]); var result = mapper.Map(record); result.Id.Should().Be(1); result.Name.Should().Be("Foo"); } - private class ConstructorClass - { - public int Id { get; } - public string Name { get; } - - public ConstructorClass(int id, string name) - { - Id = id; - Name = name; - } - } - [Fact] public void ShouldMapConstructorClass() { @@ -67,25 +49,6 @@ public void ShouldMapConstructorClass() result.Name.Should().Be("Foo"); } - private class NonDefaultConstructorClass - { - public int Id { get; } - public string Name { get; } - - public NonDefaultConstructorClass() - { - Id = -99; - Name = "error"; - } - - [MappingConstructor] - public NonDefaultConstructorClass(int id, string name) - { - Id = id; - Name = name; - } - } - [Fact] public void ShouldMapNonDefaultConstructorClass() { @@ -98,20 +61,6 @@ public void ShouldMapNonDefaultConstructorClass() result.Name.Should().Be("Foo"); } - private class Person - { - public Person( - [MappingSource("person.name")] string name, - [MappingSource("person.born")] int born) - { - Name = name; - Born = born; - } - - public string Name { get; } - public int Born { get; } - } - [Fact] public void ShouldMapFromInsideDictionaries() { @@ -151,18 +100,6 @@ public void ShouldMapFromNodesInRecords() person.Born.Should().Be(1977); } - public class NaturalPhenomenon - { - public string Name { get; } - public List Components { get; } - - public NaturalPhenomenon(string name, List components) - { - Name = name; - Components = components; - } - } - [Fact] public void ShouldMapListsThroughConstructor() { @@ -176,18 +113,6 @@ public void ShouldMapListsThroughConstructor() result.Components.Should().BeEquivalentTo("wind", "rain"); } - public class NaturalPhenomenonCommaSeparated - { - public string Name { get; } - public string Components { get; } - - public NaturalPhenomenonCommaSeparated(string name, string components) - { - Name = name; - Components = components; - } - } - [Fact] public void ShouldMapCommaSeparatedListsThroughConstructor() { @@ -201,18 +126,6 @@ public void ShouldMapCommaSeparatedListsThroughConstructor() result.Components.Should().Be("wind,rain"); } - public class HistoricalPhenomenon - { - public NaturalPhenomenon Phenomenon { get; } - public int Year { get; } - - public HistoricalPhenomenon(NaturalPhenomenon phenomenon, int year) - { - Phenomenon = phenomenon; - Year = year; - } - } - [Fact] public void ShouldMapNestedObjectsThroughConstructor() { @@ -232,18 +145,6 @@ public void ShouldMapNestedObjectsThroughConstructor() result.Year.Should().Be(2021); } - public class YearOfPhenomena - { - public int Year { get; } - public List Phenomena { get; } - - public YearOfPhenomena(int year, List phenomena) - { - Year = year; - Phenomena = phenomena; - } - } - [Fact] public void ShouldMapListsOfNodesThroughConstructor() { @@ -283,21 +184,6 @@ public void ShouldMapListsOfNodesThroughConstructor() result.Phenomena[2].Components.Should().BeEquivalentTo("earth", "quaking"); } - private class ClassWithProperties - { - public ClassWithProperties(int year, string occurrence) - { - Year = year * 10; - Occurrence = occurrence.ToLowerInvariant(); - } - - public int Year { get; } - public string Occurrence { get; } - - [MappingSource("description")] - public string Description { get; set; } - } - [Fact] public void ShouldSetPropertiesNotSetInConstructor() { @@ -313,11 +199,130 @@ public void ShouldSetPropertiesNotSetInConstructor() result.Description.Should().Be("Covid-19"); } - private class ClassWithPropertiesWithMappingHints + [Fact] + public void ShouldSetPropertiesNotSetInConstructorWithMappingHints() { - public ClassWithPropertiesWithMappingHints( - [MappingSource("year")] int year, - string occurrence) + var record = TestRecord.Create( + new[] { "year", "occurrence", "description", "something" }, + new object[] { 2021, "PANDEMIC", "Covid-19", "something" }); + + var mapper = DefaultMapper.Get(); + var result = mapper.Map(record); + + result.Year.Should().Be(20210); + result.Occurrence.Should().Be("pandemic"); + result.OtherText.Should().Be("Covid-19"); + } + + [Fact] + public void ShouldThrowIfClassHasNoConstructors() + { + var act = () => DefaultMapper.Get(); + act.Should().Throw(); + } + + private class SimpleClass + { + public int Id { get; set; } + public string Name { get; set; } = null!; + } + + private class ConstructorClass + { + public ConstructorClass(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; } + public string Name { get; } + } + + private class NonDefaultConstructorClass + { + public NonDefaultConstructorClass() + { + Id = -99; + Name = "error"; + } + + [MappingConstructor] + public NonDefaultConstructorClass(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; } + public string Name { get; } + } + + private class Person + { + public Person( + [MappingSource("person.name")] string name, + [MappingSource("person.born")] int born) + { + Name = name; + Born = born; + } + + public string Name { get; } + public int Born { get; } + } + + public class NaturalPhenomenon + { + public NaturalPhenomenon(string name, List components) + { + Name = name; + Components = components; + } + + public string Name { get; } + public List Components { get; } + } + + public class NaturalPhenomenonCommaSeparated + { + public NaturalPhenomenonCommaSeparated(string name, string components) + { + Name = name; + Components = components; + } + + public string Name { get; } + public string Components { get; } + } + + public class HistoricalPhenomenon + { + public HistoricalPhenomenon(NaturalPhenomenon phenomenon, int year) + { + Phenomenon = phenomenon; + Year = year; + } + + public NaturalPhenomenon Phenomenon { get; } + public int Year { get; } + } + + public class YearOfPhenomena + { + public YearOfPhenomena(int year, List phenomena) + { + Year = year; + Phenomena = phenomena; + } + + public int Year { get; } + public List Phenomena { get; } + } + + private class ClassWithProperties + { + public ClassWithProperties(int year, string occurrence) { Year = year * 10; Occurrence = occurrence.ToLowerInvariant(); @@ -327,22 +332,24 @@ public ClassWithPropertiesWithMappingHints( public string Occurrence { get; } [MappingSource("description")] - public string OtherText { get; set; } + public string Description { get; set; } } - [Fact] - public void ShouldSetPropertiesNotSetInConstructorWithMappingHints() + private class ClassWithPropertiesWithMappingHints { - var record = TestRecord.Create( - new[] { "year", "occurrence", "description", "something" }, - new object[] { 2021, "PANDEMIC", "Covid-19", "something" }); + public ClassWithPropertiesWithMappingHints( + [MappingSource("year")] int year, + string occurrence) + { + Year = year * 10; + Occurrence = occurrence.ToLowerInvariant(); + } - var mapper = DefaultMapper.Get(); - var result = mapper.Map(record); + public int Year { get; } + public string Occurrence { get; } - result.Year.Should().Be(20210); - result.Occurrence.Should().Be("pandemic"); - result.OtherText.Should().Be("Covid-19"); + [MappingSource("description")] + public string OtherText { get; set; } } private class NoConstructors @@ -351,11 +358,4 @@ private NoConstructors() { } } - - [Fact] - public void ShouldThrowIfClassHasNoConstructors() - { - var act = () => DefaultMapper.Get(null); - act.Should().Throw(); - } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateExecutableQueryMappingExtensionsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateExecutableQueryMappingExtensionsTests.cs index 3b1b9720d..7698ef797 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateExecutableQueryMappingExtensionsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateExecutableQueryMappingExtensionsTests.cs @@ -29,9 +29,9 @@ public void ShouldMapAllRecordsWith_01_Field() { Task>> GetRecordsAsync() { - var record1 = TestRecord.Create([("name", "Bob")]); - var record2 = TestRecord.Create([("name", "Alice")]); - var record3 = TestRecord.Create([("name", "Eve")]); + var record1 = TestRecord.Create(("name", "Bob")); + var record2 = TestRecord.Create(("name", "Alice")); + var record3 = TestRecord.Create(("name", "Eve")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -55,9 +55,9 @@ public void ShouldMapAllRecordsWith_02_Fields() { Task>> GetRecordsAsync() { - var record1 = TestRecord.Create([("name", "Bob"), ("age", 30)]); - var record2 = TestRecord.Create([("name", "Alice"), ("age", 25)]); - var record3 = TestRecord.Create([("name", "Eve"), ("age", 35)]); + var record1 = TestRecord.Create(("name", "Bob"), ("age", 30)); + var record2 = TestRecord.Create(("name", "Alice"), ("age", 25)); + var record3 = TestRecord.Create(("name", "Eve"), ("age", 35)); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -81,10 +81,10 @@ public void ShouldMapAllRecordsWith_03_Fields() { Task>> GetRecordsAsync() { - var record1 = TestRecord.Create([("name", "Bob"), ("age", 30), ("city", "New York")]); - var record2 = TestRecord.Create([("name", "Alice"), ("age", 25), ("city", "Los Angeles")]); + var record1 = TestRecord.Create(("name", "Bob"), ("age", 30), ("city", "New York")); + var record2 = TestRecord.Create(("name", "Alice"), ("age", 25), ("city", "Los Angeles")); - var record3 = TestRecord.Create([("name", "Eve"), ("age", 35), ("city", "Chicago")]); + var record3 = TestRecord.Create(("name", "Eve"), ("age", 35), ("city", "Chicago")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -109,25 +109,22 @@ public void ShouldMapAllRecordsWith_04_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - new[] - { - ("name", (object)"Bob"), ("age", (object)30), ("city", (object)"New York"), - ("country", (object)"USA") - }); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA")); var record2 = TestRecord.Create( - new[] - { - ("name", (object)"Alice"), ("age", (object)25), ("city", (object)"Los Angeles"), - ("country", (object)"USA") - }); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA")); var record3 = TestRecord.Create( - new[] - { - ("name", (object)"Eve"), ("age", (object)35), ("city", (object)"Chicago"), - ("country", (object)"USA") - }); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -152,22 +149,25 @@ public void ShouldMapAllRecordsWith_05_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - [ - ("name", "Bob"), ("age", 30), ("city", "New York"), - ("country", "USA"), ("job", "Engineer") - ]); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA"), + ("job", "Engineer")); var record2 = TestRecord.Create( - [ - ("name", "Alice"), ("age", 25), ("city", "Los Angeles"), - ("country", "USA"), ("job", "Doctor") - ]); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA"), + ("job", "Doctor")); var record3 = TestRecord.Create( - [ - ("name", "Eve"), ("age", 35), ("city", "Chicago"), - ("country", "USA"), ("job", "Teacher") - ]); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA"), + ("job", "Teacher")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -194,22 +194,28 @@ public void ShouldMapAllRecordsWith_06_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - [ - ("name", "Bob"), ("age", 30), ("city", "New York"), - ("country", "USA"), ("job", "Engineer"), ("gender", "Male") - ]); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA"), + ("job", "Engineer"), + ("gender", "Male")); var record2 = TestRecord.Create( - [ - ("name", "Alice"), ("age", 25), ("city", "Los Angeles"), - ("country", "USA"), ("job", "Doctor"), ("gender", "Female") - ]); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA"), + ("job", "Doctor"), + ("gender", "Female")); var record3 = TestRecord.Create( - [ - ("name", "Eve"), ("age", 35), ("city", "Chicago"), - ("country", "USA"), ("job", "Teacher"), ("gender", "Female") - ]); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA"), + ("job", "Teacher"), + ("gender", "Female")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -239,25 +245,31 @@ public void ShouldMapAllRecordsWith_07_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - [ - ("name", "Bob"), ("age", 30), ("city", "New York"), - ("country", "USA"), ("job", "Engineer"), ("gender", "Male"), - ("maritalStatus", "Single") - ]); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA"), + ("job", "Engineer"), + ("gender", "Male"), + ("maritalStatus", "Single")); var record2 = TestRecord.Create( - [ - ("name", "Alice"), ("age", 25), ("city", "Los Angeles"), - ("country", "USA"), ("job", "Doctor"), ("gender", "Female"), - ("maritalStatus", "Married") - ]); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA"), + ("job", "Doctor"), + ("gender", "Female"), + ("maritalStatus", "Married")); var record3 = TestRecord.Create( - [ - ("name", "Eve"), ("age", 35), ("city", "Chicago"), - ("country", "USA"), ("job", "Teacher"), ("gender", "Female"), - ("maritalStatus", "Divorced") - ]); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA"), + ("job", "Teacher"), + ("gender", "Female"), + ("maritalStatus", "Divorced")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -296,25 +308,34 @@ public void ShouldMapAllRecordsWith_08_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - [ - ("name", "Bob"), ("age", 30), ("city", "New York"), - ("country", "USA"), ("job", "Engineer"), ("gender", "Male"), - ("maritalStatus", "Single"), ("children", 2) - ]); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA"), + ("job", "Engineer"), + ("gender", "Male"), + ("maritalStatus", "Single"), + ("children", 2)); var record2 = TestRecord.Create( - [ - ("name", "Alice"), ("age", 25), ("city", "Los Angeles"), - ("country", "USA"), ("job", "Doctor"), ("gender", "Female"), - ("maritalStatus", "Married"), ("children", 0) - ]); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA"), + ("job", "Doctor"), + ("gender", "Female"), + ("maritalStatus", "Married"), + ("children", 0)); var record3 = TestRecord.Create( - [ - ("name", "Eve"), ("age", 35), ("city", "Chicago"), - ("country", "USA"), ("job", "Teacher"), ("gender", "Female"), - ("maritalStatus", "Divorced"), ("children", 1) - ]); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA"), + ("job", "Teacher"), + ("gender", "Female"), + ("maritalStatus", "Divorced"), + ("children", 1)); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -360,25 +381,37 @@ public void ShouldMapAllRecordsWith_09_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - [ - ("name", "Bob"), ("age", 30), ("city", "New York"), - ("country", "USA"), ("job", "Engineer"), ("gender", "Male"), - ("maritalStatus", "Single"), ("children", 2), ("education", "Bachelor's") - ]); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA"), + ("job", "Engineer"), + ("gender", "Male"), + ("maritalStatus", "Single"), + ("children", 2), + ("education", "Bachelor's")); var record2 = TestRecord.Create( - [ - ("name", "Alice"), ("age", 25), ("city", "Los Angeles"), - ("country", "USA"), ("job", "Doctor"), ("gender", "Female"), - ("maritalStatus", "Married"), ("children", 0), ("education", "Master's") - ]); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA"), + ("job", "Doctor"), + ("gender", "Female"), + ("maritalStatus", "Married"), + ("children", 0), + ("education", "Master's")); var record3 = TestRecord.Create( - [ - ("name", "Eve"), ("age", 35), ("city", "Chicago"), - ("country", "USA"), ("job", "Teacher"), ("gender", "Female"), - ("maritalStatus", "Divorced"), ("children", 1), ("education", "PhD") - ]); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA"), + ("job", "Teacher"), + ("gender", "Female"), + ("maritalStatus", "Divorced"), + ("children", 1), + ("education", "PhD")); var result = new EagerResult>( new List { record1, record2, record3 }, @@ -426,25 +459,40 @@ public void ShouldMapAllRecordsWith_10_Fields() Task>> GetRecordsAsync() { var record1 = TestRecord.Create( - [ - ("name", "Bob"), ("age", 30), ("city", "New York"), - ("country", "USA"), ("job", "Engineer"), ("gender", "Male"), - ("maritalStatus", "Single"), ("children", 2), ("education", "Bachelor's"), ("income", 70000) - ]); + ("name", "Bob"), + ("age", 30), + ("city", "New York"), + ("country", "USA"), + ("job", "Engineer"), + ("gender", "Male"), + ("maritalStatus", "Single"), + ("children", 2), + ("education", "Bachelor's"), + ("income", 70000)); var record2 = TestRecord.Create( - [ - ("name", "Alice"), ("age", 25), ("city", "Los Angeles"), - ("country", "USA"), ("job", "Doctor"), ("gender", "Female"), - ("maritalStatus", "Married"), ("children", 0), ("education", "Master's"), ("income", 80000) - ]); + ("name", "Alice"), + ("age", 25), + ("city", "Los Angeles"), + ("country", "USA"), + ("job", "Doctor"), + ("gender", "Female"), + ("maritalStatus", "Married"), + ("children", 0), + ("education", "Master's"), + ("income", 80000)); var record3 = TestRecord.Create( - [ - ("name", "Eve"), ("age", 35), ("city", "Chicago"), - ("country", "USA"), ("job", "Teacher"), ("gender", "Female"), - ("maritalStatus", "Divorced"), ("children", 1), ("education", "PhD"), ("income", 60000) - ]); + ("name", "Eve"), + ("age", 35), + ("city", "Chicago"), + ("country", "USA"), + ("job", "Teacher"), + ("gender", "Female"), + ("maritalStatus", "Divorced"), + ("children", 1), + ("education", "PhD"), + ("income", 60000)); var result = new EagerResult>( new List { record1, record2, record3 }, diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateMapperTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateMapperTests.cs index 8404c5713..6317a89da 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateMapperTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DelegateMapperTests.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -297,35 +297,6 @@ public void ShouldMapWithUserSuppliedLambdaExpression() result.Should().Be("AAA"); } - private abstract class SpokenStatement - { - public abstract bool IsTrue { get; } - public string Description { get; } - - protected SpokenStatement(string description) - { - Description = description; - } - } - - private class Truth : SpokenStatement - { - public override bool IsTrue => true; - - public Truth(string description) : base(description) - { - } - } - - private class Myth : SpokenStatement - { - public override bool IsTrue => false; - - public Myth(string description) : base(description) - { - } - } - [Fact] public void ShouldSucceedForExampleGivenInDocumentation() { @@ -339,8 +310,6 @@ public void ShouldSucceedForExampleGivenInDocumentation() spokenStatement.Description.Should().Be("This is true"); } - private record Person([MappingSource("name")] string Name, [MappingSource("age")] int Age); - [Fact] public void ShouldPassMappedObjectsToLambdaWhenRequired() { @@ -370,7 +339,11 @@ public void ShouldMapToObjectParameterIfPossible() public void ShouldSucceedWithMethodInsteadOfLambda() { var record = TestRecord.Create(("country", "Sweden"), ("population", 1234)); - string MakeStatement(string c,int p) => $"{c} has a population of {p}"; + + string MakeStatement(string c, int p) + { + return $"{c} has a population of {p}"; + } var result = record.AsObject((string country, int population) => MakeStatement(country, population)); @@ -382,16 +355,48 @@ public void ShouldFailIfDelegateThrowsException() { var record = TestRecord.Create(("x", 69)); - Action act = () => record.AsObject((int x) => - { - if(x == 69) + Action act = () => record.AsObject( + (int x) => { - throw new Exception("Test exception"); - } + if (x == 69) + { + throw new Exception("Test exception"); + } - return new string('A', x); - }); + return new string('A', x); + }); act.Should().Throw().WithInnerException().WithMessage("Test exception"); } + + private abstract class SpokenStatement + { + protected SpokenStatement(string description) + { + Description = description; + } + + public abstract bool IsTrue { get; } + public string Description { get; } + } + + private class Truth : SpokenStatement + { + public Truth(string description) : base(description) + { + } + + public override bool IsTrue => true; + } + + private class Myth : SpokenStatement + { + public Myth(string description) : base(description) + { + } + + public override bool IsTrue => false; + } + + private record Person([MappingSource("name")] string Name, [MappingSource("age")] int Age); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DictAsRecordTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DictAsRecordTests.cs index 08e35d4ce..cf3c0b01c 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DictAsRecordTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/DictAsRecordTests.cs @@ -1,19 +1,18 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections; using System.Collections.Generic; using FluentAssertions; @@ -21,6 +20,7 @@ using Neo4j.Driver.Mapping; using Neo4j.Driver.Tests.TestUtil; using Xunit; +using InvalidOperationException = System.InvalidOperationException; namespace Neo4j.Driver.Tests.Mapping; @@ -263,7 +263,7 @@ public void IReadOnlyDict_CountShouldReturnCorrectCount() var subject = new DictAsRecord(dict, originalRecord); - ((IReadOnlyDictionary)subject).Count.Should().Be(2); + subject.Count.Should().Be(2); } [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LabelCaptureTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LabelCaptureTests.cs index 00c43dfe7..88f39a6d4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LabelCaptureTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LabelCaptureTests.cs @@ -25,21 +25,6 @@ namespace Neo4j.Driver.Tests.Mapping; public class LabelCaptureTests { - public class TestMappedClass - { - [MappingSource("Person", EntityMappingSource.NodeLabel)] - [MappingOptional] - public string Label { get; set; } - - [MappingSource("Person", EntityMappingSource.NodeLabel)] - [MappingOptional] - public List Labels { get; set; } - - [MappingSource("Relationship", EntityMappingSource.RelationshipType)] - [MappingOptional] - public string RelationshipType { get; set; } - } - public LabelCaptureTests() { RecordObjectMapping.Reset(); @@ -78,34 +63,6 @@ public void ShouldCaptureRelationshipType() mapped.RelationshipType.Should().Be("ACTED_IN"); } - class CustomMapper : IMappingProvider - { - /// - public void CreateMappers(IMappingRegistry registry) - { - registry.RegisterMapping( - b => b - .Map( - x => x.Label, - "Person", - EntityMappingSource.NodeLabel, - x => string.Join("|", ((string[])x).Select(y => y.ToUpper())), - optional: true) - .Map( - x => x.Labels, - "Person", - EntityMappingSource.NodeLabel, - x => ((string[])x).Select(y => y.Replace("a", "x")).ToList(), - optional: true) - .Map( - x => x.RelationshipType, - "Relationship", - EntityMappingSource.RelationshipType, - x => x?.ToString()?.ToLower(), - optional: true)); - } - } - [Fact] public void ShouldCaptureAndConvertLabels() { @@ -130,4 +87,47 @@ public void ShouldCaptureAndConvertRelationshipType() mapped.RelationshipType.Should().Be("acted_in"); } + + public class TestMappedClass + { + [MappingSource("Person", EntityMappingSource.NodeLabel)] + [MappingOptional] + public string Label { get; set; } + + [MappingSource("Person", EntityMappingSource.NodeLabel)] + [MappingOptional] + public List Labels { get; set; } + + [MappingSource("Relationship", EntityMappingSource.RelationshipType)] + [MappingOptional] + public string RelationshipType { get; set; } + } + + private class CustomMapper : IMappingProvider + { + /// + public void CreateMappers(IMappingRegistry registry) + { + registry.RegisterMapping( + b => b + .Map( + x => x.Label, + "Person", + EntityMappingSource.NodeLabel, + x => string.Join("|", ((string[])x).Select(y => y.ToUpper())), + true) + .Map( + x => x.Labels, + "Person", + EntityMappingSource.NodeLabel, + x => ((string[])x).Select(y => y.Replace("a", "x")).ToList(), + true) + .Map( + x => x.RelationshipType, + "Relationship", + EntityMappingSource.RelationshipType, + x => x?.ToString()?.ToLower(), + true)); + } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LambdaMappingRecordExtensionsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LambdaMappingRecordExtensionsTests.cs index 062d4e5b8..11bafaef8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LambdaMappingRecordExtensionsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/LambdaMappingRecordExtensionsTests.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -284,7 +284,7 @@ public void ShouldMapToAnonymousTypeWithEightProperties() result.e.Should().Be(123L); result.f.Should().Be('x'); result.g.Should().Be(123.45m); - result.h.Should().Be((byte)123); + result.h.Should().Be(123); } [Fact] @@ -349,8 +349,8 @@ public void ShouldMapToAnonymousTypeWithNineProperties() result.e.Should().Be(123L); result.f.Should().Be('x'); result.g.Should().Be(123.45m); - result.h.Should().Be((byte)123); - result.i.Should().Be((short)12345); + result.h.Should().Be(123); + result.i.Should().Be(12345); } [Fact] @@ -420,9 +420,9 @@ public void ShouldMapToAnonymousTypeWithTenProperties() result.e.Should().Be(123L); result.f.Should().Be('x'); result.g.Should().Be(123.45m); - result.h.Should().Be((byte)123); - result.i.Should().Be((short)12345); - result.j.Should().Be((ushort)12345); + result.h.Should().Be(123); + result.i.Should().Be(12345); + result.j.Should().Be(12345); } [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappableValueProviderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappableValueProviderTests.cs index 8b8471ee4..8fdab357f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappableValueProviderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappableValueProviderTests.cs @@ -56,12 +56,6 @@ public void ShouldReturnSimpleValue() value.Should().Be("test-value"); } - private class Person - { - public string Name { get; set; } = null!; - public int Age { get; set; } - } - [Fact] public void ShouldApplyMappingToEntities() { @@ -276,4 +270,10 @@ public void ShouldConvertValueToRequestedType() result.Should().Be("123"); } + + private class Person + { + public string Name { get; set; } = null!; + public int Age { get; set; } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappedListCreatorTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappedListCreatorTests.cs index 982074119..4dc92ea1d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappedListCreatorTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappedListCreatorTests.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -38,18 +36,6 @@ public void ShouldCreateList() list.Should().BeEquivalentTo(Enumerable.Range(0, 10).Select(x => x.ToString())); } - private class Person - { - public Person(string name, int age) - { - Name = name; - Age = age; - } - - public string Name { get; set; } - public int Age { get; set; } - } - [Fact] public void ShouldCreateListOfMappedObjectsFromDictionaries() { @@ -57,7 +43,7 @@ public void ShouldCreateListOfMappedObjectsFromDictionaries() { new Dictionary(), new Dictionary(), new Dictionary() }; var record = Mock.Of(); - var people = new List{ new("Alan", 99), new("Basil", 999), new("David", 9999) }; + var people = new List { new("Alan", 99), new("Basil", 999), new("David", 9999) }; _mocker.GetMock() .SetupSequence(x => x.Map(It.IsAny(), typeof(Person))) @@ -87,4 +73,16 @@ public void ShouldCreateListOfMappedObjectsFromNodes() mappedList.Should().BeEquivalentTo(people); } + + private class Person + { + public Person(string name, int age) + { + Name = name; + Age = age; + } + + public string Name { get; set; } + public int Age { get; set; } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingBuilderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingBuilderTests.cs index d7d143032..bf538f0ba 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingBuilderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingBuilderTests.cs @@ -22,13 +22,6 @@ namespace Neo4j.Driver.Tests.Mapping; public class MappingBuilderTests { - private class TestClass - { - public int Settable { get; set; } - public int NotSettable { get; } = -1; - public int NotAProperty = 0x6060B017; - } - [Fact] public void ShouldThrowIfNotAMemberExpression() { @@ -52,4 +45,11 @@ public void ShouldThrowIfPropertyDoesNotHaveASetter() var act = () => subject.Map(x => x.NotSettable, "foo"); act.Should().Throw(); } -} \ No newline at end of file + + private class TestClass + { + public readonly int NotAProperty = 0x6060B017; + public int Settable { get; set; } + public int NotSettable { get; } = -1; + } +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingProviderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingProviderTests.cs index 6506128c2..80c1af5cd 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingProviderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/MappingProviderTests.cs @@ -23,6 +23,101 @@ namespace Neo4j.Driver.Tests.Mapping; public class MappingProviderTests { + public MappingProviderTests() + { + RecordObjectMapping.Reset(); + } + + [Fact] + public void ShouldOverrideDefaultMapping() + { + var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "test", 69 }); + RecordObjectMapping.RegisterProvider(); + + var obj = record.AsObject(); + + obj.Text.Should().Be("TEST!"); + obj.IntValue.Should().Be(69); + } + + [Fact] + public void ShouldUseWholeObjectMapping() + { + var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 }); + RecordObjectMapping.RegisterProvider(); + + var obj = record.AsObject(); + + obj.Text.Should().Be("test"); + obj.Number.Should().Be(101); + } + + [Fact] + public void ShouldNotUseDefaultMapperIfEmptyMappingConfigInProvider() + { + var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 }); + RecordObjectMapping.RegisterProvider(); + + var obj = record.AsObject(); + + obj.StringValue.Should().Be("unset"); + obj.IntValue.Should().Be(-1); + } + + [Fact] + public void ShouldMapPropertiesFromRecordIfRequired() + { + var record = TestRecord.Create(new[] { "name", "born", "active" }, new object[] { "Bob", 1977, 2000 }); + RecordObjectMapping.RegisterProvider(); + + var obj = record.AsObject(); + + obj.Name.Should().Be("Bob"); + obj.Age.Should().Be(23); + } + + [Fact] + public void ShouldUseCustomMapper() + { + var record1 = TestRecord.Create(("favourite_color", "blue"), ("lucky_number", 7)); + var record2 = TestRecord.Create(("job_title", "developer"), ("years_of_service", 5)); + + RecordObjectMapping.Register(new NamingConventionTranslator()); + RecordObjectMapping.Register(new NamingConventionTranslator()); + + var obj1 = record1.AsObject(); + var obj2 = record2.AsObject(); + + obj1.FavouriteColor.Should().Be("blue"); + obj1.LuckyNumber.Should().Be(7); + obj2.JobTitle.Should().Be("developer"); + obj2.YearsOfService.Should().Be(5); + } + + [Fact] + public void ShouldNotFailWhenUsingDefaultMapperButMappingSomePropertiesExplicitly() + { + var guid = Guid.NewGuid(); + var testRecord = TestRecord.Create(("Name", "Alice"), ("Guid", guid.ToString())); + RecordObjectMapping.RegisterProvider(new MappingProviderThatUsesDefaultMappingAndOverridesAGuidProperty(true)); + + var obj = testRecord.AsObject(); + + obj.Name.Should().Be("Alice"); + obj.Guid.Should().Be(guid); + } + + [Fact] + public void ShouldFailWhenUsingDefaultMapperWithoutOverriding() + { + var guid = Guid.NewGuid(); + var testRecord = TestRecord.Create(("Name", "Alice"), ("Guid", guid.ToString())); + RecordObjectMapping.RegisterProvider(new MappingProviderThatUsesDefaultMappingAndOverridesAGuidProperty(false)); + + var act = () => testRecord.AsObject(); + act.Should().Throw(); + } + private class TestObject { [MappingSource("intValue")] @@ -41,7 +136,7 @@ private class SecondTestObject private class ThirdTestObject { public int IntValue { get; set; } = -1; - public string StringValue { get; set; } = "unset"; + public string StringValue { get; } = "unset"; } private class PersonWithAge @@ -78,59 +173,6 @@ public void CreateMappers(IMappingRegistry registry) } } - public MappingProviderTests() - { - RecordObjectMapping.Reset(); - } - - [Fact] - public void ShouldOverrideDefaultMapping() - { - var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "test", 69 }); - RecordObjectMapping.RegisterProvider(); - - var obj = record.AsObject(); - - obj.Text.Should().Be("TEST!"); - obj.IntValue.Should().Be(69); - } - - [Fact] - public void ShouldUseWholeObjectMapping() - { - var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 }); - RecordObjectMapping.RegisterProvider(); - - var obj = record.AsObject(); - - obj.Text.Should().Be("test"); - obj.Number.Should().Be(101); - } - - [Fact] - public void ShouldNotUseDefaultMapperIfEmptyMappingConfigInProvider() - { - var record = TestRecord.Create(new[] { "stringValue", "intValue" }, new object[] { "TEST", 100 }); - RecordObjectMapping.RegisterProvider(); - - var obj = record.AsObject(); - - obj.StringValue.Should().Be("unset"); - obj.IntValue.Should().Be(-1); - } - - [Fact] - public void ShouldMapPropertiesFromRecordIfRequired() - { - var record = TestRecord.Create(new[] { "name", "born", "active" }, new object[] { "Bob", 1977, 2000 }); - RecordObjectMapping.RegisterProvider(); - - var obj = record.AsObject(); - - obj.Name.Should().Be("Bob"); - obj.Age.Should().Be(23); - } - private class FirstNameMappingTestObject { public string FavouriteColor { get; set; } @@ -145,6 +187,23 @@ private class SecondNameMappingTestObject private class NamingConventionTranslator : IRecordMapper { + /// + public T Map(IRecord record) + { + var type = typeof(T); + var obj = Activator.CreateInstance(type); + foreach (var field in record.Keys) + { + var property = type.GetProperty(GetTranslatedPropertyName(field)); + if (property != null) + { + property.SetValue(obj, record[field]); + } + } + + return (T)obj; + } + private string GetTranslatedPropertyName(string fieldName) { // convert from snake_case to PascalCase @@ -165,41 +224,6 @@ private string GetTranslatedPropertyName(string fieldName) return result; } - - /// - public T Map(IRecord record) - { - var type = typeof(T); - var obj = Activator.CreateInstance(type); - foreach (var field in record.Keys) - { - var property = type.GetProperty(GetTranslatedPropertyName(field)); - if (property != null) - { - property.SetValue(obj, record[field]); - } - } - - return (T)obj; - } - } - - [Fact] - public void ShouldUseCustomMapper() - { - var record1 = TestRecord.Create(("favourite_color", "blue"), ("lucky_number", 7)); - var record2 = TestRecord.Create(("job_title", "developer"), ("years_of_service", 5)); - - RecordObjectMapping.Register(new NamingConventionTranslator()); - RecordObjectMapping.Register(new NamingConventionTranslator()); - - var obj1 = record1.AsObject(); - var obj2 = record2.AsObject(); - - obj1.FavouriteColor.Should().Be("blue"); - obj1.LuckyNumber.Should().Be(7); - obj2.JobTitle.Should().Be("developer"); - obj2.YearsOfService.Should().Be(5); } private class NameAndGuid @@ -208,7 +232,7 @@ private class NameAndGuid public Guid Guid { get; set; } } - private class MappingProviderThatUsesDefaultMappingAndOverridesAGuidProperty(bool overrideGuid): IMappingProvider + private class MappingProviderThatUsesDefaultMappingAndOverridesAGuidProperty(bool overrideGuid) : IMappingProvider { public void CreateMappers(IMappingRegistry registry) { @@ -216,37 +240,11 @@ public void CreateMappers(IMappingRegistry registry) b => { b.UseDefaultMapping(); - if(overrideGuid) + if (overrideGuid) { b.Map(x => x.Guid, "Guid", converter: x => Guid.Parse(x.As())); } }); } } - - [Fact] - public void ShouldNotFailWhenUsingDefaultMapperButMappingSomePropertiesExplicitly() - { - var guid = Guid.NewGuid(); - var testRecord = TestRecord.Create(("Name", "Alice"), ("Guid", guid.ToString())); - RecordObjectMapping.RegisterProvider( - new MappingProviderThatUsesDefaultMappingAndOverridesAGuidProperty(true)); - - var obj = testRecord.AsObject(); - - obj.Name.Should().Be("Alice"); - obj.Guid.Should().Be(guid); - } - - [Fact] - public void ShouldFailWhenUsingDefaultMapperWithoutOverriding() - { - var guid = Guid.NewGuid(); - var testRecord = TestRecord.Create(("Name", "Alice"), ("Guid", guid.ToString())); - RecordObjectMapping.RegisterProvider( - new MappingProviderThatUsesDefaultMappingAndOverridesAGuidProperty(false)); - - var act = () => testRecord.AsObject(); - act.Should().Throw(); - } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/OptionalMappingTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/OptionalMappingTests.cs index bd3423571..f41798521 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/OptionalMappingTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/OptionalMappingTests.cs @@ -23,12 +23,6 @@ namespace Neo4j.Driver.Tests.Mapping; public class OptionalMappingTests { - private class ClassWithOptionalProperty - { - [MappingOptional] - public int Value { get; set; } = 1234; - } - [Fact] public void ShouldNotThrowIfOptionalPropertyIsNotPresent() { @@ -38,12 +32,6 @@ public void ShouldNotThrowIfOptionalPropertyIsNotPresent() mapped.Value.Should().Be(1234); } - private class ClassWithPropertyWithDefaultValue - { - [MappingDefaultValue(42)] - public int Value { get; set; } - } - [Fact] public void ShouldUseDefaultValueIfOptionalPropertyIsNotPresent() { @@ -53,16 +41,6 @@ public void ShouldUseDefaultValueIfOptionalPropertyIsNotPresent() mapped.Value.Should().Be(42); } - private class ClassWithConstructorParameterWithDefaultValue - { - public int Value { get; } - - public ClassWithConstructorParameterWithDefaultValue([MappingDefaultValue(42)] int value) - { - Value = value; - } - } - [Fact] public void ShouldMapConstructorParameterWithDefaultValue() { @@ -89,20 +67,12 @@ public void ShouldNotUseDefaultValueIfConstructorParameterIsPresent() mapped.Value.Should().Be(69); } - - private class ClassWithPropertyWithoutDefaultValue - { - public int Value { get; set; } - } [Fact] public void ShouldFailToCreateObject() { var record = TestRecord.Create(["NotTheValue"], [69]); - var act = () => - { - _ = record.AsObject(); - }; + var act = () => { _ = record.AsObject(); }; act.Should().Throw(); } @@ -117,12 +87,35 @@ public void ShouldFailToCreateList() TestRecord.Create(["Value"], [3]) }; - var act = () => - { - _ = records.Select(r => r.AsObject()).ToList(); - }; + var act = () => { _ = records.Select(r => r.AsObject()).ToList(); }; act.Should().Throw(); } + private class ClassWithOptionalProperty + { + [MappingOptional] + public int Value { get; } = 1234; + } + + private class ClassWithPropertyWithDefaultValue + { + [MappingDefaultValue(42)] + public int Value { get; set; } + } + + private class ClassWithConstructorParameterWithDefaultValue + { + public ClassWithConstructorParameterWithDefaultValue([MappingDefaultValue(42)] int value) + { + Value = value; + } + + public int Value { get; } + } + + private class ClassWithPropertyWithoutDefaultValue + { + public int Value { get; set; } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/RecordMappingTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/RecordMappingTests.cs index 673421593..6ef9b5155 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/RecordMappingTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Mapping/RecordMappingTests.cs @@ -25,35 +25,6 @@ namespace Neo4j.Driver.Tests.Mapping; public class RecordMappingTests { - private class TestPerson - { - [MappingDefaultValue("A. Test Name")] - [MappingSource("person.name")] - public string Name { get; set; } - - [MappingOptional] - [MappingSource("person.born")] - public int? Born { get; set; } - - [MappingOptional] - [MappingSource("hobbies")] - public List Hobbies { get; set; } = null!; - } - - private class SimpleTestPerson - { - [MappingOptional] - [MappingSource("name")] - public string Name { get; set; } = "A. Test Name"; - - [MappingOptional] - [MappingSource("born")] - public int? Born { get; set; } - - [MappingOptional] - public List Hobbies { get; set; } = null!; - } - [Fact] public void ShouldMapPrimitives() { @@ -71,15 +42,6 @@ public void ShouldMapList() person.Hobbies.Should().BeEquivalentTo("Coding", "Swimming"); } - private class PersonInDict - { - [MappingSource("person.name")] - public string Name { get; set; } = ""; - - [MappingSource("person.born")] - public int Born { get; set; } - } - [Fact] public void ShouldMapFromInsideDictionaries() { @@ -101,43 +63,6 @@ public void ShouldLeaveDefaultsIfFieldAbsent() person!.Born.Should().Be(1977); } - private class Movie - { - [MappingSource("title")] - public string Title { get; set; } = ""; - - [MappingSource("released")] - public int Released { get; set; } - - [MappingOptional] - [MappingSource("tagline")] - public string Tagline { get; set; } - } - - private class Person - { - [MappingSource("name")] - public string Name { get; set; } = ""; - - [MappingSource("born")] - public int? Born { get; set; } - } - - private class ProducingCareer - { - [MappingSource("person")] - public Person Producer { get; set; } = null!; - - [MappingSource("titles")] - public List MovieTitleIdeas { get; set; } = null!; - - [MappingSource("movies")] - public List HistoricalMovies { get; set; } = null!; - - [MappingSource("moviesDict")] - public List OtherMovies { get; set; } = null!; - } - [Fact] public void ShouldMapComplexObjects() { @@ -365,37 +290,6 @@ async IAsyncEnumerable GetRecordsAsync() new SimpleTestPerson { Name = "Eve", Born = 1999 }); } - private class CarAndPainting - { - [MappingSource("car")] - public Car Car { get; set; } = null!; - - [MappingSource("painting")] - public Painting Painting { get; set; } = null!; - } - - private class Painting - { - [MappingSource("painting.artist")] - public string Artist { get; set; } = ""; - - [MappingSource("painting.title")] - public string Title { get; set; } = ""; - } - - private class Car - { - [MappingSource("car.make")] - public string Make { get; set; } = ""; - - [MappingSource("car.model")] - public string Model { get; set; } = ""; - - [MappingDefaultValue("unset")] - [MappingSource("car.madeup")] - public string MadeUp { get; set; } - } - [Fact] public void ShouldMapSubNodesWithAbsolutePaths() { @@ -428,14 +322,6 @@ public void ShouldMapSubNodesWithAbsolutePaths() mappedObject.Car.MadeUp.Should().Be("unset"); } - private class PersonWithoutBornSetter - { - [MappingSource("name")] - public string Name { get; set; } = ""; - - public int? Born { get; } = 1999; // no setter - } - [Fact] public void DefaultMapperShouldIgnorePropertiesWithoutSetter() { @@ -445,15 +331,6 @@ public void DefaultMapperShouldIgnorePropertiesWithoutSetter() person.Born.Should().Be(1999); } - private class TestPersonWithoutBornMapped - { - [MappingSource("name")] - public string Name { get; set; } = "A. Test Name"; - - [MappingIgnored] - public int? Born { get; set; } = 9999; - } - [Fact] public void ShouldIgnorePropertiesWithDoNotMapAttribute() { @@ -463,21 +340,6 @@ public void ShouldIgnorePropertiesWithDoNotMapAttribute() person.Born.Should().Be(9999); } - private class Book - { - [MappingSource("title")] - public string Title { get; set; } - } - - private class Author - { - [MappingSource("author.name")] - public string Name { get; set; } - - [MappingSource("author.books")] - public List Books { get; set; } - } - [Fact] public void ShouldMapEntitiesWithListsOfNodes() { @@ -502,11 +364,6 @@ public void ShouldMapEntitiesWithListsOfNodes() mappedObject.Books[1].Title.Should().Be("The Thin End"); } - private record Song( - [MappingSource("recordingArtist")] string Artist, - [MappingSource("title")] string Title, - [MappingSource("year")] int Year); - [Fact] public void ShouldMapToRecords() { @@ -544,15 +401,6 @@ public void ShouldFailMappingToRecordsWithMissingFields() act.Should().Throw(); } - private class ClassWithInitProperties - { - [MappingSource("name")] - public string Name { get; init; } = ""; - - [MappingSource("age")] - public int Age { get; init; } - } - [Fact] public void ShouldMapToInitProperties() { @@ -562,12 +410,6 @@ public void ShouldMapToInitProperties() person.Age.Should().Be(1977); } - private class ClassWithDefaultConstructor(string forename, int age) - { - public string Name => forename; - public int Age => age; - } - [Fact] public void ShouldMapToDefaultConstructorParameters() { @@ -577,12 +419,6 @@ public void ShouldMapToDefaultConstructorParameters() person.Age.Should().Be(1977); } - private class ClassWithDefaultConstructorWithAttributes([MappingSource("forename")] string name, int age) - { - public string Name => name; - public int Age => age; - } - [Fact] public void ShouldMapToDefaultConstructorParametersWithAttributes() { @@ -653,10 +489,6 @@ public void ShouldMapToAnonymousTypeWithLambda() result.y.Should().Be("test"); } - private record TestXY(int X, string Y) - { - } - [Fact] public void ShouldMapToAnonymousTypeWithTypedLambda() { @@ -679,4 +511,172 @@ public void AsObject_ShouldMapRecordToObjectType() result.Should().BeOfType(expectedPerson.GetType()); result.Should().BeEquivalentTo(expectedPerson); } + + private class TestPerson + { + [MappingDefaultValue("A. Test Name")] + [MappingSource("person.name")] + public string Name { get; set; } + + [MappingOptional] + [MappingSource("person.born")] + public int? Born { get; set; } + + [MappingOptional] + [MappingSource("hobbies")] + public List Hobbies { get; set; } = null!; + } + + private class SimpleTestPerson + { + [MappingOptional] + [MappingSource("name")] + public string Name { get; set; } = "A. Test Name"; + + [MappingOptional] + [MappingSource("born")] + public int? Born { get; set; } + + [MappingOptional] + public List Hobbies { get; set; } = null!; + } + + private class PersonInDict + { + [MappingSource("person.name")] + public string Name { get; set; } = ""; + + [MappingSource("person.born")] + public int Born { get; set; } + } + + private class Movie + { + [MappingSource("title")] + public string Title { get; set; } = ""; + + [MappingSource("released")] + public int Released { get; set; } + + [MappingOptional] + [MappingSource("tagline")] + public string Tagline { get; set; } + } + + private class Person + { + [MappingSource("name")] + public string Name { get; set; } = ""; + + [MappingSource("born")] + public int? Born { get; set; } + } + + private class ProducingCareer + { + [MappingSource("person")] + public Person Producer { get; set; } = null!; + + [MappingSource("titles")] + public List MovieTitleIdeas { get; set; } = null!; + + [MappingSource("movies")] + public List HistoricalMovies { get; set; } = null!; + + [MappingSource("moviesDict")] + public List OtherMovies { get; set; } = null!; + } + + private class CarAndPainting + { + [MappingSource("car")] + public Car Car { get; set; } = null!; + + [MappingSource("painting")] + public Painting Painting { get; set; } = null!; + } + + private class Painting + { + [MappingSource("painting.artist")] + public string Artist { get; set; } = ""; + + [MappingSource("painting.title")] + public string Title { get; set; } = ""; + } + + private class Car + { + [MappingSource("car.make")] + public string Make { get; set; } = ""; + + [MappingSource("car.model")] + public string Model { get; set; } = ""; + + [MappingDefaultValue("unset")] + [MappingSource("car.madeup")] + public string MadeUp { get; set; } + } + + private class PersonWithoutBornSetter + { + [MappingSource("name")] + public string Name { get; set; } = ""; + + public int? Born { get; } = 1999; // no setter + } + + private class TestPersonWithoutBornMapped + { + [MappingSource("name")] + public string Name { get; set; } = "A. Test Name"; + + [MappingIgnored] + public int? Born { get; set; } = 9999; + } + + private class Book + { + [MappingSource("title")] + public string Title { get; set; } + } + + private class Author + { + [MappingSource("author.name")] + public string Name { get; set; } + + [MappingSource("author.books")] + public List Books { get; set; } + } + + private record Song( + [MappingSource("recordingArtist")] string Artist, + [MappingSource("title")] string Title, + [MappingSource("year")] int Year); + + private class ClassWithInitProperties + { + [MappingSource("name")] + public string Name { get; set; } = ""; + + [MappingSource("age")] + public int Age { get; init; } + } + + private class ClassWithDefaultConstructor(string forename, int age) + { + public string Name => forename; + public int Age => age; + } + + private class ClassWithDefaultConstructorWithAttributes([MappingSource("forename")] string name, int age) + { + public string Name => name; + public int Age => age; + } + + private record TestXY(int X, string Y) + { + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj b/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj index 27a6fe85e..263c40e2b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj @@ -1,5 +1,5 @@  - + false Debug;Release;ReleaseSigned;DebugDelaySigned @@ -16,38 +16,38 @@ preview - - - - - - - - - - - + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - - - - - - - + + + + + + + - + diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/NodeTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/NodeTests.cs index f83e04906..c7753e3c4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/NodeTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/NodeTests.cs @@ -55,4 +55,4 @@ public void ShouldEqualIfIdEquals() node1.Equals(node3).Should().BeTrue(); Equals(node1, node3).Should().BeTrue(); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Internal/InternalRxSessionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Internal/InternalRxSessionTests.cs index 37fe5a92b..330992822 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Internal/InternalRxSessionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Internal/InternalRxSessionTests.cs @@ -114,8 +114,7 @@ public class BeginTransaction : AbstractRxTest public void ShouldReturnObservable() { var session = new Mock(); - session.Setup( - x => x.BeginTransactionAsync(It.IsAny>(), It.IsAny())) + session.Setup(x => x.BeginTransactionAsync(It.IsAny>(), It.IsAny())) .ReturnsAsync(Mock.Of()); var rxSession = new InternalRxSession(session.Object, Mock.Of()); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Utils/Utils.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Utils/Utils.cs index bd6d460c2..e5e46d114 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Utils/Utils.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Reactive/Utils/Utils.cs @@ -48,11 +48,12 @@ public static Func MatchesKeys(params string[] keys) public static Func MatchesRecord(string[] keys, params object[] fields) { - return Matches(rec => - { - rec.Keys.Should().BeEquivalentTo(keys); - rec.Values.Values.Should().BeEquivalentTo(fields); - }); + return Matches( + rec => + { + rec.Keys.Should().BeEquivalentTo(keys); + rec.Values.Values.Should().BeEquivalentTo(fields); + }); } public static Func MatchesSummary( diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/RelationshipTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/RelationshipTests.cs index e6a662963..6cc6cca17 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/RelationshipTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/RelationshipTests.cs @@ -44,4 +44,4 @@ public void ShouldEqualIfIdEquals() // TODO: The following test is currently not supported by Moq //rel1.GetHashCode().Should().Be(rel3.GetHashCode()); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Result/RecordTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Result/RecordTests.cs index f70b641e1..49fb4d52f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Result/RecordTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Result/RecordTests.cs @@ -82,10 +82,10 @@ public void TryGet_IsCaseSensitive() { _record.TryGet("Key1", out var value).Should().BeTrue(); value.Should().Be("Value1"); - _record.TryGet("KEY1", out _).Should().BeFalse(); + _record.TryGet("KEY1", out var _).Should().BeFalse(); _record.TryGet("Key2", out value).Should().BeTrue(); value.Should().Be("Value2"); - _record.TryGet("KEY2", out _).Should().BeFalse(); + _record.TryGet("KEY2", out var _).Should().BeFalse(); } [Fact] @@ -161,7 +161,7 @@ public void TryGetValue_ReturnsCorrectResult() var dictionary = _record as IReadOnlyDictionary; dictionary.TryGetValue("Key1", out var value).Should().BeTrue(); value.Should().Be("Value1"); - dictionary.TryGetValue("NonExistentKey", out _).Should().BeFalse(); + dictionary.TryGetValue("NonExistentKey", out var _).Should().BeFalse(); } [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultCursorBuilderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultCursorBuilderTests.cs index 3a38fa96a..fb2ac988c 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultCursorBuilderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultCursorBuilderTests.cs @@ -32,8 +32,14 @@ public class ResultCursorBuilderTests public void ShouldStartInRunRequestedStateRx() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, - 1000, true, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, + 1000, + true, new Mock().Object); builder.CurrentState.Should().Be(ResultCursorBuilder.State.RunRequested); @@ -43,8 +49,14 @@ public void ShouldStartInRunRequestedStateRx() public void ShouldStartInRunAndRecordsRequestedState() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, - 1000, false, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, + 1000, + false, new Mock().Object); builder.CurrentState.Should().Be(ResultCursorBuilder.State.RunAndRecordsRequested); @@ -54,7 +66,14 @@ public void ShouldStartInRunAndRecordsRequestedState() public void ShouldTransitionToRunCompletedWhenRunCompletedRx() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, 1000, true, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, + 1000, + true, new Mock().Object); builder.CurrentState.Should().Be(ResultCursorBuilder.State.RunRequested); @@ -67,7 +86,12 @@ public void ShouldTransitionToRunCompletedWhenRunCompletedRx() public void ShouldNotTransitionToRunCompletedWhenRunCompleted() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, 1000, false, new Mock().Object); @@ -82,7 +106,12 @@ public void ShouldNotTransitionToRunCompletedWhenRunCompleted() public void ShouldTransitionToRecordsStreamingStreamingWhenRecordIsPushedRx() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, 1000, true, new Mock().Object); @@ -100,7 +129,12 @@ public void ShouldTransitionToRecordsStreamingStreamingWhenRecordIsPushedRx() public void ShouldTransitionToRecordsStreamingStreamingWhenRecordIsPushed() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, 1000, false, new Mock().Object); @@ -118,7 +152,12 @@ public void ShouldTransitionToRecordsStreamingStreamingWhenRecordIsPushed() public void ShouldTransitionToRunCompletedWhenPullCompletedWithHasMore() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, 1000, false, new Mock().Object) @@ -134,7 +173,12 @@ public void ShouldTransitionToRunCompletedWhenPullCompletedWithHasMore() public void ShouldTransitionToCompletedWhenPullCompleted() { var builder = - new ResultCursorBuilder(CreateSummaryBuilder(), CreateTaskQueue(), null, null, null, + new ResultCursorBuilder( + CreateSummaryBuilder(), + CreateTaskQueue(), + null, + null, + null, 1000, false, new Mock().Object) @@ -454,10 +498,10 @@ public async Task ShouldReturnFirstBatchOfRecordsAndCancel() [Fact] public async Task ShouldThrowIfTranasactionTerminatedOnFetch() { - var expected = new ClientException("Neo.Broken.Db","it's broken!") as Exception; + var expected = new ClientException("Neo.Broken.Db", "it's broken!") as Exception; var mockTx = new Mock(); mockTx.Setup(x => x.IsErrored(out expected)).Returns(true).Verifiable(); - + var builder = new ResultCursorBuilder( CreateSummaryBuilder(), @@ -468,6 +512,7 @@ public async Task ShouldThrowIfTranasactionTerminatedOnFetch() 1000, true, mockTx.Object); + var cursor = builder.CreateCursor(); var exception = await Record.ExceptionAsync(() => cursor.FetchAsync()); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultTests.cs index 00c0d3dfe..94016e736 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultTests.cs @@ -207,8 +207,7 @@ public IEnumerable Records { while (i == _records.Count) { - _output.WriteLine( - $"{DateTime.Now:HH:mm:ss.fff} -> Waiting for more Records"); + _output.WriteLine($"{DateTime.Now:HH:mm:ss.fff} -> Waiting for more Records"); Thread.Sleep(50); } @@ -229,8 +228,7 @@ public IEnumerable RecordsWithAutoLoad { while (i == _records.Count) { - _output.WriteLine( - $"{DateTime.Now:HH:mm:ss.fff} -> Waiting for more Records"); + _output.WriteLine($"{DateTime.Now:HH:mm:ss.fff} -> Waiting for more Records"); Thread.Sleep(500); AddNew(1); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterConnectionPoolTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterConnectionPoolTests.cs index 328153a46..e45dbd6f9 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterConnectionPoolTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterConnectionPoolTests.cs @@ -110,7 +110,7 @@ public async Task ShouldReturnExistingConnectionPoolIfUriAlreadyExist() // Then connection.Should().NotBeNull(); var exception = - await Record.ExceptionAsync(() => connection.InitAsync(null)); + await Record.ExceptionAsync(() => connection.InitAsync()); mockedConnection.Verify( c => c.InitAsync(It.IsAny(), CancellationToken.None), diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryTests.cs index 10de75810..a738b8ae5 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryTests.cs @@ -19,6 +19,7 @@ using FluentAssertions; using Moq; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; using Neo4j.Driver.Internal.Protocol; @@ -63,12 +64,18 @@ public async Task ShouldParseRoutingTableResult() var sessionConfig = new SessionConfig("fake-person"); var mockConn = new Mock(); - mockConn.Setup(x => x.GetRoutingTableAsync("test", sessionConfig, bookmarks)) + mockConn.Setup( + x => x.GetRoutingTableAsync("test", sessionConfig, bookmarks, It.IsAny())) .ReturnsAsync(routingTable); // When var manager = new ClusterDiscovery(); - var table = await manager.DiscoverAsync(mockConn.Object, "test", sessionConfig, bookmarks); + var table = await manager.DiscoverAsync( + mockConn.Object, + "test", + sessionConfig, + bookmarks, + It.IsAny()); // Then table.Database.Should().Be("test"); @@ -180,4 +187,4 @@ public MockedConnection( public Mock MockConn { get; } = new(); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs index a946be6e4..709941f80 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs @@ -18,6 +18,7 @@ using System.Threading.Tasks; using FluentAssertions; using Moq; +using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; using Neo4j.Driver.Internal.Routing; using Xunit; @@ -39,7 +40,7 @@ public async Task ShouldRemoveFromLoadBalancer() var uri = new Uri("https://neo4j.com"); var routingTableManagerMock = new Mock(); routingTableManagerMock - .Setup(x => x.EnsureRoutingTableForModeAsync(AccessMode.Read, null, null, Bookmarks.Empty)) + .Setup(x => x.EnsureRoutingTableForModeAsync(AccessMode.Read, null, false, null, Bookmarks.Empty)) .ReturnsAsync(routingTableMock.Object); var loadBalancer = new LoadBalancer(clusterPoolMock.Object, routingTableManagerMock.Object); @@ -62,7 +63,7 @@ public void ShouldRemoveWriterFromRoutingTable() var uri = new Uri("https://neo4j.com"); var routingTableManagerMock = new Mock(); routingTableManagerMock - .Setup(x => x.EnsureRoutingTableForModeAsync(AccessMode.Write, null, null, Bookmarks.Empty)) + .Setup(x => x.EnsureRoutingTableForModeAsync(AccessMode.Write, null, false, null, Bookmarks.Empty)) .ReturnsAsync(routingTableMock.Object); var loadBalancer = new LoadBalancer(clusterPoolMock.Object, routingTableManagerMock.Object); @@ -83,14 +84,17 @@ public class AcquireMethod public async Task ShouldThrowSessionExpiredExceptionIfNoServerAvailable(AccessMode mode) { // Given - var mock = new Mock(); - mock.Setup(x => x.EnsureRoutingTableForModeAsync(mode, null, null, Bookmarks.Empty)) + var mockRoutingTableManager = new Mock(); + mockRoutingTableManager.Setup(x => x.EnsureRoutingTableForModeAsync(mode, null, false, null, Bookmarks.Empty)) .ReturnsAsync(NewMockedRoutingTable(mode, null, string.Empty).Object); - var balancer = new LoadBalancer(null, mock.Object); + var mockClusterConnectionPool = new Mock(); + mockClusterConnectionPool.Setup(x => x.CanUseHomeDbCache()).Returns(false); + + var balancer = new LoadBalancer(mockClusterConnectionPool.Object, mockRoutingTableManager.Object); // When - var error = await Record.ExceptionAsync(() => balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)); + var error = await Record.ExceptionAsync(() => balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)); // Then error.Should().BeOfType(); @@ -110,6 +114,7 @@ public async Task ShouldReturnConnectionWithCorrectMode(AccessMode mode) x => x.EnsureRoutingTableForModeAsync( mode, It.IsAny(), + It.IsAny(), It.IsAny(), Bookmarks.Empty)) .ReturnsAsync(routingTableMock.Object); @@ -120,13 +125,20 @@ public async Task ShouldReturnConnectionWithCorrectMode(AccessMode mode) mockedConn.Setup(x => x.Mode).Returns(mode); var conn = mockedConn.Object; clusterPoolMock - .Setup(x => x.AcquireAsync(uri, mode, It.IsAny(), It.IsAny(), Bookmarks.Empty, false)) + .Setup( + x => x.AcquireAsync( + uri, + mode, + It.IsAny(), + It.IsAny(), + Bookmarks.Empty, + false)) .ReturnsAsync(conn); var balancer = new LoadBalancer(clusterPoolMock.Object, mock.Object); // When - var acquiredConn = await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty); + var acquiredConn = await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false); // Then acquiredConn.Server.Address.Should().Be(uri.ToString()); @@ -147,7 +159,7 @@ public async Task ShouldReturnConnectionWithDBFromRoutingTable( var uri = new Uri("neo4j://123:456"); var mockManager = new Mock(); var routingTableMock = NewMockedRoutingTable(mode, uri, aliasDbName); - mockManager.Setup(x => x.EnsureRoutingTableForModeAsync(mode, dbName, null, Bookmarks.Empty)) + mockManager.Setup(x => x.EnsureRoutingTableForModeAsync(mode, dbName, false, null, Bookmarks.Empty)) .ReturnsAsync(routingTableMock.Object); var clusterPoolMock = new Mock(); @@ -161,7 +173,7 @@ public async Task ShouldReturnConnectionWithDBFromRoutingTable( var balancer = new LoadBalancer(clusterPoolMock.Object, mockManager.Object); // When - var acquiredConn = await balancer.AcquireAsync(mode, dbName, null, Bookmarks.Empty); + var acquiredConn = await balancer.AcquireAsync(mode, dbName, null, Bookmarks.Empty, false); // Then acquiredConn.Database.Should().Be(desiredResult); @@ -180,8 +192,9 @@ public void ShouldForgetServerWhenFailedToEstablishConn(AccessMode mode) x => x.EnsureRoutingTableForModeAsync( mode, It.IsAny(), + It.IsAny(), It.IsAny(), - Bookmarks.Empty)) + It.IsAny())) .ReturnsAsync(routingTableMock.Object); mock.Setup(x => x.ForgetServer(It.IsAny(), It.IsAny())) @@ -192,14 +205,19 @@ public void ShouldForgetServerWhenFailedToEstablishConn(AccessMode mode) var clusterConnPoolMock = new Mock(); clusterConnPoolMock.Setup( - x => x.AcquireAsync(uri, mode, It.IsAny(), It.IsAny(), Bookmarks.Empty, + x => x.AcquireAsync( + uri, + mode, + It.IsAny(), + It.IsAny(), + Bookmarks.Empty, false)) .Returns(Task.FromException(new ServiceUnavailableException("failed init"))); var balancer = new LoadBalancer(clusterConnPoolMock.Object, mock.Object); // When & Then - balancer.Awaiting(b => b.AcquireAsync(mode, It.IsAny(), It.IsAny(), Bookmarks.Empty)) + balancer.Awaiting(b => b.AcquireAsync(mode, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Should() .Throw() .WithMessage("Failed to connect to any*"); @@ -218,12 +236,17 @@ public async Task ShouldThrowErrorDirectlyIfSecurityError(AccessMode mode) var uri = new Uri("neo4j://123:456"); var routingTableMock = NewMockedRoutingTable(mode, uri, string.Empty); var mock = new Mock(); - mock.Setup(x => x.EnsureRoutingTableForModeAsync(mode, null, null, Bookmarks.Empty)) + mock.Setup(x => x.EnsureRoutingTableForModeAsync(mode, null, false, null, Bookmarks.Empty)) .ReturnsAsync(routingTableMock.Object); var clusterConnPoolMock = new Mock(); clusterConnPoolMock.Setup( - x => x.AcquireAsync(uri, mode, It.IsAny(), It.IsAny(), Bookmarks.Empty, + x => x.AcquireAsync( + uri, + mode, + It.IsAny(), + It.IsAny(), + Bookmarks.Empty, false)) .Returns( Task.FromException( @@ -232,7 +255,7 @@ public async Task ShouldThrowErrorDirectlyIfSecurityError(AccessMode mode) var balancer = new LoadBalancer(clusterConnPoolMock.Object, mock.Object); // When - var error = await Record.ExceptionAsync(() => balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)); + var error = await Record.ExceptionAsync(() => balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)); // Then error.Should().BeOfType(); @@ -256,20 +279,27 @@ public void ShouldThrowErrorDirectlyIfProtocolError(AccessMode mode) x => x.EnsureRoutingTableForModeAsync( mode, It.IsAny(), + It.IsAny(), It.IsAny(), Bookmarks.Empty)) .ReturnsAsync(routingTableMock.Object); var clusterConnPoolMock = new Mock(); clusterConnPoolMock - .Setup(x => x.AcquireAsync(uri, mode, It.IsAny(), It.IsAny(), Bookmarks.Empty, - false)) + .Setup( + x => x.AcquireAsync( + uri, + mode, + It.IsAny(), + It.IsAny(), + Bookmarks.Empty, + false)) .Returns(Task.FromException(new ProtocolException("do not understand struct 0x01"))); var balancer = new LoadBalancer(clusterConnPoolMock.Object, mock.Object); // When - balancer.Awaiting(b => b.AcquireAsync(mode, null, null, Bookmarks.Empty)) + balancer.Awaiting(b => b.AcquireAsync(mode, null, null, Bookmarks.Empty, false)) .Should() .Throw() .WithMessage("*do not understand struct 0x01*"); @@ -290,7 +320,7 @@ public async Task ShouldReturnConnectionAccordingToLoadBalancingStrategy(AccessM new List { new("writer:1"), new("writer:2") }); var routingTableManager = new Mock(); - routingTableManager.Setup(x => x.EnsureRoutingTableForModeAsync(mode, null, null, Bookmarks.Empty)) + routingTableManager.Setup(x => x.EnsureRoutingTableForModeAsync(mode, null, false, null, Bookmarks.Empty)) .ReturnsAsync(routingTable); var clusterPoolMock = new Mock(); @@ -305,40 +335,47 @@ public async Task ShouldReturnConnectionAccordingToLoadBalancingStrategy(AccessM .ReturnsAsync( (Uri uri, AccessMode m, string _, string _, Bookmarks _, bool _) => NewConnectionMock(uri, m)); - var balancer = new LoadBalancer(clusterPoolMock.Object, routingTableManager.Object); + clusterPoolMock.Setup(x => x.CanUseHomeDbCache()).Returns(true); + + var uri = new Uri("bolt://123:456"); + var driverContext = new DriverContext( + uri, + AuthTokenManagers.None, + new Config()); + var balancer = new LoadBalancer(clusterPoolMock.Object, routingTableManager.Object, driverContext); if (mode == AccessMode.Read) { - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("reader:1"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("reader:2"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("reader:3"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("reader:1"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("reader:2"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("reader:3"); } else if (mode == AccessMode.Write) { - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("writer:1"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("writer:2"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("writer:1"); - (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty)).Server.Address.Should() + (await balancer.AcquireAsync(mode, null, null, Bookmarks.Empty, false)).Server.Address.Should() .Be("writer:2"); } else diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoundRobinArrayIndexTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoundRobinArrayIndexTests.cs index a8f51b81c..4dcb07533 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoundRobinArrayIndexTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoundRobinArrayIndexTests.cs @@ -61,4 +61,4 @@ public void ShouldHandleOverflow() roundRobinIndex.Next(arrayLength).Should().Be(1); roundRobinIndex.Next(arrayLength).Should().Be(2); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableManagerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableManagerTests.cs index 23eb83126..bd24ac06a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableManagerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableManagerTests.cs @@ -21,6 +21,7 @@ using Moq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.Logging; using Neo4j.Driver.Internal.Routing; using Xunit; @@ -178,7 +179,13 @@ public async Task ShouldAddInitialUriWhenNoAvailableRouters() .ReturnsAsync(Mock.Of()); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .ReturnsAsync(Mock.Of()); var manager = NewRoutingTableManager(routingTableMock.Object, poolManagerMock.Object, discovery.Object); @@ -221,7 +228,13 @@ public async Task ShouldNotTryInitialUriIfAlreadyTried() mockProvider.Setup(x => x.Get()).Returns(initialUriSet); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(Mock.Of()); var manager = @@ -257,7 +270,13 @@ public async Task ShouldForgetAndTryNextRouterWhenConnectionIsNull() .ReturnsAsync((ClusterConnection)null); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .Throws(); var manager = NewRoutingTableManager(routingTable, poolManagerMock.Object, discovery.Object); @@ -283,22 +302,32 @@ public async Task ShouldForgetAndTryNextRouterWhenFailedWithConnectionError() // This ensures that uri and uri2 will return in order var routingTable = new RoutingTable(null, new List { uriA, uriB }); var poolManagerMock = new Mock(); - poolManagerMock.SetupSequence(x => x.CreateClusterConnectionAsync(It.IsAny(), - It.IsAny())) + poolManagerMock.SetupSequence( + x => x.CreateClusterConnectionAsync( + It.IsAny(), + It.IsAny())) .ReturnsAsync(connA) .ReturnsAsync(connB); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), null, null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + null, + null, + Bookmarks.Empty, + It.IsAny())) .Callback( - (IConnection c, string _, SessionConfig _, Bookmarks _) => + (IConnection c, string _, SessionConfig _, Bookmarks _, IHomeDbCache _) => throw new NotSupportedException($"Unknown uri: {c.Server.Address}")); - discovery.Setup(x => x.DiscoverAsync(connA, "", null, Bookmarks.Empty)) - .Callback((IConnection _, string _, SessionConfig _, Bookmarks _) => routingTable.Remove(uriA)) + discovery.Setup( + x => x.DiscoverAsync(connA, "", null, Bookmarks.Empty, It.IsAny())) + .Callback((IConnection _, string _, SessionConfig _, Bookmarks _, IHomeDbCache _) => routingTable.Remove(uriA)) .Throws(new SessionExpiredException("failed init")); - discovery.Setup(x => x.DiscoverAsync(connB, "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync(connB, "", null, Bookmarks.Empty, It.IsAny())) .ReturnsAsync(NewRoutingTable(new[] { uriA }, new[] { uriA }, new[] { uriA })); var manager = NewRoutingTableManager(routingTable, poolManagerMock.Object, discovery.Object); @@ -323,7 +352,14 @@ public async Task ShouldLogServiceUnavailable() .Returns(Task.FromResult(new Mock().Object)); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)).Throws(error); + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) + .Throws(error); var logger = new Mock(); logger.Setup(x => x.Warn(It.IsAny(), It.IsAny(), It.IsAny())); @@ -350,7 +386,14 @@ public async Task ShouldPropagateSecurityException() .ReturnsAsync(new Mock().Object); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)).Throws(error); + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) + .Throws(error); var logger = new Mock(); logger.Setup(x => x.Error(It.IsAny(), It.IsAny(), It.IsAny())); @@ -381,7 +424,12 @@ public async Task ShouldPropagateInvalidBookmarkException() var discovery = new Mock(); discovery.Setup( - x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.From("Invalid bookmark"))) + x => x.DiscoverAsync( + It.IsAny(), + "", + It.IsAny(), + It.IsAny(), + It.IsAny())) .Throws(error); var logger = new Mock(); @@ -399,8 +447,7 @@ public async Task ShouldPropagateInvalidBookmarkException() routingTable, AccessMode.Read, "", - null, - Bookmarks.From("Invalid bookmark"))); + SessionConfig.Default, Bookmarks.From("Invalid bookmarks"))); exc.Should().Be(error); logger.Verify(x => x.Error(error, It.IsAny(), It.IsAny())); @@ -420,21 +467,31 @@ public async Task ShouldTryNextRouterIfNoReader() var routingTable = new RoutingTable(null, new List { uriA, uriB }); var poolManagerMock = new Mock(); - poolManagerMock.SetupSequence(x => x.CreateClusterConnectionAsync(It.IsAny(), - It.IsAny())) + poolManagerMock.SetupSequence( + x => x.CreateClusterConnectionAsync( + It.IsAny(), + It.IsAny())) .ReturnsAsync(connA) .ReturnsAsync(connB); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .Callback( - (IConnection c, string _, SessionConfig _, Bookmarks _) => + (IConnection c, string _, SessionConfig _, Bookmarks _, IHomeDbCache _) => throw new NotSupportedException($"Unknown uri: {c.Server.Address}")); - discovery.Setup(x => x.DiscoverAsync(connA, "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync(connA, "", null, Bookmarks.Empty, It.IsAny())) .ReturnsAsync(NewRoutingTable(new[] { uriX }, new Uri[0], new[] { uriX })); - discovery.Setup(x => x.DiscoverAsync(connB, "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync(connB, "", null, Bookmarks.Empty, It.IsAny())) .ReturnsAsync(NewRoutingTable(new[] { uriY }, new[] { uriY }, new[] { uriY })); var manager = NewRoutingTableManager(routingTable, poolManagerMock.Object, discovery.Object); @@ -466,12 +523,19 @@ public async Task ShouldAcceptRoutingTableIfNoWriter() .ReturnsAsync(connA); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .Callback( - (IConnection c, string _, SessionConfig _, Bookmarks _) => + (IConnection c, string _, SessionConfig _, Bookmarks _, IHomeDbCache _) => throw new NotSupportedException($"Unknown uri: {c.Server?.Address}")); - discovery.Setup(x => x.DiscoverAsync(connA, "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync(connA, "", null, Bookmarks.Empty, It.IsAny())) .ReturnsAsync(NewRoutingTable(new[] { uriX }, new[] { uriX })); var manager = NewRoutingTableManager(routingTable, poolManagerMock.Object, discovery.Object); @@ -514,11 +578,17 @@ public async Task ShouldTryNextRouterOnDiscoveryError() var discovery = new Mock(); discovery - .Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + .Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .Throws(new ServiceUnavailableException("something went wrong")); discovery - .Setup(x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty)) + .Setup(x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty, It.IsAny())) .ReturnsAsync(routingTable); var logger = new Mock(); @@ -540,10 +610,18 @@ public async Task ShouldTryNextRouterOnDiscoveryError() result.Should().Be(routingTable); discovery .Verify( - x => x.DiscoverAsync(It.Is(c => c != connE), "", null, Bookmarks.Empty), + x => x.DiscoverAsync( + It.Is(c => c != connE), + "", + null, + Bookmarks.Empty, + It.IsAny()), Times.Exactly(4)); - discovery.Verify(x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty), Times.Once); + discovery.Verify( + x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty, It.IsAny()), + Times.Once); + logger.Verify( x => x.Warn( @@ -575,10 +653,18 @@ public async Task ShouldTryNextRouterOnConnectionError() .ReturnsAsync(connE); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .Throws(new ServiceUnavailableException("something went wrong")); - discovery.Setup(x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty)).ReturnsAsync(routingTable); + discovery.Setup( + x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty, It.IsAny())) + .ReturnsAsync(routingTable); var logger = new Mock(); logger.Setup( @@ -610,10 +696,14 @@ await manager.UpdateRoutingTableAsync( It.Is(c => c != connE), "", null, - Bookmarks.Empty), + Bookmarks.Empty, + It.IsAny()), Times.Never); - discovery.Verify(x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty), Times.Once); + discovery.Verify( + x => x.DiscoverAsync(connE, "", null, Bookmarks.Empty, It.IsAny()), + Times.Once); + logger.Verify( x => x.Warn( It.IsAny(), @@ -722,13 +812,31 @@ public async Task ShouldAllowMultipleRoutingTables(AccessMode mode) new RoutingTable("bar", new[] { server07 }, new[] { server08 }, new[] { server09 }, 100); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(defaultRoutingTable); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "foo", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "foo", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(fooRoutingTable); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "bar", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "bar", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(barRoutingTable); var poolManager = new Mock(); @@ -746,12 +854,12 @@ public async Task ShouldAllowMultipleRoutingTables(AccessMode mode) TimeSpan.MaxValue); // When - var routingTable1 = await manager.EnsureRoutingTableForModeAsync(mode, null, null, Bookmarks.Empty); + var routingTable1 = await manager.EnsureRoutingTableForModeAsync(mode, null, false, null, Bookmarks.Empty); var routingTable2 = - await manager.EnsureRoutingTableForModeAsync(mode, "foo", null, Bookmarks.Empty); + await manager.EnsureRoutingTableForModeAsync(mode, "foo", false, null, Bookmarks.Empty); var routingTable3 = - await manager.EnsureRoutingTableForModeAsync(mode, "bar", null, Bookmarks.Empty); + await manager.EnsureRoutingTableForModeAsync(mode, "bar", false, null, Bookmarks.Empty); routingTable1.Should().Be(defaultRoutingTable); routingTable2.Should().Be(fooRoutingTable); @@ -773,10 +881,22 @@ public async Task ShouldRemoveStaleEntriesOnUpdate() new RoutingTable("bar", new[] { server07 }, new[] { server08 }, new[] { server09 }, 4); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "foo", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "foo", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(fooRoutingTable); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "bar", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "bar", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(barRoutingTable); var poolManager = new Mock(); @@ -795,7 +915,7 @@ public async Task ShouldRemoveStaleEntriesOnUpdate() // When var routingTable1 = - await manager.EnsureRoutingTableForModeAsync(AccessMode.Read, "foo", null, Bookmarks.Empty); + await manager.EnsureRoutingTableForModeAsync(AccessMode.Read, "foo", false, null, Bookmarks.Empty); routingTable1.Should().Be(fooRoutingTable); @@ -803,7 +923,7 @@ public async Task ShouldRemoveStaleEntriesOnUpdate() // An update should trigger an implicit clean-up of stale entries var routingTable2 = - await manager.EnsureRoutingTableForModeAsync(AccessMode.Read, "bar", null, Bookmarks.Empty); + await manager.EnsureRoutingTableForModeAsync(AccessMode.Read, "bar", false, null, Bookmarks.Empty); routingTable2.Should().Be(barRoutingTable); @@ -821,13 +941,31 @@ public async Task ShouldIsolateEntriesFromFailures() new RoutingTable("foo", new[] { server04 }, new[] { server05 }, new[] { server06 }, 80); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(defaultRoutingTable); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "foo", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "foo", + null, + Bookmarks.Empty, + It.IsAny())) .ReturnsAsync(fooRoutingTable); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "bar", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "bar", + null, + Bookmarks.Empty, + It.IsAny())) .ThrowsAsync(new FatalDiscoveryException("message")); var poolManager = new Mock(); @@ -846,15 +984,16 @@ public async Task ShouldIsolateEntriesFromFailures() // When var routingTable1 = - await manager.EnsureRoutingTableForModeAsync(AccessMode.Write, null, null, Bookmarks.Empty); + await manager.EnsureRoutingTableForModeAsync(AccessMode.Write, null, false, null, Bookmarks.Empty); var routingTable2 = - await manager.EnsureRoutingTableForModeAsync(AccessMode.Write, "foo", null, Bookmarks.Empty); + await manager.EnsureRoutingTableForModeAsync(AccessMode.Write, "foo", false, null, Bookmarks.Empty); routingTable1.Should().Be(defaultRoutingTable); routingTable2.Should().Be(fooRoutingTable); - manager.Awaiting(m => m.EnsureRoutingTableForModeAsync(AccessMode.Write, "bar", null, Bookmarks.Empty)) + manager.Awaiting( + m => m.EnsureRoutingTableForModeAsync(AccessMode.Write, "bar", false, null, Bookmarks.Empty)) .Should() .Throw(); @@ -868,7 +1007,13 @@ public void ShouldThrowOnFatalDiscovery() var error = new FatalDiscoveryException("message"); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "bar", null, Bookmarks.Empty)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "bar", + null, + Bookmarks.Empty, + It.IsAny())) .ThrowsAsync(error); var poolManager = new Mock(); @@ -885,7 +1030,8 @@ public void ShouldThrowOnFatalDiscovery() Mock.Of(), TimeSpan.MaxValue); - manager.Awaiting(m => m.EnsureRoutingTableForModeAsync(AccessMode.Write, "bar", null, Bookmarks.Empty)) + manager.Awaiting( + m => m.EnsureRoutingTableForModeAsync(AccessMode.Write, "bar", false, null, Bookmarks.Empty)) .Should() .Throw() .Which.Should() @@ -899,7 +1045,13 @@ public async Task ShouldPassBookmarkDownToDiscovery() var rt = new RoutingTable("foo", new[] { server01 }, new[] { server02 }, new[] { server03 }, 10); var discovery = new Mock(); - discovery.Setup(x => x.DiscoverAsync(It.IsAny(), "foo", null, bookmark)) + discovery.Setup( + x => x.DiscoverAsync( + It.IsAny(), + "foo", + null, + bookmark, + It.IsAny())) .ReturnsAsync(rt); var poolManager = new Mock(); @@ -917,10 +1069,17 @@ public async Task ShouldPassBookmarkDownToDiscovery() TimeSpan.MaxValue); var routingTable = - await manager.EnsureRoutingTableForModeAsync(AccessMode.Write, "foo", null, bookmark); + await manager.EnsureRoutingTableForModeAsync(AccessMode.Write, "foo", false, null, bookmark); routingTable.Should().Be(rt); - discovery.Verify(x => x.DiscoverAsync(It.IsAny(), "foo", null, bookmark), Times.Once); + discovery.Verify( + x => x.DiscoverAsync( + It.IsAny(), + "foo", + null, + bookmark, + It.IsAny()), + Times.Once); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableTests.cs index 155ed39dc..61e5ba3a0 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/RoutingTableTests.cs @@ -446,4 +446,4 @@ public async Task ShouldReturnExpectedValue(int expiresAfterSecs, int expiredFor } */ } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/SessionConfigBuilderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/SessionConfigBuilderTests.cs index 0dbc661c2..8289e69f4 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/SessionConfigBuilderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/SessionConfigBuilderTests.cs @@ -47,7 +47,7 @@ public void WithNotifications_ShouldSetCategoryWithClassification( config .Which .DisabledCategories.Should() - .BeEquivalentTo([category]); + .BeEquivalentTo(category); config .Which @@ -80,7 +80,7 @@ public void WithNotifications_ShouldSetCategory( config .Which .DisabledCategories.Should() - .BeEquivalentTo([outCat]); + .BeEquivalentTo(outCat); config .Which @@ -102,7 +102,7 @@ public void WithNotifications_ShouldSetMultipleCategories() config .Which .DisabledCategories.Should() - .BeEquivalentTo([Category.Deprecation, Category.Hint]); + .BeEquivalentTo(Category.Deprecation, Category.Hint); config .Which @@ -124,7 +124,7 @@ public void WithNotifications_ShouldSetMultipleClassifications() config .Which .DisabledCategories.Should() - .BeEquivalentTo([Category.Deprecation, Category.Hint]); + .BeEquivalentTo(Category.Deprecation, Category.Hint); config .Which diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/TestDriverContext.cs b/Neo4j.Driver/Neo4j.Driver.Tests/TestDriverContext.cs index 5cba8fe7c..d0f2bced7 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/TestDriverContext.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/TestDriverContext.cs @@ -22,7 +22,9 @@ internal static class TestDriverContext { static TestDriverContext() { - MockContext = new DriverContext(new Uri("bolt://localhost:7687"), AuthTokenManagers.None, + MockContext = new DriverContext( + new Uri("bolt://localhost:7687"), + AuthTokenManagers.None, new ConfigBuilder(new Config()).Build()); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/CollectionExtensionsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/CollectionExtensionsTests.cs index 1af4a4899..23dc54651 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/CollectionExtensionsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/CollectionExtensionsTests.cs @@ -308,8 +308,7 @@ public void ShouldHandleCollectionsOfArbitraryObjects() innerCollection.Should() .Contain( o => o is IDictionary && - ((IDictionary)o).Contains( - new KeyValuePair("Key1", "value1"))); + ((IDictionary)o).Contains(new KeyValuePair("Key1", "value1"))); } [Fact] @@ -464,4 +463,4 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/MockExtensionsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/MockExtensionsTests.cs index 00747ef67..f51e3344f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/MockExtensionsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/MockExtensionsTests.cs @@ -21,11 +21,6 @@ namespace Neo4j.Driver.Tests.TestUtil; public class MockExtensionsTests { - public interface IntGetter - { - int Value { get; } - } - [Fact] public void ShouldSetupSequentialReturns() { @@ -36,4 +31,9 @@ public void ShouldSetupSequentialReturns() mock.Object.Value.Should().Be(2); mock.Object.Value.Should().Be(3); } + + public interface IntGetter + { + int Value { get; } + } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/TestRecord.cs b/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/TestRecord.cs index 1c51c254a..a7e3d0d1d 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/TestRecord.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/TestUtil/TestRecord.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs index 3c128017e..f6edbdc31 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs @@ -14,11 +14,13 @@ // limitations under the License. using System; +using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Moq; using Neo4j.Driver.Internal; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.Logging; using Neo4j.Driver.Internal.Protocol; using Neo4j.Driver.Internal.Telemetry; @@ -43,7 +45,8 @@ public async Task ShouldSaveBookmark() Mock.Of(), null, null, - bookmarks); + bookmarks, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( null, @@ -52,7 +55,10 @@ await tx.BeginTransactionAsync( mockProtocol.Verify( x => x.BeginTransactionAsync( It.IsAny(), - It.IsAny()), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); } } @@ -64,8 +70,11 @@ public async Task ShouldDelegateToProtocolBeginTxMethod() { var protocol = new Mock(); var mockConn = NewMockedConnection(protocol); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new ("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( TransactionConfig.Default, @@ -74,7 +83,10 @@ await tx.BeginTransactionAsync( protocol.Verify( x => x.BeginTransactionAsync( It.IsAny(), - It.IsAny()), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); } } @@ -86,7 +98,8 @@ public async void ShouldDelegateToBoltProtocol() { var mockProtocol = new Mock(); var mockConn = NewMockedConnection(mockProtocol); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), NullLogger.Instance); + var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); var query = new Query("lala"); await tx.RunAsync(query); @@ -98,15 +111,22 @@ public async void ShouldDelegateToBoltProtocol() query, false, It.IsAny(), - It.IsAny())); + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())); } [Fact] public async void ShouldThrowExceptionIfPreviousTxFailed() { var mockConn = new Mock(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); + tx.TransactionError = new Exception(); await tx.MarkToCloseAsync(); @@ -119,8 +139,12 @@ public async void ShouldThrowExceptionIfFailedToRunAndFetchResult() { var mockProtocol = new Mock(); var mockConn = NewMockedConnection(mockProtocol); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); + var query = new Query("lala"); mockProtocol.Setup( @@ -130,7 +154,10 @@ public async void ShouldThrowExceptionIfFailedToRunAndFetchResult() query, false, It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .Throws(); var error = await ExceptionAsync(() => tx.RunAsync(query)); @@ -146,7 +173,8 @@ public async void ShouldCommitOnSuccess() var mockProtocol = new Mock(); var mockConn = NewMockedConnection(mockProtocol); var mockHandler = new Mock(); - var tx = new AsyncTransaction(mockConn.Object, mockHandler.Object, NullLogger.Instance); + var tx = new AsyncTransaction(mockConn.Object, mockHandler.Object, NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); mockConn.Invocations.Clear(); await tx.CommitAsync(); @@ -161,7 +189,8 @@ public async void ShouldRollbackOnFailure() var mockProtocol = new Mock(); var mockConn = NewMockedConnection(mockProtocol); var mockHandler = new Mock(); - var tx = new AsyncTransaction(mockConn.Object, mockHandler.Object, NullLogger.Instance); + var tx = new AsyncTransaction(mockConn.Object, mockHandler.Object, NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); mockConn.Invocations.Clear(); await tx.RollbackAsync(); @@ -174,7 +203,8 @@ public async Task ShouldNotDisposeIfAlreadyClosed() { var mockConn = NewMockedConnection(); var mockHandler = new Mock(); - var tx = new AsyncTransaction(mockConn.Object, mockHandler.Object, NullLogger.Instance); + var tx = new AsyncTransaction(mockConn.Object, mockHandler.Object, NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); mockConn.Invocations.Clear(); await tx.CommitAsync(); @@ -188,8 +218,12 @@ public class MarkToClosedMethod public async Task ShouldNotEnqueueMoreMessagesAfterMarkToClosed() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); + mockConn.Invocations.Clear(); await tx.MarkToCloseAsync(); @@ -202,8 +236,12 @@ public async Task ShouldNotEnqueueMoreMessagesAfterMarkToClosed() public async Task ShouldThrowExceptionToRunAfterMarkToClosed() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); + mockConn.Invocations.Clear(); tx.TransactionError = new Exception(); await tx.MarkToCloseAsync(); @@ -220,8 +258,12 @@ public async Task ShouldThrowExceptionToRunAfterMarkToClosed() public async Task ShouldNotEnqueueMoreMessagesAfterMarkToClosedInCommitAsync() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); + mockConn.Invocations.Clear(); tx.TransactionError = new Exception(); await tx.MarkToCloseAsync(); @@ -237,8 +279,12 @@ public async Task ShouldNotEnqueueMoreMessagesAfterMarkToClosedInCommitAsync() public async Task ShouldNotEnqueueMoreMessagesAfterMarkToClosedInRollbackAsync() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); + mockConn.Invocations.Clear(); await tx.MarkToCloseAsync(); @@ -254,7 +300,8 @@ public class IsOpenTests public async Task ShouldBeOpenWhenConstructed() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), NullLogger.Instance); + var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( TransactionConfig.Default, @@ -267,8 +314,11 @@ await tx.BeginTransactionAsync( public async Task ShouldBeOpenWhenRun() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( TransactionConfig.Default, @@ -283,8 +333,11 @@ await tx.BeginTransactionAsync( public async Task ShouldBeClosedWhenFailed() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( TransactionConfig.Default, @@ -299,8 +352,11 @@ await tx.BeginTransactionAsync( public async Task ShouldBeClosedWhenCommitted() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( TransactionConfig.Default, @@ -315,8 +371,11 @@ await tx.BeginTransactionAsync( public async Task ShouldBeClosedWhenRollBacked() { var mockConn = NewMockedConnection(); - var tx = new AsyncTransaction(mockConn.Object, Mock.Of(), - NullLogger.Instance); + var tx = new AsyncTransaction( + mockConn.Object, + Mock.Of(), + NullLogger.Instance, + driverContext: new DriverContext(new("bolt://localhost"), null, new Config())); await tx.BeginTransactionAsync( TransactionConfig.Default, @@ -327,4 +386,4 @@ await tx.BeginTransactionAsync( tx.IsOpen.Should().BeFalse(); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Types/DurationTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Types/DurationTests.cs index 32c934bcf..0502706b9 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Types/DurationTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Types/DurationTests.cs @@ -268,4 +268,4 @@ public void ShouldThrowWhenConversionIsNotSupported() testAction.Should().Throw(); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Types/LocalTimeTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Types/LocalTimeTests.cs index abcf11295..30eebd46a 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Types/LocalTimeTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Types/LocalTimeTests.cs @@ -310,4 +310,4 @@ public void ShouldThrowWhenConversionIsNotSupported() testAction.Should().Throw(); } } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Types/NotificationTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Types/NotificationTests.cs index 1b04cfa7c..070f6b5ad 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Types/NotificationTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Types/NotificationTests.cs @@ -32,25 +32,27 @@ public void ShouldSerialize() new InputPosition(0, 1, 2), "WARNING", "HINT"); - + var text = JsonConvert.SerializeObject(notification, Formatting.Indented).ReplaceLineEndings(); - - text.Should().BeEquivalentTo(""" - { - "RawSeverityLevel": "WARNING", - "SeverityLevel": 1, - "RawCategory": "HINT", - "Category": 1, - "Code": "code", - "Title": "title", - "Description": "description", - "Position": { - "Offset": 0, - "Line": 1, - "Column": 2 - }, - "Severity": "WARNING" - } - """.ReplaceLineEndings()); + + text.Should() + .BeEquivalentTo( + """ + { + "RawSeverityLevel": "WARNING", + "SeverityLevel": 1, + "RawCategory": "HINT", + "Category": 1, + "Code": "code", + "Title": "title", + "Description": "description", + "Position": { + "Offset": 0, + "Line": 1, + "Column": 2 + }, + "Severity": "WARNING" + } + """.ReplaceLineEndings()); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithOffsetTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithOffsetTests.cs index 4f4a576ca..4a35a0851 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithOffsetTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithOffsetTests.cs @@ -467,4 +467,4 @@ public void ShouldCreateMaxZonedDateTimeFromComponents() var zone = new ZonedDateTime(999_999_999, 12, 31, 23, 59, 59, Zone.Of(0)); zone.UtcSeconds.Should().Be(TemporalHelpers.MaxUtcForZonedDateTime); } -} \ No newline at end of file +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithZoneIdTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithZoneIdTests.cs index 43f20e1a0..aa21d18ad 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithZoneIdTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Types/ZonedDateTimeWithZoneIdTests.cs @@ -158,8 +158,7 @@ public void ShouldThrowOnInvalidSecond(int second) [InlineData(999_999_999 + 1)] public void ShouldThrowOnInvalidNanosecond(int nanosecond) { - var ex = Record.Exception( - () => new ZonedDateTime(1990, 1, 1, 0, 0, 0, nanosecond, Zone.Of("Europe/Athens"))); + var ex = Record.Exception(() => new ZonedDateTime(1990, 1, 1, 0, 0, 0, nanosecond, Zone.Of("Europe/Athens"))); ex.Should().NotBeNull().And.BeOfType(); } @@ -452,6 +451,7 @@ public void ShouldThrowExceptionWhenNonMonotonicTimeProvidedAndUnknownZoneId(Fun public void ShouldNotThrowExceptionWhenNonMonotonicTimeProvidedAndUnknownZoneId() { Record.Exception(() => new ZonedDateTime(new LocalDateTime(DateTime.Now), new ZoneId("Europe/Neo4j"))) - .Should().BeNull(); + .Should() + .BeNull(); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ValueExtensionsTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ValueExtensionsTests.cs index a8be70b32..96c17b7db 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/ValueExtensionsTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/ValueExtensionsTests.cs @@ -351,7 +351,7 @@ public void ShouldConvertToPath() node.Properties["key1"].As().Should().Be("value1"); node.Properties["key2"].As().Should().Be("2"); - obj.As().Should().Be($"Path with 0 segments"); + obj.As().Should().Be("Path with 0 segments"); } } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/AcquireStatus.cs b/Neo4j.Driver/Neo4j.Driver/Internal/AcquireStatus.cs index f0921937e..4bf39dccc 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/AcquireStatus.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/AcquireStatus.cs @@ -1,5 +1,4 @@ - -// Copyright (c) "Neo4j" +// Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] // // Licensed under the Apache License, Version 2.0 (the "License"). diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs b/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs index ef6c05052..09bb6b3c0 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/AsyncSession.cs @@ -34,6 +34,7 @@ internal partial class AsyncSession : AsyncQueryRunner, IInternalAsyncSession private readonly IConnectionProvider _connectionProvider; private readonly AccessMode _defaultMode; + private readonly DriverContext _driverContext; private readonly long _fetchSize; private readonly ILogger _logger; @@ -67,6 +68,7 @@ public AsyncSession( _logger = logger; _retryLogic = retryLogic; _reactive = reactive; + _driverContext = config.DriverContext;; _database = config.Database; _defaultMode = config.DefaultAccessMode; @@ -86,6 +88,27 @@ public AsyncSession( } TelemetryEnabled = telemetryEnabled; + + config.OnPinDatabase = OnPinDatabase; + } + + private void OnPinDatabase(string db) + { + if(_connectionProvider.IsDirectDriver) + { + // don't pin + return; + } + + if (string.IsNullOrWhiteSpace(_database)) + { + _logger.Info($"Database '{db}' is pinned to the session."); + _database = db; + } + else + { + _logger.Info($"Database {_database} is already pinned to the session, ignoring {db}."); + } } internal bool TelemetryEnabled { get; set; } @@ -145,8 +168,10 @@ public async Task BeginTransactionAsync( var config = BuildTransactionConfig(action); return await TryExecuteAsync( _logger, - () => BeginTransactionWithoutLoggingAsync(mode, - config, disposeUnconsumedSessionResult, + () => BeginTransactionWithoutLoggingAsync( + mode, + config, + disposeUnconsumedSessionResult, new TransactionInfo(QueryApiType.UnmanagedTransaction, TelemetryEnabled, true))) .ConfigureAwait(false); } @@ -176,7 +201,7 @@ public Task RunAsync( { Query = query, Reactive = _reactive, - Database = _database, + Database = SessionConfig.Database ?? _database, Bookmarks = LastBookmarks, Config = options, SessionConfig = SessionConfig, @@ -188,7 +213,8 @@ public Task RunAsync( TelemetryEnabled, false) }, - _notificationsConfig) + _notificationsConfig, + _driverContext?.HomeDbCache) .ConfigureAwait(false); }); @@ -196,7 +222,8 @@ public Task RunAsync( return result; } - public Task> PipelinedExecuteReadAsync(Func>> func, + public Task> PipelinedExecuteReadAsync( + Func>> func, TransactionConfig config) { return RunTransactionAsync( @@ -206,24 +233,17 @@ public Task> PipelinedExecuteReadAsync(Func> PipelinedExecuteWriteAsync(Func>> func, + public Task> PipelinedExecuteWriteAsync( + Func>> func, TransactionConfig config) { - return RunTransactionAsync(AccessMode.Write, func, config, + return RunTransactionAsync( + AccessMode.Write, + func, + config, new TransactionInfo(QueryApiType.DriverLevel, TelemetryEnabled, false)); } - private TransactionConfig BuildTransactionConfig(Action action) - { - if (action == null) - { - return TransactionConfig.Default; - } - var builder = new TransactionConfigBuilder(_logger, new TransactionConfig()); - action.Invoke(builder); - return builder.Build(); - } - public Task ReadTransactionAsync( Func> work, Action action = null) @@ -278,6 +298,18 @@ public Task ExecuteWriteAsync( return RunTransactionAsync(AccessMode.Write, work, BuildTransactionConfig(action)); } + private TransactionConfig BuildTransactionConfig(Action action) + { + if (action == null) + { + return TransactionConfig.Default; + } + + var builder = new TransactionConfigBuilder(_logger, new TransactionConfig()); + action.Invoke(builder); + return builder.Build(); + } + private async Task GetBookmarksAsync() { return _initialBookmarks == null @@ -310,13 +342,15 @@ private Task RunTransactionAsync( TransactionConfig config, TransactionInfo transactionInfo = null) { - transactionInfo ??= new TransactionInfo (QueryApiType.TransactionFunction, TelemetryEnabled, true); + transactionInfo ??= new TransactionInfo(QueryApiType.TransactionFunction, TelemetryEnabled, true); return TryExecuteAsync( _logger, () => _retryLogic.RetryAsync( async () => { - var tx = await BeginTransactionWithoutLoggingAsync(mode, config, true, transactionInfo).ConfigureAwait(false); + var tx = await BeginTransactionWithoutLoggingAsync(mode, config, true, transactionInfo) + .ConfigureAwait(false); + try { var result = await work(tx).ConfigureAwait(false); @@ -362,7 +396,8 @@ private async Task BeginTransactionWithoutLoggingAsyn _reactive, _fetchSize, SessionConfig, - _notificationsConfig); + _notificationsConfig, + _driverContext); await tx.BeginTransactionAsync(config, transactionInfo).ConfigureAwait(false); _transaction = tx; @@ -383,10 +418,6 @@ private async Task AcquireConnectionAndDbNameAsync(AccessMode mode, bool forceAu LastBookmarks, forceAuth) .ConfigureAwait(false); - - //Update the database. If a routing request occurred it may have returned a differing DB alias name that needs to be used for the - //rest of the sessions lifetime. - _database = _connection.Database; } protected override void Dispose(bool disposing) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/AsyncTransaction.cs b/Neo4j.Driver/Neo4j.Driver/Internal/AsyncTransaction.cs index 63bc7fb58..428e75933 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/AsyncTransaction.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/AsyncTransaction.cs @@ -18,6 +18,7 @@ using System.Threading; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Protocol; @@ -31,6 +32,7 @@ internal class AsyncTransaction : AsyncQueryRunner, IInternalAsyncTransaction, I private static readonly IState Failed = new FailedState(); private readonly IConnection _connection; + private readonly DriverContext _driverContext; private readonly long _fetchSize; private readonly ILogger _logger; private readonly INotificationsConfig _notificationsConfig; @@ -54,7 +56,8 @@ public AsyncTransaction( bool reactive = false, long fetchSize = Config.Infinite, SessionConfig sessionConfig = null, - INotificationsConfig notificationsConfig = null) + INotificationsConfig notificationsConfig = null, + DriverContext driverContext = null) { _connection = new TransactionConnection(this, connection); _resourceHandler = resourceHandler ?? throw new ArgumentNullException(nameof(resourceHandler)); @@ -65,6 +68,7 @@ public AsyncTransaction( _fetchSize = fetchSize; _sessionConfig = sessionConfig; _notificationsConfig = notificationsConfig; + _driverContext = driverContext; } private string Database { get; set; } @@ -80,16 +84,6 @@ public void UpdateBookmarks(Bookmarks bookmarks, IDatabaseInfo dbInfo = null) _bookmarks = bookmarks; } - /// - /// Sets the error for the transaction if it is not already set. - /// This avoids the exception changing if multiple errors occur. - /// - /// The first exception to occur in the transaction. - internal void SetErrorIfNull(Exception ex) - { - TransactionError ??= ex; - } - public bool IsErrored(out Exception ex) { if (TransactionError != null) @@ -110,7 +104,17 @@ public override Task RunAsync(Query query) { throw new TransactionTerminatedException(TransactionError); } - var result = _state.RunAsync(query, _connection, _logger, _reactive, _fetchSize, this, out var nextState); + + var result = _state.RunAsync( + query, + _connection, + _logger, + _reactive, + _fetchSize, + this, + _driverContext.HomeDbCache, + out var nextState); + _state = nextState; _results.Add(result); return result; @@ -122,6 +126,7 @@ public async Task CommitAsync() { throw new TransactionTerminatedException(TransactionError); } + try { await DiscardUnconsumed().ConfigureAwait(false); @@ -150,10 +155,20 @@ public async Task RollbackAsync() public TransactionConfig TransactionConfig { get; private set; } + /// + /// Sets the error for the transaction if it is not already set. This avoids the exception changing if multiple + /// errors occur. + /// + /// The first exception to occur in the transaction. + internal void SetErrorIfNull(Exception ex) + { + TransactionError ??= ex; + } + public Task BeginTransactionAsync(TransactionConfig config, TransactionInfo transactionInfo) { TransactionConfig = config; - + return _connection.BeginTransactionAsync( new BeginTransactionParams( Database, @@ -161,7 +176,8 @@ public Task BeginTransactionAsync(TransactionConfig config, TransactionInfo tran TransactionConfig, _sessionConfig, _notificationsConfig, - transactionInfo)); + transactionInfo), + _driverContext?.HomeDbCache); } public async Task MarkToCloseAsync() @@ -245,6 +261,7 @@ Task RunAsync( bool reactive, long fetchSize, AsyncTransaction transaction, + IHomeDbCache homeDbCache, out IState nextState); Task CommitAsync( @@ -267,10 +284,11 @@ public Task RunAsync( bool reactive, long fetchSize, AsyncTransaction transaction, + IHomeDbCache homeDbCache, out IState nextState) { nextState = Active; - return connection.RunInExplicitTransactionAsync(query, reactive, fetchSize, transaction); + return connection.RunInExplicitTransactionAsync(query, reactive, fetchSize, transaction, homeDbCache); } public Task CommitAsync( @@ -301,6 +319,7 @@ public Task RunAsync( bool reactive, long fetchSize, AsyncTransaction transaction, + IHomeDbCache homeDbCache, out IState nextState) { throw new TransactionClosedException( @@ -335,6 +354,7 @@ public Task RunAsync( bool reactive, long fetchSize, AsyncTransaction transaction, + IHomeDbCache homeDbCache, out IState nextState) { throw new TransactionClosedException( @@ -369,6 +389,7 @@ public Task RunAsync( bool reactive, long fetchSize, AsyncTransaction transaction, + IHomeDbCache homeDbCache, out IState nextState) { throw new TransactionTerminatedException(transaction.TransactionError); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthToken.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthToken.cs index a42a49e6b..26167b053 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthToken.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthToken.cs @@ -20,7 +20,7 @@ namespace Neo4j.Driver.Internal.Auth; /// A simple common token for authentication schemes that easily convert to an auth token map -internal sealed class AuthToken : IAuthToken +internal class AuthToken : IAuthToken { public const string SchemeKey = "scheme"; public const string PrincipalKey = "principal"; @@ -28,7 +28,7 @@ internal sealed class AuthToken : IAuthToken public const string RealmKey = "realm"; public const string ParametersKey = "parameters"; - public AuthToken(IDictionary content) + internal AuthToken(IDictionary content) { content = content ?? throw new ArgumentNullException(nameof(content)); Content = new Dictionary(); @@ -41,6 +41,10 @@ public AuthToken(IDictionary content) } } + protected AuthToken() : this(new Dictionary()) + { + } + public IDictionary Content { get; } public override bool Equals(object obj) @@ -50,6 +54,11 @@ public override bool Equals(object obj) private bool Equals(AuthToken other) { + if(other is null) + { + return false; + } + if (ReferenceEquals(this, other)) { return true; @@ -64,23 +73,29 @@ private bool Equals(AuthToken other) public override int GetHashCode() { - return Content != null ? Content.GetHashCode() : 0; - } -} + // combine hash codes of all key-value pairs in the dictionary. We sort the dictionary by key to ensure + // that the hash code is the same for all dictionaries that have the same key-value pairs but in different + // order. -internal static class AuthTokenExtensions -{ - public static IDictionary AsDictionary(this IAuthToken authToken) - { - if (authToken is not AuthToken token) + var hash = 17; + foreach (var kvp in Content.OrderBy(kvp => kvp.Key)) { - throw new ClientException( - $"Unknown authentication token, `{authToken}`. Please use one of the supported " + - $"tokens from `{nameof(AuthTokens)}`."); + hash = hash * 31 + (kvp.Key?.GetHashCode() ?? 0); + hash = hash * 31 + (kvp.Value?.GetHashCode() ?? 0); } - return token.Content - .Where(kvp => kvp.Value is not null) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + return hash; + } + + public string Scheme => Content.GetValueOrDefault(SchemeKey) as string; + public string Principal => Content.GetValueOrDefault(PrincipalKey) as string; + public string Realm => Content.GetValueOrDefault(RealmKey) as string; + + public override string ToString() + { + var scheme = Content.ContainsKey(SchemeKey) ? Content[SchemeKey] ?? "(null)" : "(none)"; + var principal = Content.ContainsKey(PrincipalKey) ? Content[PrincipalKey] ?? "(null)" : "(none)"; + var realm = Content.ContainsKey(RealmKey) ? Content[RealmKey] ?? "(null)" : "(none)"; + return $"BasicAuthToken[scheme: {scheme}, principal: {principal}, realm: {realm}]"; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthTokenExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthTokenExtensions.cs new file mode 100644 index 000000000..cff8a5f02 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/AuthTokenExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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 System.Linq; + +namespace Neo4j.Driver.Internal.Auth; + +internal static class AuthTokenExtensions +{ + public static IDictionary AsDictionary(this IAuthToken authToken) + { + if (authToken is not AuthToken token) + { + throw new ClientException( + $"Unknown authentication token, `{authToken}`. Please use one of the supported " + + $"tokens from `{nameof(AuthTokens)}`."); + } + + return token.Content + .Where(kvp => kvp.Value is not null) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/BasicAuthToken.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/BasicAuthToken.cs new file mode 100644 index 000000000..7a8834261 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/BasicAuthToken.cs @@ -0,0 +1,36 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. + +namespace Neo4j.Driver.Internal.Auth; + +internal class BasicAuthToken : AuthToken +{ + public BasicAuthToken(string username, string password, string realm = null) + { + Content[SchemeKey] = "basic"; + Content[PrincipalKey] = username; + Content[CredentialsKey] = password; + if (realm != null) + { + Content[RealmKey] = realm; + } + } + + /// + public override int GetHashCode() + { + return Content[PrincipalKey].GetHashCode(); + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/BearerAuthToken.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/BearerAuthToken.cs new file mode 100644 index 000000000..d5fbfb9cc --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/BearerAuthToken.cs @@ -0,0 +1,39 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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; + +namespace Neo4j.Driver.Internal.Auth; + +internal class BearerAuthToken : AuthToken +{ + public BearerAuthToken(string token) + { + if (string.IsNullOrEmpty(token)) + { + throw new ArgumentException("Bearer token cannot be null or an empty string"); + } + + Content[SchemeKey] = "bearer"; + Content[CredentialsKey] = token; + } + + public string Token => Content[CredentialsKey] as string ?? string.Empty; + + public override string ToString() + { + return $"BearerAuthToken{{scheme: {Scheme}, token: {Token.Substring(0, 5)}...}}"; + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/CustomAuthToken.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/CustomAuthToken.cs new file mode 100644 index 000000000..12132ea40 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/CustomAuthToken.cs @@ -0,0 +1,69 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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 System.Linq; + +namespace Neo4j.Driver.Internal.Auth; + +internal class CustomAuthToken : AuthToken +{ + public CustomAuthToken( + string principal, + string credentials, + string realm, + string scheme, + Dictionary parameters = null) + { + if (principal != null) + { + Content[PrincipalKey] = principal; + } + + if (!string.IsNullOrEmpty(scheme)) + { + Content[SchemeKey] = scheme; + } + + if (!string.IsNullOrEmpty(credentials)) + { + Content[CredentialsKey] = credentials; + } + + if (!string.IsNullOrEmpty(realm)) + { + Content[RealmKey] = realm; + } + + if (parameters != null) + { + Content[ParametersKey] = parameters; + } + } + + public override string ToString() + { + var scheme = Content.ContainsKey(SchemeKey) ? Content[SchemeKey] ?? "(null)" : "(none)"; + var principal = Content.ContainsKey(PrincipalKey) ? Content[PrincipalKey] ?? "(null)" : "(none)"; + var realm = Content.ContainsKey(RealmKey) ? Content[RealmKey] ?? "(null)" : "(none)"; + return $"CustomAuthToken[scheme: {scheme}, principal: {principal}, realm: {realm}" + + // list of other keys present in Content + Content.Keys + .Where(key => key != SchemeKey && key != PrincipalKey && key != RealmKey) + .Select(key => $", {key}: {Content[key] ?? "(null)"}") + .Aggregate("", (acc, next) => acc + next) + + "]"; + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DefaultTlsNegotiator.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DefaultTlsNegotiator.cs index 8a07de4f7..56a668253 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DefaultTlsNegotiator.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DefaultTlsNegotiator.cs @@ -22,8 +22,8 @@ namespace Neo4j.Driver.Internal.Auth; internal sealed class DefaultTlsNegotiator : ITlsNegotiator { - private readonly ILogger _logger; private readonly EncryptionManager _encryptionManager; + private readonly ILogger _logger; public DefaultTlsNegotiator(ILogger logger, EncryptionManager encryptionManager) { @@ -31,7 +31,7 @@ public DefaultTlsNegotiator(ILogger logger, EncryptionManager encryptionManager) _encryptionManager = encryptionManager; } - /// + /// public SslStream NegotiateTls(Uri uri, Stream stream) { return new SslStream( diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DelegateTlsNegotiator.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DelegateTlsNegotiator.cs index b4c547ab9..3f4010a60 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DelegateTlsNegotiator.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/DelegateTlsNegotiator.cs @@ -28,5 +28,8 @@ public DelegateTlsNegotiator(NegotiateTlsDelegate negotiateTlsDelegate) _negotiateTlsDelegate = negotiateTlsDelegate; } - public SslStream NegotiateTls(Uri uri, Stream stream) => _negotiateTlsDelegate(uri, stream); + public SslStream NegotiateTls(Uri uri, Stream stream) + { + return _negotiateTlsDelegate(uri, stream); + } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/KerberosAuthToken.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/KerberosAuthToken.cs new file mode 100644 index 000000000..8c74fb0a7 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/KerberosAuthToken.cs @@ -0,0 +1,33 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. + +namespace Neo4j.Driver.Internal.Auth; + +internal class KerberosAuthToken : AuthToken +{ + public KerberosAuthToken(string base64EncodedTicket) + { + Content[SchemeKey] = "kerberos"; + Content[PrincipalKey] = string.Empty; // This empty string is required for backwards compatibility. + Content[CredentialsKey] = base64EncodedTicket; + } + + public string Ticket => Content[CredentialsKey] as string; + + public override string ToString() + { + return $"KerberosAuthToken[scheme: {Scheme}, ticket: {Ticket.Substring(0, 5)}...]"; + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/Neo4jAuthTokenManager.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/Neo4jAuthTokenManager.cs index b6cf56d13..ed5bb4e71 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Auth/Neo4jAuthTokenManager.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Auth/Neo4jAuthTokenManager.cs @@ -26,8 +26,8 @@ internal class Neo4jAuthTokenManager : IAuthTokenManager private readonly IDateTimeProvider _dateTimeProvider; private readonly Func> _getAuthTokenAndExpirationAsync; private readonly Type[] _handledExceptionTypes; - private AuthTokenAndExpiration _currentAuthTokenAndExpiration; private readonly SemaphoreSlim _sync; + private AuthTokenAndExpiration _currentAuthTokenAndExpiration; public Neo4jAuthTokenManager( Func> getAuthTokenAndExpirationAsync, diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs index 42df28854..25a766322 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs @@ -49,6 +49,9 @@ internal sealed class ConnectionPool : IConnectionPool private readonly object _poolSizeSync = new(); private readonly Uri _uri; + private int _connectionsWithSsrDisabled; + + private int _connectionsWithSsrEnabled; private int _poolSize; @@ -103,6 +106,9 @@ internal ConnectionPool( internal int PoolSize => Interlocked.CompareExchange(ref _poolSize, -1, -1); public int NumberOfInUseConnections => _inUseConnections.Count; public int NumberOfIdleConnections => _idleConnections.Count; + public int NumberOfConnectionsWithSsrEnabled => _connectionsWithSsrEnabled; + public int NumberOfConnectionsWithSsrDisabled => _connectionsWithSsrDisabled; + public int TotalNumberOfConnections => _connectionsWithSsrDisabled + _connectionsWithSsrEnabled; public ConnectionPoolStatus Status { @@ -228,6 +234,9 @@ public async Task VerifyConnectivityAndGetInfoAsync() return connection.Server; } + /// + public bool IsDirectDriver => true; + public DriverContext DriverContext { get; } public Task SupportsMultiDbAsync() @@ -261,6 +270,28 @@ public void Activate() Interlocked.CompareExchange(ref _poolStatus, Active, Inactive); } + /// + public bool IsOnlyConnectionWithoutSsr(IConnection connection) + { + var allConnections = _inUseConnections.Concat(_idleConnections).ToArray(); + if (allConnections.All(c => c != connection)) + { + // the connection is not in the pool + return false; + } + + // go through all connections in the pool and check if there is any connection that has SSR enabled + var ssrDisabledCount = 0; + var connectionFound = false; + foreach (var conn in allConnections) + { + ssrDisabledCount += conn.SsrEnabled ? 0 : 1; + connectionFound |= conn == connection; + } + + return connectionFound && ssrDisabledCount == 1; + } + public ValueTask DisposeAsync() { return new ValueTask(CloseAsync()); @@ -358,6 +389,15 @@ private async Task DestroyConnectionAsync(IPooledConnection conn) return; } + if (conn.SsrEnabled) + { + Interlocked.Decrement(ref _connectionsWithSsrEnabled); + } + else + { + Interlocked.Decrement(ref _connectionsWithSsrDisabled); + } + _poolMetricsListener?.ConnectionClosing(); try { @@ -487,6 +527,14 @@ private async Task AcquireAsync( private async ValueTask AddConnectionAsync(IPooledConnection connection) { _inUseConnections.TryAdd(connection); + if (connection.SsrEnabled) + { + Interlocked.Increment(ref _connectionsWithSsrEnabled); + } + else + { + Interlocked.Increment(ref _connectionsWithSsrDisabled); + } if (!IsClosed) { diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionValidator.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionValidator.cs index b8717e861..0a6119b44 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionValidator.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionValidator.cs @@ -35,9 +35,9 @@ internal interface IConnectionValidator internal class ConnectionValidator : IConnectionValidator { private readonly long _connIdleTimeout; + private readonly bool _idleTimerActive; private readonly long _livenessTimeout; private readonly long _maxConnLifetime; - private readonly bool _idleTimerActive; public ConnectionValidator( TimeSpan connIdleTimeout, diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/DelegatedConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/DelegatedConnection.cs index a33d90133..73d73ef50 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/DelegatedConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/DelegatedConnection.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; using Neo4j.Driver.Internal.Protocol; @@ -213,6 +214,12 @@ public bool TelemetryEnabled set => Delegate.TelemetryEnabled = value; } + public bool SsrEnabled + { + get => Delegate.SsrEnabled; + set => Delegate.SsrEnabled = value; + } + public Task LoginAsync(string userAgent, IAuthToken authToken, INotificationsConfig notificationsConfig) { return BoltProtocol.AuthenticateAsync(this, userAgent, authToken, notificationsConfig); @@ -231,30 +238,46 @@ public Task ResetAsync() public Task> GetRoutingTableAsync( string database, SessionConfig sessionConfig, - Bookmarks bookmarks) + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { - return BoltProtocol.GetRoutingTableAsync(this, database, sessionConfig, bookmarks); + return BoltProtocol.GetRoutingTableAsync(this, database, sessionConfig, bookmarks, homeDbCache); } public Task RunInAutoCommitTransactionAsync( AutoCommitParams autoCommitParams, - INotificationsConfig notificationsConfig) + INotificationsConfig notificationsConfig, + IHomeDbCache homeDbCache) { - return BoltProtocol.RunInAutoCommitTransactionAsync(this, autoCommitParams, notificationsConfig); + return BoltProtocol.RunInAutoCommitTransactionAsync(this, autoCommitParams, notificationsConfig, homeDbCache); } - public Task BeginTransactionAsync(BeginTransactionParams beginTransactionParams) + public Task BeginTransactionAsync( + BeginTransactionParams beginTransactionParams, + IHomeDbCache homeDbCache) { - return BoltProtocol.BeginTransactionAsync(this, beginTransactionParams); + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(AuthToken, beginTransactionParams.SessionConfig); + return BoltProtocol.BeginTransactionAsync(this, beginTransactionParams, cacheKey, homeDbCache, SessionConfig); } public Task RunInExplicitTransactionAsync( Query query, bool reactive, long fetchSize, - IInternalAsyncTransaction transaction) - { - return BoltProtocol.RunInExplicitTransactionAsync(this, query, reactive, fetchSize, transaction); + IInternalAsyncTransaction transaction, + IHomeDbCache homeDbCache) + { + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(AuthToken, null); + + return BoltProtocol.RunInExplicitTransactionAsync( + this, + query, + reactive, + fetchSize, + transaction, + cacheKey, + homeDbCache, + SessionConfig); } public Task CommitTransactionAsync(IBookmarksTracker bookmarksTracker) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs index 256082f77..61424b36a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs @@ -13,9 +13,11 @@ // 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.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; using Neo4j.Driver.Internal.Protocol; @@ -62,6 +64,8 @@ internal interface IConnection : IConnectionDetails, IConnectionRunner public SessionConfig SessionConfig { get; set; } bool TelemetryEnabled { get; set; } + bool SsrEnabled { get; set; } + void ConfigureMode(AccessMode? mode); void Configure(string database, AccessMode? mode); @@ -122,19 +126,22 @@ Task LoginAsync( Task> GetRoutingTableAsync( string database, SessionConfig sessionConfig, - Bookmarks bookmarks); + Bookmarks bookmarks, + IHomeDbCache homeDbCache); Task RunInAutoCommitTransactionAsync( AutoCommitParams autoCommitParams, - INotificationsConfig notificationsConfig); + INotificationsConfig notificationsConfig, + IHomeDbCache homeDbCache); - Task BeginTransactionAsync(BeginTransactionParams beginParams); + Task BeginTransactionAsync(BeginTransactionParams beginParams, IHomeDbCache homeDbCache); Task RunInExplicitTransactionAsync( Query query, bool reactive, long fetchSize, - IInternalAsyncTransaction transaction); + IInternalAsyncTransaction transaction, + IHomeDbCache homeDbCache); Task CommitTransactionAsync(IBookmarksTracker bookmarksTracker); Task RollbackTransactionAsync(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs index c29172c46..c69507f35 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs @@ -32,7 +32,7 @@ public PooledConnection( : base(conn) { _releaseManager = releaseManager; - + // IdleTimer starts to count when the connection is put back to the pool. IdleTimer = DateTimeProvider.StaticInstance.NewTimer(); // LifetimeTimer starts to count once the connection is created. diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs index e762e94cd..242a6196b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs @@ -26,15 +26,14 @@ namespace Neo4j.Driver.Internal.Connector; internal sealed class SocketClient : ISocketClient { - public DriverContext Context { get; } private const string MessagePattern = "C: {0}"; private readonly IConnectionIoFactory _connectionIoFactory; private readonly IBoltHandshaker _handshaker; - - private readonly Uri _uri; private readonly ILogger _logger; private readonly IPackStreamFactory _packstreamFactory; private readonly ITcpSocketClient _tcpSocketClient; + + private readonly Uri _uri; private IChunkWriter _chunkWriter; private int _closedMarker = -1; @@ -63,6 +62,8 @@ public SocketClient( _tcpSocketClient = _connectionIoFactory.TcpSocketClient(context, _logger); } + public DriverContext Context { get; } + public bool IsOpen => _closedMarker == 0; public async Task ConnectAsync( @@ -99,7 +100,7 @@ public async Task SendAsync(IEnumerable messages) } catch (Exception ex) { - _logger.Warn(ex, $"Unable to send message to server {_uri}, connection will be terminated."); + _logger.Warn(ex, $"Unable to send message to server {_uri}, connection will be terminated. ({ex.Message})"); await DisposeAsync().ConfigureAwait(false); throw; } @@ -121,7 +122,9 @@ public async Task ReceiveOneAsync(IResponsePipeline responsePipeline) } catch (Exception ex) { - _logger.Error(ex, $"Unable to read message from server {_uri}, connection will be terminated."); + _logger.Error( + ex, + $"Unable to read message from server {_uri}, connection will be terminated. ({ex.Message})"); await DisposeAsync().ConfigureAwait(false); throw; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs index e775bdb41..014b39abe 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs @@ -19,6 +19,7 @@ using System.Threading; using System.Threading.Tasks; using Neo4j.Driver.Internal.Helpers; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.Logging; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; @@ -102,6 +103,9 @@ internal SocketConnection( public AuthorizationStatus AuthorizationStatus { get; set; } + /// + public bool SsrEnabled { get; set; } + public void ConfigureMode(AccessMode? mode) { Mode = mode; @@ -425,30 +429,44 @@ public Task LogoutAsync() public Task> GetRoutingTableAsync( string database, SessionConfig sessionConfig, - Bookmarks bookmarks) + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { - return BoltProtocol.GetRoutingTableAsync(this, database, sessionConfig, bookmarks); + return BoltProtocol.GetRoutingTableAsync(this, database, sessionConfig, bookmarks, homeDbCache); } public Task RunInAutoCommitTransactionAsync( AutoCommitParams autoCommitParams, - INotificationsConfig notificationsConfig) + INotificationsConfig notificationsConfig, + IHomeDbCache homeDbCache) { - return BoltProtocol.RunInAutoCommitTransactionAsync(this, autoCommitParams, notificationsConfig); + return BoltProtocol.RunInAutoCommitTransactionAsync(this, autoCommitParams, notificationsConfig, homeDbCache); } - public Task BeginTransactionAsync(BeginTransactionParams beginParams) + public Task BeginTransactionAsync(BeginTransactionParams beginParams, IHomeDbCache homeDbCache) { - return BoltProtocol.BeginTransactionAsync(this, beginParams); + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(AuthToken, beginParams.SessionConfig); + return BoltProtocol.BeginTransactionAsync(this, beginParams, cacheKey, homeDbCache, SessionConfig); } public Task RunInExplicitTransactionAsync( Query query, bool reactive, long fetchSize, - IInternalAsyncTransaction transaction) - { - return BoltProtocol.RunInExplicitTransactionAsync(this, query, reactive, fetchSize, transaction); + IInternalAsyncTransaction transaction, + IHomeDbCache homeDbCache) + { + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(AuthToken, null); + + return BoltProtocol.RunInExplicitTransactionAsync( + this, + query, + reactive, + fetchSize, + transaction, + cacheKey, + homeDbCache, + SessionConfig); } public Task CommitTransactionAsync(IBookmarksTracker bookmarksTracker) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs index b79624db6..eba0df9c5 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs @@ -30,7 +30,6 @@ namespace Neo4j.Driver.Internal.Connector; internal sealed class TcpSocketClient : ITcpSocketClient { - private DriverContext DriverContext { get; } private readonly ILogger _logger; public Uri ConnectionUri { get; private set; } @@ -42,6 +41,8 @@ public TcpSocketClient(DriverContext driverContext, ILogger logger = null) _logger = logger; } + private DriverContext DriverContext { get; } + public Stream ReaderStream { get; private set; } public Stream WriterStream => ReaderStream; @@ -67,7 +68,9 @@ await sslStream } catch (Exception e) { - throw new ServiceUnavailableException($"Failed to establish encrypted connection with server {uri}.", e); + throw new ServiceUnavailableException( + $"Failed to establish encrypted connection with server {uri}.", + e); } } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs index 8b651b80a..34386b9e8 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Driver.cs @@ -68,6 +68,7 @@ public IInternalAsyncSession Session(Action action, bool r } var sessionConfig = ConfigBuilders.BuildSessionConfig(action); + sessionConfig.DriverContext = Context; var session = new AsyncSession( _connectionProvider, @@ -232,12 +233,14 @@ private async Task> ExecuteQueryAsyncInternal( { if (config.Routing == RoutingControl.Readers) { - return await session.PipelinedExecuteReadAsync(x => Work(query, x, cursorProcessor, cancellationToken), + return await session.PipelinedExecuteReadAsync( + x => Work(query, x, cursorProcessor, cancellationToken), config.TransactionConfig) .ConfigureAwait(false); } - return await session.PipelinedExecuteWriteAsync(x => Work(query, x, cursorProcessor, cancellationToken), + return await session.PipelinedExecuteWriteAsync( + x => Work(query, x, cursorProcessor, cancellationToken), config.TransactionConfig) .ConfigureAwait(false); } @@ -276,7 +279,7 @@ private void ApplyConfig(QueryConfig config, SessionConfigBuilder sessionConfigB sessionConfigBuilder.WithBookmarkManager(config.BookmarkManager ?? _bookmarkManager); } - if(config.AuthToken != null) + if (config.AuthToken != null) { sessionConfigBuilder.WithAuthToken(config.AuthToken); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/DriverContext.cs b/Neo4j.Driver/Neo4j.Driver/Internal/DriverContext.cs index 6656b047d..0275dd581 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/DriverContext.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/DriverContext.cs @@ -16,9 +16,10 @@ using System; using System.Collections.Generic; using Neo4j.Driver.Internal.Connector; -using Neo4j.Driver.Internal.Metrics; using Neo4j.Driver.Internal.Connector.Resolvers; using Neo4j.Driver.Internal.Helpers; +using Neo4j.Driver.Internal.HomeDbCaching; +using Neo4j.Driver.Internal.Metrics; using Neo4j.Driver.Internal.Util; namespace Neo4j.Driver.Internal; @@ -40,8 +41,9 @@ internal DriverContext( config.NullableEncryptionLevel, config.TrustManager, config.Logger); + DriverBookmarkManager = new DefaultBookmarkManager(new BookmarkManagerConfig()); - + HostResolver = customHostResolver ?? (RuntimeHelper.IsDotNetCore ? new SystemNetCoreHostResolver(new SystemHostResolver()) @@ -54,19 +56,18 @@ internal DriverContext( public DefaultBookmarkManager DriverBookmarkManager { get; } - /// - /// The root uri configured on the driver. - /// This is not a uri for a connection. - /// + /// The root uri configured on the driver. This is not a uri for a connection. public Uri InitialUri { get; } + public Config Config { get; } - /// - /// Shortcut to Config.Logger. - /// + + /// Shortcut to Config.Logger. public ILogger Logger => Config.Logger; + public IAuthTokenManager AuthTokenManager { get; } public EncryptionManager EncryptionManager { get; } public IHostResolver HostResolver { get; } public IInternalMetrics Metrics { get; } public IDictionary RoutingContext { get; } + public IHomeDbCache HomeDbCache { get; } = new HomeDbCache(); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/EncryptionManager.cs b/Neo4j.Driver/Neo4j.Driver/Internal/EncryptionManager.cs index 86e53bff7..a38396433 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/EncryptionManager.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/EncryptionManager.cs @@ -72,13 +72,13 @@ public static EncryptionManager CreateFromConfig( { return new EncryptionManager(true, CreateSecureTrustManager(logger)); } - + if (trustManager != null && trustManager.Logger == null) { // likely to be true, as this is passed in by a user and logger is internal so we should set it. trustManager.Logger = logger; } - + return new EncryptionManager(encrypted, trustManager); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/ErrorCodeAttribute.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/ErrorCodeAttribute.cs index c117830a2..7cbe1b3fe 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/ErrorCodeAttribute.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/ErrorCodeAttribute.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -20,20 +18,18 @@ namespace Neo4j.Driver.Internal.ExceptionHandling; /// -/// Use this attribute to decorate an exception class to declare that the class -/// is the correct class to create when an error with the specified code is raised. +/// Use this attribute to decorate an exception class to declare that the class is the correct class to create +/// when an error with the specified code is raised. /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] internal class ErrorCodeAttribute : Attribute { - public string Code { get; } - - /// - /// Creates a new instance of the class. - /// + /// Creates a new instance of the class. /// The error code that the decorated class is the exception for. public ErrorCodeAttribute(string code) { Code = code; } + + public string Code { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/Neo4jExceptionFactory.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/Neo4jExceptionFactory.cs index 03e6eff41..2263d82db 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/Neo4jExceptionFactory.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/ExceptionHandling/Neo4jExceptionFactory.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -26,8 +24,6 @@ namespace Neo4j.Driver.Internal.ExceptionHandling; internal class Neo4jExceptionFactory { - private record FactoryInfo(string Code, Func ExceptionFactory); - private readonly List _exceptionFactories = new(); private readonly SimpleWildcardHelper _simpleWildcardHelper = new(); @@ -103,7 +99,6 @@ Neo4jException Factory(FailureMessage message, Exception inner) $"Neo4jException type {type.FullName} does not have a constructor that takes " + $"a {nameof(FailureMessage)} and an {nameof(Exception)}"); } - } } @@ -122,4 +117,6 @@ public Neo4jException GetException(FailureMessage failureMessage) var exception = factoryInfo.ExceptionFactory(failureMessage, innerException); return exception; } + + private record FactoryInfo(string Code, Func ExceptionFactory); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/CollectionExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/CollectionExtensions.cs index f194fa047..f836d0914 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/CollectionExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/CollectionExtensions.cs @@ -40,6 +40,14 @@ public static T GetMandatoryValue( return (T)dictionary[key]; } + public static TValue GetValueOrDefault( + this IDictionary dict, + TKey key, + TValue defaultValue = default) + { + return dict.TryGetValue(key, out var value) ? value : defaultValue; + } + public static T GetValue(this IDictionary dict, string key, T defaultValue) { return dict.TryGetValue(key, out var value) ? (T)value : defaultValue; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/ErrorExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/ErrorExtensions.cs index 1a85e01b9..edc081411 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/ErrorExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/ErrorExtensions.cs @@ -23,7 +23,7 @@ namespace Neo4j.Driver.Internal; internal static class ErrorExtensions { - private static Neo4jExceptionFactory _exceptionFactory = new(); + private static readonly Neo4jExceptionFactory _exceptionFactory = new(); public static Neo4jException ParseServerException(FailureMessage failureMessage) { diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/GqlCompliance/GqlErrors.cs b/Neo4j.Driver/Neo4j.Driver/Internal/GqlCompliance/GqlErrors.cs index a9fc12ee9..f2fa96cce 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/GqlCompliance/GqlErrors.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/GqlCompliance/GqlErrors.cs @@ -35,8 +35,8 @@ public static void FillGqlDefaults(FailureMessage message) message.Code ??= UnknownNeo4JCode; message.Message ??= UnknownMessage; message.GqlStatus ??= UnknownGqlStatus; - - if(string.IsNullOrEmpty(message.GqlStatusDescription)) + + if (string.IsNullOrEmpty(message.GqlStatusDescription)) { message.GqlStatusDescription = UnknownGqlStatusDescription + " " + message.Message; } @@ -44,13 +44,13 @@ public static void FillGqlDefaults(FailureMessage message) message.GqlDiagnosticRecord ??= new Dictionary(); message.GqlDiagnosticRecord.FillMissingFrom(NewDefaultDiagnosticRecord()); - if(message.GqlDiagnosticRecord.TryGetValue("_classification", out var classification)) + if (message.GqlDiagnosticRecord.TryGetValue("_classification", out var classification)) { message.GqlRawClassification = classification.ToString(); message.GqlClassification = UnknownError; - foreach(var c in new[] { ClientError, DatabaseError, TransientError }) + foreach (var c in new[] { ClientError, DatabaseError, TransientError }) { - if(classification is string cl && cl == c) + if (classification is string cl && cl == c) { message.GqlClassification = c; break; @@ -66,7 +66,7 @@ public static void FillGqlDefaults(FailureMessage message) private static Dictionary NewDefaultDiagnosticRecord() { - return new() + return new Dictionary { ["OPERATION"] = "", ["OPERATION_CODE"] = "0", diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/SimpleWildcardHelper.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/SimpleWildcardHelper.cs index 3e5c87718..bc86387b4 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/SimpleWildcardHelper.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/SimpleWildcardHelper.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -20,8 +18,8 @@ namespace Neo4j.Driver.Internal.Helpers; internal class SimpleWildcardHelper { /// - /// Returns true if the two strings are the same, or, if ends with an asterisk (*), - /// returns true if starts with (minus the asterisk). + /// Returns true if the two strings are the same, or, if ends with an asterisk (*), returns + /// true if starts with (minus the asterisk). /// /// The string to check. /// The (potential) wildcard to compare with diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/TemporalHelpers.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/TemporalHelpers.cs index c3d20ed02..f702f4bb1 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/TemporalHelpers.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Helpers/TemporalHelpers.cs @@ -56,7 +56,7 @@ internal static class TemporalHelpers internal const long DateTimeOffsetMinSeconds = -62_135_596_800; internal const long DateTimeOffsetMaxSeconds = 253_402_300_799; internal const long MinUtcForZonedDateTime = -31557014135596800; - internal const long MaxUtcForZonedDateTime = 31556889832780799; + internal const long MaxUtcForZonedDateTime = 31556889832780799; public static long ToNanoOfDay(this IHasTimeComponents time) { @@ -112,7 +112,7 @@ public static LocalDateTime EpochSecondsAndNanoToDateTime(long epochSeconds, int { var epochDay = FloorDiv(epochSeconds, SecondsPerDay); var secondsOfDay = FloorMod(epochSeconds, SecondsPerDay); - var nanoOfDay = secondsOfDay * NanosPerSecond + nano; + var nanoOfDay = secondsOfDay * NanosPerSecond + nano; ComponentsOfEpochDays(epochDay, out var year, out var month, out var day); ComponentsOfNanoOfDay(nanoOfDay, out var hour, out var minute, out var second, out var nanosecond); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCache.cs b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCache.cs new file mode 100644 index 000000000..152cd3213 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCache.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; + +namespace Neo4j.Driver.Internal.HomeDbCaching; + +internal class HomeDbCache : IHomeDbCache +{ + private const int PurgeThreshold = 10_000; + private const int PurgeAmount = PurgeThreshold / 10; + + private class CacheItem + { + public HomeDbCacheKey Key { get; } + public string DatabaseName { get; set; } + + public CacheItem(HomeDbCacheKey key, string databaseName) + { + Key = key; + DatabaseName = databaseName; + } + } + + private readonly LinkedList _cachedItems = new(); + private readonly Dictionary> _cacheLookup = new(); + + public bool TryGetCached(HomeDbCacheKey key, out string value) + { + value = null; + var found = _cacheLookup.TryGetValue(key, out var node); + if (!found) + { + return false; + } + + _cachedItems.Remove(node); + _cachedItems.AddFirst(node); + value = node.Value.DatabaseName; + return true; + + } + + public void AddOrUpdate(HomeDbCacheKey key, string value) + { + LinkedListNode node; + // if we already have an entry + if (_cacheLookup.TryGetValue(key, out node)) + { + _cachedItems.Remove(node); + } + else + { + node = new LinkedListNode(new CacheItem(key, value)); + _cacheLookup[key] = node; + } + + node.Value.DatabaseName = value; + _cachedItems.AddFirst(node); + PurgeOldItems(); + } + + private void PurgeOldItems() + { + if (_cachedItems.Count < PurgeThreshold) + { + return; + } + + for (var i = 0; i < PurgeAmount; i++) + { + RemoveLastItem(); + } + } + + private void RemoveLastItem() + { + var last = _cachedItems.Last; + + if(last == null) + { + return; + } + + _cacheLookup.Remove(last!.Value.Key); + _cachedItems.RemoveLast(); + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCacheKey.cs b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCacheKey.cs new file mode 100644 index 000000000..6c9788c08 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCacheKey.cs @@ -0,0 +1,40 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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; + +namespace Neo4j.Driver.Internal.HomeDbCaching; + +internal readonly struct HomeDbCacheKey(object key) : IEquatable +{ + private readonly object _key = key; + + public override int GetHashCode() => _key.GetHashCode(); + + public override string ToString() => $"{_key.ToString()} ({GetHashCode():x8})"; + + public override bool Equals(object obj) + { + return obj is HomeDbCacheKey other && _key.Equals(other._key); + } + + public static readonly HomeDbCacheKey Default = new ("default"); + + /// + public bool Equals(HomeDbCacheKey other) + { + return Equals(_key, other._key); + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCacheKeyProvider.cs b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCacheKeyProvider.cs new file mode 100644 index 000000000..b03d366ac --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/HomeDbCacheKeyProvider.cs @@ -0,0 +1,37 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. + +namespace Neo4j.Driver.Internal.HomeDbCaching; + +internal static class HomeDbCacheKeyProvider +{ + public static HomeDbCacheKey GetCacheKey(IAuthToken driverLevelAuthToken, SessionConfig sessionConfig) + { + if(!string.IsNullOrWhiteSpace(sessionConfig?.ImpersonatedUser)) + { + // if an impersonated user is set, we should use the impersonated user as the cache key + return new HomeDbCacheKey($"basic:{sessionConfig.ImpersonatedUser}"); + } + + if (sessionConfig?.AuthToken != null) + { + // if a session auth token is set, we should use the auth token as the cache key + return new HomeDbCacheKey(sessionConfig.AuthToken); + } + + // otherwise, use a default cache key + return HomeDbCacheKey.Default; + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/IHomeDbCache.cs b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/IHomeDbCache.cs new file mode 100644 index 000000000..0cacd717d --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/HomeDbCaching/IHomeDbCache.cs @@ -0,0 +1,22 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. + +namespace Neo4j.Driver.Internal.HomeDbCaching; + +internal interface IHomeDbCache +{ + bool TryGetCached(HomeDbCacheKey key, out string value); + void AddOrUpdate(HomeDbCacheKey key, string value); +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionPool.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionPool.cs index b56ab4ea2..38e419fe5 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionPool.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionPool.cs @@ -14,6 +14,7 @@ // limitations under the License. using System.Threading.Tasks; +using Neo4j.Driver.Internal.Connector; namespace Neo4j.Driver.Internal; @@ -21,7 +22,13 @@ internal interface IConnectionPool : IConnectionProvider, IConnectionReleaseMana { int NumberOfInUseConnections { get; } int NumberOfIdleConnections { get; } + int NumberOfConnectionsWithSsrEnabled { get; } + int NumberOfConnectionsWithSsrDisabled { get; } + + int TotalNumberOfConnections { get; } ConnectionPoolStatus Status { get; } Task DeactivateAsync(); void Activate(); + + bool IsOnlyConnectionWithoutSsr(IConnection connection); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionProvider.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionProvider.cs index 0560a9b99..4859e8ef5 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionProvider.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IConnectionProvider.cs @@ -35,4 +35,6 @@ Task AcquireAsync( Task SupportsReAuthAsync(); IRoutingTable GetRoutingTable(string database); Task VerifyConnectivityAndGetInfoAsync(); + + bool IsDirectDriver { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncSession.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncSession.cs index 260b762a3..bd2dbba14 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncSession.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncSession.cs @@ -34,6 +34,11 @@ Task RunAsync( Action action, bool disposeUnconsumedSessionResult); - Task> PipelinedExecuteReadAsync(Func>> func, TransactionConfig config); - Task> PipelinedExecuteWriteAsync(Func>> func, TransactionConfig config); + Task> PipelinedExecuteReadAsync( + Func>> func, + TransactionConfig config); + + Task> PipelinedExecuteWriteAsync( + Func>> func, + TransactionConfig config); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncTransaction.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncTransaction.cs index ceee1b5ef..c722c0b95 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncTransaction.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IInternalAsyncTransaction.cs @@ -19,6 +19,6 @@ namespace Neo4j.Driver.Internal; internal interface IInternalAsyncTransaction : IAsyncTransaction { - bool IsErrored(out Exception ex); bool IsOpen { get; } + bool IsErrored(out Exception ex); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ChunkWriter.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ChunkWriter.cs index 0c8b486e9..3da343250 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ChunkWriter.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ChunkWriter.cs @@ -55,7 +55,7 @@ public ChunkWriter(Stream downStream, DriverContext context, ILogger logger) $"Parameter {nameof(downStream)} is invalid. " + $"Property:{nameof(downStream.CanWrite)} is false but should be true"); } - + _logger = logger; _defaultBufferSize = context.Config.DefaultWriteBufferSize; _maxBufferSize = context.Config.MaxWriteBufferSize; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageReader.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageReader.cs index b293692da..36d3ac0cb 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageReader.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageReader.cs @@ -28,10 +28,8 @@ internal sealed class MessageReader : IMessageReader private readonly int _defaultBufferSize; private readonly ILogger _logger; private readonly int _maxBufferSize; + private readonly ByteBuffers _readerBuffers; private int _shrinkCounter; - readonly ByteBuffers _readerBuffers; - - public MemoryStream BufferStream { get; } public MessageReader(IChunkReader chunkReader, DriverContext driverContext, ILogger logger) { @@ -43,6 +41,8 @@ public MessageReader(IChunkReader chunkReader, DriverContext driverContext, ILog _readerBuffers = new ByteBuffers(); } + public MemoryStream BufferStream { get; } + public async ValueTask ReadAsync(IResponsePipeline pipeline, MessageFormat format) { var messageCount = await _chunkReader.ReadMessageChunksToBufferStreamAsync(BufferStream).ConfigureAwait(false); @@ -55,6 +55,12 @@ public void SetReadTimeoutInMs(int ms) _chunkReader.SetTimeoutInMs(ms); } + public ValueTask DisposeAsync() + { + BufferStream.Dispose(); + return new ValueTask(Task.CompletedTask); + } + private void ConsumeMessages(IResponsePipeline pipeline, int messages, PackStreamReader packStreamReader) { var leftMessages = messages; @@ -105,10 +111,4 @@ private void ProcessMessage(IResponsePipeline pipeline, PackStreamReader packStr throw new ProtocolException($"Unknown response message type {message.GetType().FullName}"); } } - - public ValueTask DisposeAsync() - { - BufferStream.Dispose(); - return new ValueTask(Task.CompletedTask); - } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/FailureMessageSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/FailureMessageSerializer.cs index 8e779b32d..59bd2d296 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/FailureMessageSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/FailureMessageSerializer.cs @@ -27,20 +27,20 @@ internal sealed class FailureMessageSerializer : ReadOnlySerializer, IPackStream private static readonly byte[] StructTags = { MessageFormat.MsgFailure }; public override byte[] ReadableStructs => StructTags; - public override object Deserialize( + public IResponseMessage DeserializeMessage( BoltProtocolVersion boltProtocolVersion, - PackStreamReader packStreamReader, - byte _, - long __) + SpanPackStreamReader packStreamReader) { var values = packStreamReader.ReadMap(); var majorVersion = boltProtocolVersion.MajorVersion; return BuildFailureMessage(values, majorVersion); } - public IResponseMessage DeserializeMessage( + public override object Deserialize( BoltProtocolVersion boltProtocolVersion, - SpanPackStreamReader packStreamReader) + PackStreamReader packStreamReader, + byte _, + long __) { var values = packStreamReader.ReadMap(); var majorVersion = boltProtocolVersion.MajorVersion; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/IgnoredMessageSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/IgnoredMessageSerializer.cs index 257c04cbf..205fc238f 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/IgnoredMessageSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/IgnoredMessageSerializer.cs @@ -25,14 +25,14 @@ internal sealed class IgnoredMessageSerializer : ReadOnlySerializer, IPackStream private static readonly byte[] StructTags = { MessageFormat.MsgIgnored }; public override byte[] ReadableStructs => StructTags; - public override object Deserialize(PackStreamReader _) + public IResponseMessage DeserializeMessage( + BoltProtocolVersion formatVersion, + SpanPackStreamReader packStreamReader) { return IgnoredMessage.Instance; } - public IResponseMessage DeserializeMessage( - BoltProtocolVersion formatVersion, - SpanPackStreamReader packStreamReader) + public override object Deserialize(PackStreamReader _) { return IgnoredMessage.Instance; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/RecordMessageSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/RecordMessageSerializer.cs index c5421b099..376428129 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/RecordMessageSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/RecordMessageSerializer.cs @@ -25,26 +25,27 @@ internal sealed class RecordMessageSerializer : ReadOnlySerializer, IPackStreamM private static readonly byte[] StructTags = { MessageFormat.MsgRecord }; public override byte[] ReadableStructs => StructTags; - public override object Deserialize(PackStreamReader reader) + public IResponseMessage DeserializeMessage(BoltProtocolVersion formatVersion, SpanPackStreamReader packStreamReader) { - var fieldCount = (int)reader.ReadListHeader(); + var fieldCount = packStreamReader.ReadListHeader(); var fields = new object[fieldCount]; for (var i = 0; i < fieldCount; i++) { - fields[i] = reader.Read(); + fields[i] = packStreamReader.Read(); } return new RecordMessage(fields); } - public IResponseMessage DeserializeMessage(BoltProtocolVersion formatVersion, SpanPackStreamReader packStreamReader) + public override object Deserialize(PackStreamReader reader) { - var fieldCount = packStreamReader.ReadListHeader(); + var fieldCount = (int)reader.ReadListHeader(); var fields = new object[fieldCount]; for (var i = 0; i < fieldCount; i++) { - fields[i] = packStreamReader.Read(); + fields[i] = reader.Read(); } + return new RecordMessage(fields); } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/SuccessMessageSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/SuccessMessageSerializer.cs index c413ac6d7..85a7d5877 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/SuccessMessageSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/SuccessMessageSerializer.cs @@ -25,15 +25,15 @@ internal sealed class SuccessMessageSerializer : ReadOnlySerializer, IPackStream private static readonly byte[] StructTags = { MessageFormat.MsgSuccess }; public override byte[] ReadableStructs => StructTags; - public override object Deserialize(PackStreamReader reader) + public IResponseMessage DeserializeMessage(BoltProtocolVersion formatVersion, SpanPackStreamReader packStreamReader) { - var map = reader.ReadMap(); + var map = packStreamReader.ReadMap(); return new SuccessMessage(map); } - public IResponseMessage DeserializeMessage(BoltProtocolVersion formatVersion, SpanPackStreamReader packStreamReader) + public override object Deserialize(PackStreamReader reader) { - var map = packStreamReader.ReadMap(); + var map = reader.ReadMap(); return new SuccessMessage(map); } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/TelemetryMessageSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/TelemetryMessageSerializer.cs index b32a37a83..70edf9624 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/TelemetryMessageSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/MessageSerializers/TelemetryMessageSerializer.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -24,15 +22,14 @@ namespace Neo4j.Driver.Internal.IO.MessageSerializers; internal class TelemetryMessageSerializer : WriteOnlySerializer { - public static TelemetryMessageSerializer Instance { get; } = new(); - private static readonly Type[] Types = { typeof(TelemetryMessage) }; + public static TelemetryMessageSerializer Instance { get; } = new(); public override IEnumerable WritableTypes => Types; - /// + /// public override void Serialize(PackStreamWriter writer, object value) { - if(value is not TelemetryMessage telemetryMessage) + if (value is not TelemetryMessage telemetryMessage) { throw new ArgumentOutOfRangeException( $"Encountered {value?.GetType().Name} where {nameof(TelemetryMessage)} was expected"); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamReader.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamReader.cs index fc74ecb15..668c77b40 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamReader.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamReader.cs @@ -25,12 +25,8 @@ namespace Neo4j.Driver.Internal.IO; // we don't use the return value of Read() in most cases, so suppress the warning [SuppressMessage("ReSharper", "MustUseReturnValue")] - internal sealed class PackStreamReader { - public ByteBuffers Buffers { get; } - public MessageFormat Format { get; } - public MemoryStream Stream; internal PackStreamReader(MessageFormat format, MemoryStream stream, ByteBuffers buffers) @@ -40,6 +36,9 @@ internal PackStreamReader(MessageFormat format, MemoryStream stream, ByteBuffers Buffers = buffers; } + public ByteBuffers Buffers { get; } + public MessageFormat Format { get; } + public object Read() { var type = PeekNextType(); @@ -65,7 +64,6 @@ public Dictionary ReadMap() var size = (int)ReadMapHeader(); if (size == 0) { - return new Dictionary(0); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamWriter.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamWriter.cs index f7e899c12..c5553b307 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamWriter.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/PackStreamWriter.cs @@ -146,7 +146,8 @@ public void WriteLong(long value) { switch (value) { - case >= Minus2ToThe4 and < Plus2ToThe7: _stream.WriteByte((byte)value); + case >= Minus2ToThe4 and < Plus2ToThe7: + _stream.WriteByte((byte)value); break; case >= Minus2ToThe7 and < Minus2ToThe4: diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/PipelinedMessageReader.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/PipelinedMessageReader.cs index 74a33eae6..f043d7588 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/PipelinedMessageReader.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/PipelinedMessageReader.cs @@ -1,7 +1,5 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. +// Neo4j Sweden AB [https://neo4j.com] // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -32,18 +30,18 @@ namespace Neo4j.Driver.Internal.IO; internal sealed class PipelinedMessageReader : IMessageReader { - private readonly Stream _stream; - private int _timeoutInMs; - private CancellationTokenSource _source; private readonly Memory _headerMemory; private readonly PipeReader _pipeReader; private readonly MemoryPool _pool; + private readonly Stream _stream; + private CancellationTokenSource _source; + private int _timeoutInMs; internal PipelinedMessageReader(Stream inputStream, DriverContext context) : this(inputStream, context, inputStream.ReadTimeout) { } - + internal PipelinedMessageReader(Stream inputStream, DriverContext context, int timeout) { _timeoutInMs = timeout; @@ -53,7 +51,7 @@ internal PipelinedMessageReader(Stream inputStream, DriverContext context, int t _headerMemory = new Memory(new byte[2]); _pipeReader = PipeReader.Create(_stream, context.Config.MessageReaderConfig.StreamPipeReaderOptions); } - + public ValueTask DisposeAsync() { _source.Dispose(); @@ -77,7 +75,8 @@ public async ValueTask ReadAsync(IResponsePipeline pipeline, MessageFormat forma // A timeout has occurred, close the connection. await _pipeReader.CompleteAsync(canceledException).ConfigureAwait(false); _stream.Close(); - throw new ConnectionReadTimeoutException("Failed to read message from server within the specified timeout.", + throw new ConnectionReadTimeoutException( + "Failed to read message from server within the specified timeout.", canceledException); } catch (Exception ex) @@ -88,6 +87,11 @@ public async ValueTask ReadAsync(IResponsePipeline pipeline, MessageFormat forma } } + public void SetReadTimeoutInMs(int ms) + { + _timeoutInMs = ms; + } + private void ResetCancellation() { if (_timeoutInMs <= 0) @@ -121,13 +125,14 @@ private async ValueTask ReadNextMessage(MessageFormat format, readResult = await pipeReader.ReadAtLeastAsync(2, _source.Token).ConfigureAwait(false); if (readResult is { IsCompleted: true, Buffer.Length: < 2 }) { - throw new IOException("Unexpected end of stream, unable to read expected data from the network connection"); + throw new IOException( + "Unexpected end of stream, unable to read expected data from the network connection"); } var lengthSlice = readResult.Buffer.Slice(0, 2); lengthSlice.CopyTo(_headerMemory.Span); size = BinaryPrimitives.ReadUInt16BigEndian(_headerMemory.Span); - + // if the size is 0, it means the message was a noop message. if (size == 0) { @@ -135,9 +140,9 @@ private async ValueTask ReadNextMessage(MessageFormat format, pipeReader.AdvanceTo(readResult.Buffer.Slice(2).Start); continue; } - break; - } while(true); + break; + } while (true); // Read chunks storing the lengths of each chunk. // Because the length of the message is unknown until the end of writing allocating a single buffer to read the entire @@ -187,13 +192,13 @@ private async ValueTask ReadNextMessage(MessageFormat format, // Otherwise we need to copy the data into a single buffer to parse it. return CondenseChunksAndParse(format, pipeReader, totalSize, sizes, readResult); } - + private static bool SingleSegmentSlice(ReadResult readResult, int totalSize, out ReadOnlySequence chunkBuffer) { chunkBuffer = readResult.Buffer.Slice(2, totalSize + 2); return chunkBuffer.IsSingleSegment; } - + private static IResponseMessage RawParse( MessageFormat format, PipeReader pipeReader, @@ -209,7 +214,7 @@ private static IResponseMessage RawParse( pipeReader.AdvanceTo(buffer.End); return message; } - + private IResponseMessage CondenseChunksAndParse( MessageFormat format, PipeReader pipeReader, @@ -238,12 +243,8 @@ private static void CopyToMemory(List sizes, ReadResult readResult, Span memoryPosition += chunkSize; streamStart += chunkSize + 2; } + // Advance to end of message by create a buffer slice from the end of the chunk. pipeReader.AdvanceTo(readResult.Buffer.Slice(streamStart).Start); } - - public void SetReadTimeoutInMs(int ms) - { - _timeoutInMs = ms; - } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ReadOnlySerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ReadOnlySerializer.cs index 4f9e115d9..f721b5c04 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ReadOnlySerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ReadOnlySerializer.cs @@ -36,11 +36,6 @@ public virtual object Deserialize(BoltProtocolVersion _, PackStreamReader reader return Deserialize(reader); } - public virtual object Deserialize(PackStreamReader reader) - { - throw new NotImplementedException(); - } - public virtual (object, int) DeserializeSpan( BoltProtocolVersion version, SpanPackStreamReader reader, @@ -50,6 +45,11 @@ public virtual (object, int) DeserializeSpan( return DeserializeSpan(reader); } + public virtual object Deserialize(PackStreamReader reader) + { + throw new NotImplementedException(); + } + public virtual (object, int) DeserializeSpan(SpanPackStreamReader reader) { throw new NotImplementedException(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/SpanPackStreamReader.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/SpanPackStreamReader.cs index 0fcb9750c..592077948 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/SpanPackStreamReader.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/SpanPackStreamReader.cs @@ -1,7 +1,5 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. +// Neo4j Sweden AB [https://neo4j.com] // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -77,7 +75,6 @@ internal PackStreamType PeekNextType() case PackStream.TinyList: return PackStreamType.List; case PackStream.TinyMap: return PackStreamType.Map; case PackStream.TinyStruct: return PackStreamType.Struct; - default: break; // otherwise continue processing } if ((sbyte)markerByte >= PackStream.Minus2ToThe4) @@ -203,7 +200,7 @@ public int ReadInteger() PackStream.Int8 => NextSByte(), PackStream.Int16 => NextShort(), PackStream.Int32 => NextInt(), - PackStream.Int64 => throw new OverflowException($"Unexpectedly large Integer value unpacked."), + PackStream.Int64 => throw new OverflowException("Unexpectedly large Integer value unpacked."), _ => throw new ProtocolException($"Expected an integer, but got: 0x{marker:X2}") }; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PathSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PathSerializer.cs index 1f836c273..f42467064 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PathSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PathSerializer.cs @@ -86,7 +86,7 @@ public override object Deserialize(PackStreamReader reader) return new Path(segments.ToList(), nodes.ToList(), rels.ToList()); } - + public override (object, int) DeserializeSpan(SpanPackStreamReader reader) { // List of unique nodes @@ -97,6 +97,7 @@ public override (object, int) DeserializeSpan(SpanPackStreamReader reader) { throw new ProtocolException("Expecting receivedNode to be true, however the value is false"); } + uniqNodes[i] = node; } @@ -106,8 +107,10 @@ public override (object, int) DeserializeSpan(SpanPackStreamReader reader) { if (reader.Read() is not Relationship uniqRel) { - throw new ProtocolException("Expecting receivedUnboundRelationship to be true, however the value is false"); + throw new ProtocolException( + "Expecting receivedUnboundRelationship to be true, however the value is false"); } + uniqRels[i] = uniqRel; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PointSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PointSerializer.cs index 9b65eda4e..367f3775c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PointSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/PointSerializer.cs @@ -93,7 +93,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val } } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { switch (signature) { diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/DurationSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/DurationSerializer.cs index c7c0f7ff5..6cb6e62e9 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/DurationSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/DurationSerializer.cs @@ -52,7 +52,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val writer.WriteInt(duration.Nanos); } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { PackStream.EnsureStructSize("Duration", StructSize, size); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateSerializer.cs index 96eae6e2d..da1ce6178 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateSerializer.cs @@ -56,7 +56,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val WriteLocalDate(writer, value); } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { PackStream.EnsureStructSize("Date", StructSize, size); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializer.cs index e6572364a..0b35192d3 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalDateTimeSerializer.cs @@ -49,7 +49,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val writer.WriteInt(dateTime.Nanosecond); } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { PackStream.EnsureStructSize("LocalDateTime", StructSize, size); var epochSeconds = reader.ReadLong(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalTimeSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalTimeSerializer.cs index 6d4b6dce4..52cc42c72 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalTimeSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/LocalTimeSerializer.cs @@ -43,7 +43,6 @@ public object Deserialize(BoltProtocolVersion _, PackStreamReader reader, byte s return TemporalHelpers.NanoOfDayToTime(nanosOfDay); } - public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object value) { #if NET6_0_OR_GREATER @@ -56,7 +55,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val WriteLocalTime(writer, value); } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { PackStream.EnsureStructSize("LocalTime", StructSize, size); var nanosOfDay = reader.ReadLong(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializer.cs index 1a0dffe74..0c029b802 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/OffsetTimeSerializer.cs @@ -49,7 +49,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val writer.WriteInt(time.OffsetSeconds); } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { PackStream.EnsureStructSize("Time", StructSize, size); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializer.cs index d0f7a1b14..c9c7ec6a4 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/ValueSerializers/Temporal/UtcZonedDateTimeSerializer.cs @@ -75,7 +75,11 @@ public void Serialize(BoltProtocolVersion _, PackStreamWriter writer, object val } } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { PackStream.EnsureStructSize($"ZonedDateTime[{(char)signature}]", StructSize, size); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/IO/WriteOnlySerializer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/IO/WriteOnlySerializer.cs index d57df8cea..d9dd75871 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/IO/WriteOnlySerializer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/IO/WriteOnlySerializer.cs @@ -36,7 +36,11 @@ public virtual void Serialize(BoltProtocolVersion _, PackStreamWriter writer, ob Serialize(writer, value); } - public (object, int) DeserializeSpan(BoltProtocolVersion version, SpanPackStreamReader reader, byte signature, int size) + public (object, int) DeserializeSpan( + BoltProtocolVersion version, + SpanPackStreamReader reader, + byte signature, + int size) { throw new NotImplementedException(); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/HelloResponseHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/HelloResponseHandler.cs index 9ef074be6..7597bbe8d 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/HelloResponseHandler.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/HelloResponseHandler.cs @@ -50,6 +50,7 @@ public override void OnSuccess(IDictionary metadata) UpdateReadTimeout(configMetadata); UpdateTelemetryEnabled(configMetadata); + UpdateSsrEnabled(configMetadata); } private void UpdateUtcEncodedDateTime() @@ -95,6 +96,20 @@ private void UpdateTelemetryEnabled(Dictionary configMetadata) } } + private void UpdateSsrEnabled(Dictionary configMetadata) + { + if (configMetadata == null || _connection.Version < BoltProtocolVersion.V5_8) + { + return; + } + + var found = configMetadata.TryGetValue("ssr.enabled", out var value); + if (found && value is bool ssrEnabled) + { + _connection.SsrEnabled = ssrEnabled; + } + } + private void UpdateConnectionServerVersion() { _connection.UpdateVersion(GetMetadata()); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollector.cs b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollector.cs index 85b2b36db..20a7fc2c0 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollector.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/Metadata/GqlStatusObjectsAndNotificationsCollector.cs @@ -43,15 +43,19 @@ public void Collect(IDictionary metadata) { statuses = ConvertObjects(metadata, NotificationsKey, ConvertNotificationValuesToStatus); } + if (notifications == null && statuses != null) { notifications = ConvertObjects(metadata, StatusesKey, ConvertStatusValuesToNotification); } - + Collected = new GqlStatusObjectsAndNotifications(notifications, statuses, useRawStatuses); } - private static IList ConvertObjects(IDictionary metadata, string key, Func, T> parse) + private static IList ConvertObjects( + IDictionary metadata, + string key, + Func, T> parse) { if (metadata.TryGetValue(key, out var x) && x is IList statuses) { @@ -73,7 +77,7 @@ private static INotification ConvertNotification(IDictionary not var severity = notification.GetValue("severity", string.Empty); var category = notification.GetValue("category", default(string)); var position = InputPosition.ConvertFromDictionary(notification, "position"); - + return new Notification( code, title, @@ -112,10 +116,11 @@ private static IGqlStatusObject ConvertNotificationValuesToStatus(IDictionary("code", null); var description = notification.GetValue("description", null) ?? (isWarning @@ -129,17 +134,19 @@ private static IGqlStatusObject ConvertNotificationValuesToStatus(IDictionary("title", null); - + return new GqlStatusObject( status, description, @@ -158,6 +165,7 @@ private static INotification ConvertStatusValuesToNotification(IDictionary>(); } @@ -30,6 +45,15 @@ public RouteResponseHandler() public override void OnSuccess(IDictionary metadata) { base.OnSuccess(metadata); + + if(_isDefaultRequest && metadata?["rt"] is IDictionary rt && rt.TryGetValue("db", out var db)) + { + var dbName = (string) db; + _sessionConfig?.DriverContext?.Logger?.Debug($"Caching database name '{dbName}' for key '{_cacheKey}'"); + _homeDbCache.AddOrUpdate(_cacheKey, dbName); + _sessionConfig?.PinDatabase(dbName); + } + RoutingInformation = GetMetadata>(); } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/TelemetryResponseHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/TelemetryResponseHandler.cs index de2be3dc3..eb1dafe7e 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/TelemetryResponseHandler.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/TelemetryResponseHandler.cs @@ -27,25 +27,25 @@ public TelemetryResponseHandler(TransactionInfo info) _info = info; } - /// + /// public void OnSuccess(IDictionary metadata) { _info.SetAcked(); } - /// + /// public void OnRecord(object[] fieldValues) { // will not be called } - /// + /// public void OnFailure(IResponsePipelineError error) { // do nothing } - /// + /// public void OnIgnored() { // do nothing diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V3/RunResponseHandlerV3.cs b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V3/RunResponseHandlerV3.cs index 27152de81..411765ab4 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V3/RunResponseHandlerV3.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V3/RunResponseHandlerV3.cs @@ -26,7 +26,9 @@ internal sealed class RunResponseHandlerV3 : MetadataCollectingResponseHandler private readonly IResultStreamBuilder _streamBuilder; private readonly SummaryBuilder _summaryBuilder; - public RunResponseHandlerV3(IResultStreamBuilder streamBuilder, SummaryBuilder summaryBuilder) + public RunResponseHandlerV3( + IResultStreamBuilder streamBuilder, + SummaryBuilder summaryBuilder) { _streamBuilder = streamBuilder ?? throw new ArgumentNullException(nameof(streamBuilder)); _summaryBuilder = summaryBuilder ?? throw new ArgumentNullException(nameof(summaryBuilder)); @@ -40,7 +42,6 @@ public override void OnSuccess(IDictionary metadata) base.OnSuccess(metadata); _summaryBuilder.ResultAvailableAfter = GetMetadata(); - _streamBuilder.RunCompleted(NoQueryId, GetMetadata(), null); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/BeginResponseHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/BeginResponseHandler.cs new file mode 100644 index 000000000..1ec09a6ee --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/BeginResponseHandler.cs @@ -0,0 +1,56 @@ +// Copyright (c) "Neo4j" +// Neo4j Sweden AB [https://neo4j.com] +// +// 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.HomeDbCaching; +using Neo4j.Driver.Internal.MessageHandling.Metadata; + +namespace Neo4j.Driver.Internal.MessageHandling.V4; + +internal sealed class BeginResponseHandler : MetadataCollectingResponseHandler +{ + private readonly HomeDbCacheKey _cacheKey; + private readonly IHomeDbCache _homeDbCache; + private readonly SessionConfig _sessionConfig; + private readonly bool _isDefaultDatabase; + + public BeginResponseHandler( + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultDatabase) + { + _cacheKey = cacheKey; + _homeDbCache = homeDbCache; + _sessionConfig = sessionConfig; + _isDefaultDatabase = isDefaultDatabase; + AddMetadata(); + } + + public override void OnSuccess(IDictionary metadata) + { + base.OnSuccess(metadata); + + var dbInfo = GetMetadata(); + if (_isDefaultDatabase && _homeDbCache != null && dbInfo?.Name != null) + { + _sessionConfig.DriverContext.Logger?.Debug( + $"Caching database name '{dbInfo.Name}' for key '{_cacheKey}'"); + + _homeDbCache.AddOrUpdate(_cacheKey, dbInfo.Name); + _sessionConfig?.PinDatabase(dbInfo.Name); + } + } +} diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/RunResponseHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/RunResponseHandler.cs index 1dac105b9..5dedda32a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/RunResponseHandler.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/MessageHandling/V4/RunResponseHandler.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling.Metadata; using Neo4j.Driver.Internal.Result; using static Neo4j.Driver.Internal.Messaging.ResultHandleMessage; @@ -23,17 +24,32 @@ namespace Neo4j.Driver.Internal.MessageHandling.V4; internal sealed class RunResponseHandler : MetadataCollectingResponseHandler { + private readonly HomeDbCacheKey _cacheKey; + private readonly IHomeDbCache _homeDbCache; + private readonly SessionConfig _sessionConfig; private readonly IResultStreamBuilder _streamBuilder; private readonly SummaryBuilder _summaryBuilder; + private readonly bool _isDefaultDatabase; - public RunResponseHandler(IResultStreamBuilder streamBuilder, SummaryBuilder summaryBuilder) + public RunResponseHandler( + IResultStreamBuilder streamBuilder, + SummaryBuilder summaryBuilder, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultDatabase) { _streamBuilder = streamBuilder ?? throw new ArgumentNullException(nameof(streamBuilder)); _summaryBuilder = summaryBuilder ?? throw new ArgumentNullException(nameof(summaryBuilder)); + _cacheKey = cacheKey; + _homeDbCache = homeDbCache; + _sessionConfig = sessionConfig; + _isDefaultDatabase = isDefaultDatabase; AddMetadata(); AddMetadata(); AddMetadata(); + AddMetadata(); } public override void OnSuccess(IDictionary metadata) @@ -46,6 +62,14 @@ public override void OnSuccess(IDictionary metadata) GetMetadata(), GetMetadata(), null); + + var dbInfo = GetMetadata(); + if (_isDefaultDatabase && _homeDbCache != null && dbInfo?.Name != null) + { + _sessionConfig.DriverContext.Logger?.Debug($"Caching database name '{dbInfo.Name}' for key '{_cacheKey}'"); + _homeDbCache.AddOrUpdate(_cacheKey, dbInfo.Name); + _sessionConfig?.PinDatabase(dbInfo.Name); + } } public override void OnFailure(IResponsePipelineError error) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/FailureMessage.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/FailureMessage.cs index 6a48a08e5..5bb2e5ed7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/FailureMessage.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/FailureMessage.cs @@ -32,46 +32,33 @@ public FailureMessage(string code, string message) Message = message; } - /// - /// Code is the Neo4j-specific error code, to be deprecated in favor of GqlStatus. - /// + /// Code is the Neo4j-specific error code, to be deprecated in favor of GqlStatus. public string Code { get; set; } - /// - /// The specific error message describing the failure. - /// + /// The specific error message describing the failure. public string Message { get; set; } - /// - /// Returns the GQLSTATUS. - /// + /// Returns the GQLSTATUS. public string GqlStatus { get; set; } - /// - /// Provides a standard description for the associated GQLStatus code. - /// + /// Provides a standard description for the associated GQLStatus code. public string GqlStatusDescription { get; set; } - /// - /// A high-level categorization of the error, specific to GQL error handling. - /// + /// A high-level categorization of the error, specific to GQL error handling. public string GqlClassification { get; set; } - /// - /// The raw classification as received from the server. - /// + /// The raw classification as received from the server. public string GqlRawClassification { get; set; } /// - /// GqlDiagnosticRecord returns further information about the status for diagnostic purposes. - /// GqlDiagnosticRecord is part of the GQL compliant errors preview feature. + /// GqlDiagnosticRecord returns further information about the status for diagnostic purposes. GqlDiagnosticRecord + /// is part of the GQL compliant errors preview feature. /// public Dictionary GqlDiagnosticRecord { get; set; } /// - /// GqlCause represents the underlying error, if any, which caused the current error. - /// GqlCause is part of the GQL compliant errors preview feature - /// (see README on what it means in terms of support and compatibility guarantees) + /// GqlCause represents the underlying error, if any, which caused the current error. GqlCause is part of the GQL + /// compliant errors preview feature (see README on what it means in terms of support and compatibility guarantees) /// public FailureMessage GqlCause { get; set; } @@ -86,6 +73,4 @@ public override string ToString() { return $"FAILURE code={Code}, message={Message}"; } - - } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessage.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessage.cs index d7c55d52b..84948805a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessage.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessage.cs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Generic; using System.Text; using Neo4j.Driver.Internal.IO; @@ -33,7 +32,7 @@ public RouteMessage( string impersonatedUser) { Routing = routingContext ?? new Dictionary(); - Bookmarks = bookmarks ?? Bookmarks.From(Array.Empty()); + Bookmarks = bookmarks ?? Bookmarks.From(); DatabaseContext = new Dictionary(); if (!string.IsNullOrEmpty(databaseName)) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessageV43.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessageV43.cs index d08c1b494..7ea8b5608 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessageV43.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/RouteMessageV43.cs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Generic; using System.Text; using Neo4j.Driver.Internal.IO; @@ -26,7 +25,7 @@ internal sealed class RouteMessageV43 : IRequestMessage public RouteMessageV43(IDictionary routingContext, Bookmarks bookmarks, string db) { Routing = routingContext ?? new Dictionary(); - Bookmarks = bookmarks ?? Bookmarks.From(Array.Empty()); + Bookmarks = bookmarks ?? Bookmarks.From(); DatabaseParam = db; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/TelemetryMessage.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/TelemetryMessage.cs index f5e36379a..28f696e63 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/TelemetryMessage.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Messaging/TelemetryMessage.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -23,13 +21,13 @@ namespace Neo4j.Driver.Internal.Messaging; internal class TelemetryMessage : IRequestMessage { - public QueryApiType Api { get; } - public TelemetryMessage(QueryApiType apiType) { Api = apiType; } + public QueryApiType Api { get; } + public IPackStreamSerializer Serializer => TelemetryMessageSerializer.Instance; public override string ToString() diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BeginTransactionParams.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BeginTransactionParams.cs index 8aee96dc7..ea19d6004 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BeginTransactionParams.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BeginTransactionParams.cs @@ -28,6 +28,12 @@ internal sealed record BeginTransactionParams( internal sealed record TransactionInfo { + /// Holds if the driver enables the sending of metrics to Neo4j. + private readonly bool _enabled; + + // This is used to ensure that the transaction meta is only sent once. + private long _interlocked; + public TransactionInfo(QueryApiType apiType, bool metricsEnabled, bool awaitBegin) { ApiType = apiType; @@ -35,20 +41,10 @@ public TransactionInfo(QueryApiType apiType, bool metricsEnabled, bool awaitBegi _enabled = metricsEnabled; } - // This is used to ensure that the transaction meta is only sent once. - private long _interlocked; - - /// - /// Holds if the driver enables the sending of metrics to Neo4j. - /// - private readonly bool _enabled; - public QueryApiType ApiType { get; } public bool AwaitBegin { get; } - /// - /// Returns true if driver enabled and hasn't been acked yet. - /// + /// Returns true if driver enabled and hasn't been acked yet. public bool TelemetryEnabled => _enabled && !Acked; public bool Acked => Interlocked.Read(ref _interlocked) > 0; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocol.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocol.cs index 54974669c..29c9ba8bd 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocol.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocol.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; using Neo4j.Driver.Internal.Protocol.Utility; @@ -73,7 +74,8 @@ public Task> GetRoutingTableAsync( IConnection connection, string database, SessionConfig sessionConfig, - Bookmarks bookmarks) + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { connection = connection ?? throw new ProtocolException("Attempting to get a routing table on a null connection"); @@ -82,14 +84,20 @@ public Task> GetRoutingTableAsync( BoltProtocolV3.ValidateImpersonatedUserForVersion(connection); return connection.Version >= BoltProtocolVersion.V4_3 - ? GetRoutingTableWithRouteMessageAsync(connection, database, sessionConfig?.ImpersonatedUser, bookmarks) - : GetRoutingTableWithQueryAsync(connection, database, bookmarks); + ? GetRoutingTableWithRouteMessageAsync( + connection, + database, + sessionConfig, + bookmarks, + homeDbCache) + : GetRoutingTableWithQueryAsync(connection, database, bookmarks, homeDbCache); } public async Task RunInAutoCommitTransactionAsync( IConnection connection, AutoCommitParams autoCommitParams, - INotificationsConfig notificationsConfig) + INotificationsConfig notificationsConfig, + IHomeDbCache homeDbCache) { BoltProtocolV3.ValidateImpersonatedUserForVersion(connection); BoltProtocolV3.ValidateNotificationsForVersion(connection, notificationsConfig); @@ -112,7 +120,15 @@ public async Task RunInAutoCommitTransactionAsync( autoCommitParams, notificationsConfig); - var runHandler = _protocolHandlerFactory.NewRunResponseHandler(streamBuilder, summaryBuilder); + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(connection.AuthToken, autoCommitParams.SessionConfig); + + var runHandler = _protocolHandlerFactory.NewRunResponseHandler( + streamBuilder, + summaryBuilder, + cacheKey, + homeDbCache, + autoCommitParams.SessionConfig, + autoCommitParams.Database == null); await AddTelemetryAsync(connection, autoCommitParams.TransactionInfo).ConfigureAwait(false); @@ -136,13 +152,19 @@ public async Task RunInAutoCommitTransactionAsync( return streamBuilder.CreateCursor(); } - public async Task BeginTransactionAsync(IConnection connection, BeginTransactionParams beginParams) + public async Task BeginTransactionAsync( + IConnection connection, + BeginTransactionParams beginParams, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig) { connection.SessionConfig = beginParams.SessionConfig; BoltProtocolV3.ValidateImpersonatedUserForVersion(connection); BoltProtocolV3.ValidateNotificationsForVersion(connection, beginParams.NotificationsConfig); await AddTelemetryAsync(connection, beginParams.TransactionInfo).ConfigureAwait(false); - await _boltProtocolV3.BeginTransactionAsync(connection, beginParams).ConfigureAwait(false); + await _boltProtocolV3.BeginTransactionAsync(connection, beginParams, cacheKey, homeDbCache, sessionConfig) + .ConfigureAwait(false); } public async Task RunInExplicitTransactionAsync( @@ -150,7 +172,10 @@ public async Task RunInExplicitTransactionAsync( Query query, bool reactive, long fetchSize, - IInternalAsyncTransaction transaction) + IInternalAsyncTransaction transaction, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig) { var summaryBuilder = new SummaryBuilder(query, connection.Server); @@ -166,7 +191,13 @@ public async Task RunInExplicitTransactionAsync( transaction); var runMessage = _protocolMessageFactory.NewRunWithMetadataMessage(connection, query, null); - var runHandler = _protocolHandlerFactory.NewRunResponseHandler(streamBuilder, summaryBuilder); + var runHandler = _protocolHandlerFactory.NewRunResponseHandler( + streamBuilder, + summaryBuilder, + cacheKey, + homeDbCache, + sessionConfig, + false); if (!reactive) { @@ -228,7 +259,8 @@ private async Task AuthenticateWithLogonAsync( private async Task> GetRoutingTableWithQueryAsync( IConnection connection, string database, - Bookmarks bookmarks) + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { connection.ConfigureMode(AccessMode.Read); @@ -250,7 +282,9 @@ private async Task> GetRoutingTableWithQuery Bookmarks = bookmarks }; - var result = await RunInAutoCommitTransactionAsync(connection, autoCommitParams, null).ConfigureAwait(false); + var result = await RunInAutoCommitTransactionAsync(connection, autoCommitParams, null, homeDbCache) + .ConfigureAwait(false); + var record = await result.SingleAsync().ConfigureAwait(false); //Since 4.4 the Routing information will contain a db. @@ -264,8 +298,9 @@ private async Task> GetRoutingTableWithQuery private async Task> GetRoutingTableWithRouteMessageAsync( IConnection connection, string database, - string impersonatedUser, - Bookmarks bookmarks) + SessionConfig sessionConfig, + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { var dbParameter = string.IsNullOrWhiteSpace(database) ? null : database; //TODO: Consider refactoring logic of v43 into message factory. @@ -278,9 +313,11 @@ private async Task> GetRoutingTableWithRoute connection, bookmarks, dbParameter, - impersonatedUser); + sessionConfig?.ImpersonatedUser); - var responseHandler = _protocolHandlerFactory.NewRouteResponseHandler(); + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(connection.AuthToken, sessionConfig); + var responseHandler = + _protocolHandlerFactory.NewRouteResponseHandler(cacheKey, homeDbCache, sessionConfig, dbParameter == null); await connection.EnqueueAsync(message, responseHandler).ConfigureAwait(false); await connection.SyncAsync().ConfigureAwait(false); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolFactory.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolFactory.cs index 25b78a08b..a355dc4e7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolFactory.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolFactory.cs @@ -56,7 +56,8 @@ internal class BoltProtocolFactory : IBoltProtocolFactory BoltProtocolVersion.V5_4, BoltProtocolVersion.V5_5, BoltProtocolVersion.V5_6, - BoltProtocolVersion.V5_7 + BoltProtocolVersion.V5_7, + BoltProtocolVersion.V5_8 }; private static readonly Lazy HandshakeBytesLazy = @@ -75,7 +76,7 @@ internal class BoltProtocolFactory : IBoltProtocolFactory BoltProtocolVersion.HandshakeManifestV1.PackToInt(), // 3 more versions max. - BoltProtocolVersion.V5_7.PackToIntRange(BoltProtocolVersion.V5_0), + BoltProtocolVersion.V5_8.PackToIntRange(BoltProtocolVersion.V5_0), BoltProtocolVersion.V4_4.PackToIntRange(BoltProtocolVersion.V4_2), BoltProtocolVersion.V3_0.PackToInt() }; @@ -101,7 +102,7 @@ public IBoltProtocol ForVersion(BoltProtocolVersion version) { MajorVersion: 0, MinorVersion: 0 } => throw new NotSupportedException(NoAgreedVersion), { MajorVersion: 3, MinorVersion: 0 } => BoltProtocolV3.Instance, { MajorVersion: 4, MinorVersion: <= 4, MinorVersion: >= 1 } => BoltProtocol.Instance, - { MajorVersion: 5, MinorVersion: <= 7, MinorVersion: >= 0 } => BoltProtocol.Instance, + { MajorVersion: 5, MinorVersion: <= 8, MinorVersion: >= 0 } => BoltProtocol.Instance, _ => throw new NotSupportedException( $"Protocol error, server suggested unexpected protocol version: {version}") }; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolHandlerFactory.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolHandlerFactory.cs index 9fa58ec75..ece5d4811 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolHandlerFactory.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolHandlerFactory.cs @@ -14,8 +14,10 @@ // limitations under the License. using System; +using System.Collections.Generic; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.MessageHandling.V3; using Neo4j.Driver.Internal.MessageHandling.V4; @@ -36,7 +38,13 @@ IResultCursorBuilder NewResultCursorBuilder( bool reactive, IInternalAsyncTransaction transaction); - RunResponseHandler NewRunResponseHandler(IResultCursorBuilder streamBuilder, SummaryBuilder summaryBuilder); + RunResponseHandler NewRunResponseHandler( + IResultCursorBuilder streamBuilder, + SummaryBuilder summaryBuilder, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultDatabase); PullResponseHandler NewPullResponseHandler( IConnection connection, @@ -44,7 +52,12 @@ PullResponseHandler NewPullResponseHandler( IResultStreamBuilder cursorBuilder, SummaryBuilder summaryBuilder); - RouteResponseHandler NewRouteResponseHandler(); + RouteResponseHandler NewRouteResponseHandler( + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultRequest); + HelloResponseHandler NewHelloResponseHandler(IConnection connection); CommitResponseHandler NewCommitResponseHandler(IBookmarksTracker bookmarksTracker); @@ -58,6 +71,12 @@ PullAllResponseHandler NewPullAllResponseHandler( IBookmarksTracker bookmarksTracker); TelemetryResponseHandler NewTelemetryResponseHandler(TransactionInfo info); + + BeginResponseHandler NewBeginResponseHandler( + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultDatabase); } internal class BoltProtocolHandlerFactory : IBoltProtocolHandlerFactory @@ -86,9 +105,21 @@ public IResultCursorBuilder NewResultCursorBuilder( transaction); } - public RunResponseHandler NewRunResponseHandler(IResultCursorBuilder streamBuilder, SummaryBuilder summaryBuilder) + public RunResponseHandler NewRunResponseHandler( + IResultCursorBuilder streamBuilder, + SummaryBuilder summaryBuilder, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultDatabase) { - return new RunResponseHandler(streamBuilder, summaryBuilder); + return new RunResponseHandler( + streamBuilder, + summaryBuilder, + cacheKey, + homeDbCache, + sessionConfig, + isDefaultDatabase); } public PullResponseHandler NewPullResponseHandler( @@ -97,12 +128,20 @@ public PullResponseHandler NewPullResponseHandler( IResultStreamBuilder cursorBuilder, SummaryBuilder summaryBuilder) { - return new PullResponseHandler(cursorBuilder, summaryBuilder, bookmarksTracker, connection.Version >= BoltProtocolVersion.V5_5); + return new PullResponseHandler( + cursorBuilder, + summaryBuilder, + bookmarksTracker, + connection.Version >= BoltProtocolVersion.V5_5); } - public RouteResponseHandler NewRouteResponseHandler() + public RouteResponseHandler NewRouteResponseHandler( + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultRequest) { - return new RouteResponseHandler(); + return new RouteResponseHandler(cacheKey, homeDbCache, sessionConfig, isDefaultRequest); } public HelloResponseHandler NewHelloResponseHandler(IConnection connection) @@ -134,4 +173,14 @@ public TelemetryResponseHandler NewTelemetryResponseHandler(TransactionInfo info { return new TelemetryResponseHandler(info); } + + /// + public BeginResponseHandler NewBeginResponseHandler( + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig, + bool isDefaultDatabase) + { + return new BeginResponseHandler(cacheKey, homeDbCache, sessionConfig, isDefaultDatabase); + } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolV3.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolV3.cs index caac23ddf..2b4c38385 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolV3.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolV3.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; using Neo4j.Driver.Internal.Messaging; using Neo4j.Driver.Internal.Protocol.Utility; @@ -83,7 +84,8 @@ public async Task> GetRoutingTableAsync( IConnection connection, string database, SessionConfig sessionConfig, - Bookmarks bookmarks) + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { connection = connection ?? throw new ProtocolException("Attempting to get a routing table on a null connection"); @@ -108,7 +110,9 @@ public async Task> GetRoutingTableAsync( ResultResourceHandler = resourceHandler }; - var result = await RunInAutoCommitTransactionAsync(connection, autoCommitParams, null).ConfigureAwait(false); + var result = await RunInAutoCommitTransactionAsync(connection, autoCommitParams, null, homeDbCache) + .ConfigureAwait(false); + var record = await result.SingleAsync().ConfigureAwait(false); //Since 4.4 the Routing information will contain a db. @@ -122,7 +126,8 @@ public async Task> GetRoutingTableAsync( public async Task RunInAutoCommitTransactionAsync( IConnection connection, AutoCommitParams autoCommitParams, - INotificationsConfig notificationsConfig) + INotificationsConfig notificationsConfig, + IHomeDbCache homeDbCache) { connection.SessionConfig = autoCommitParams.SessionConfig; ValidateImpersonatedUserForVersion(connection); @@ -162,7 +167,12 @@ await connection.EnqueueAsync(autoCommitMessage, runHandler, PullAllMessage.Inst return streamBuilder.CreateCursor(); } - public async Task BeginTransactionAsync(IConnection connection, BeginTransactionParams beginParams) + public async Task BeginTransactionAsync( + IConnection connection, + BeginTransactionParams beginParams, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig) { connection.SessionConfig = beginParams.SessionConfig; ValidateImpersonatedUserForVersion(connection); @@ -180,19 +190,28 @@ public async Task BeginTransactionAsync(IConnection connection, BeginTransaction mode, beginParams.NotificationsConfig); - await connection.EnqueueAsync(message, NoOpResponseHandler.Instance).ConfigureAwait(false); + var responseHandler = _protocolHandlerFactory.NewBeginResponseHandler( + cacheKey, + homeDbCache, + sessionConfig, + beginParams.Database == null); + + await connection.EnqueueAsync(message, responseHandler).ConfigureAwait(false); if (beginParams.TransactionInfo.AwaitBegin) { await connection.SyncAsync().ConfigureAwait(false); } } - + public async Task RunInExplicitTransactionAsync( IConnection connection, Query query, bool reactive, long _, - IInternalAsyncTransaction transaction) + IInternalAsyncTransaction transaction, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig) { var summaryBuilder = new SummaryBuilder(query, connection.Server); var streamBuilder = _protocolHandlerFactory.NewResultCursorBuilder( @@ -216,7 +235,7 @@ public async Task RunInExplicitTransactionAsync( await connection.EnqueueAsync(message, runHandler, PullAllMessage.Instance, pullAllHandler) .ConfigureAwait(false); - + await connection.SendAsync().ConfigureAwait(false); return streamBuilder.CreateCursor(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolVersion.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolVersion.cs index 579caff7a..eff0bbf31 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolVersion.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolVersion.cs @@ -45,8 +45,9 @@ internal sealed class BoltProtocolVersion : IEquatable, ICo public static readonly BoltProtocolVersion V5_5 = new(5, 5); public static readonly BoltProtocolVersion V5_6 = new(5, 6); public static readonly BoltProtocolVersion V5_7 = new(5, 7); + public static readonly BoltProtocolVersion V5_8 = new(5, 8); - public static readonly BoltProtocolVersion LatestVersion = V5_7; + public static readonly BoltProtocolVersion LatestVersion = V5_8; public static readonly BoltProtocolVersion HandshakeManifestV1 = new(ManifestSchema, ManifestVersion); // ReSharper restore InconsistentNaming diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/IBoltProtocol.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/IBoltProtocol.cs index 10449eadb..1274507b5 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/IBoltProtocol.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/IBoltProtocol.cs @@ -13,9 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Collections.Generic; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.MessageHandling; namespace Neo4j.Driver.Internal.Protocol; @@ -36,21 +38,31 @@ Task> GetRoutingTableAsync( IConnection connection, string database, SessionConfig sessionConfig, - Bookmarks bookmarks); + Bookmarks bookmarks, + IHomeDbCache homeDbCache); Task RunInAutoCommitTransactionAsync( IConnection connection, AutoCommitParams autoCommitParams, - INotificationsConfig notificationsConfig); + INotificationsConfig notificationsConfig, + IHomeDbCache homeDbCache); - Task BeginTransactionAsync(IConnection connection, BeginTransactionParams beginParams); + Task BeginTransactionAsync( + IConnection connection, + BeginTransactionParams beginParams, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig); Task RunInExplicitTransactionAsync( IConnection connection, Query query, bool reactive, long fetchSize, - IInternalAsyncTransaction transaction); + IInternalAsyncTransaction transaction, + HomeDbCacheKey cacheKey, + IHomeDbCache homeDbCache, + SessionConfig sessionConfig); Task CommitTransactionAsync(IConnection connection, IBookmarksTracker bookmarksTracker); Task RollbackTransactionAsync(IConnection connection); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/MessageFormat.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/MessageFormat.cs index e630e8415..44d8f23fe 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/MessageFormat.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/MessageFormat.cs @@ -55,6 +55,7 @@ internal sealed class MessageFormat // v5.4+ public const byte MsgTelemetry = 0x54; + private readonly Dictionary _messageReaders = new(); private readonly Dictionary _readerStructHandlers = new(); private readonly Dictionary _writerStructHandlers = new(); @@ -143,14 +144,13 @@ internal MessageFormat( public IReadOnlyDictionary WriteStructHandlers => _writerStructHandlers; public BoltProtocolVersion Version { get; } - private readonly Dictionary _messageReaders = new(); public IReadOnlyDictionary MessageReaders => _messageReaders; private void AddMessageHandler(T instance) where T : class, IPackStreamMessageDeserializer, IPackStreamSerializer { _messageReaders.Add(instance.ReadableStructs[0], instance); } - + private void AddHandler(T instance) where T : class, IPackStreamSerializer { foreach (var readableStruct in instance.ReadableStructs) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/ConnectionResourceHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/ConnectionResourceHandler.cs index cad9646c5..d29d02ca0 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/ConnectionResourceHandler.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/ConnectionResourceHandler.cs @@ -20,13 +20,13 @@ namespace Neo4j.Driver.Internal.Protocol.Utility; internal sealed class ConnectionResourceHandler : IResultResourceHandler { + private readonly IConnection _connection; + public ConnectionResourceHandler(IConnection conn) { _connection = conn; } - private IConnection _connection; - public Task OnResultConsumedAsync() { return Task.CompletedTask; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/NullTransaction.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/NullTransaction.cs index acf0f9b05..d9d5fd622 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/NullTransaction.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Protocol/Utility/NullTransaction.cs @@ -20,8 +20,8 @@ namespace Neo4j.Driver.Internal.Protocol.Utility; /// -/// Supports cursors on auto commit functions so we don't need to null check and check error. -/// this will always return false for the transaction has errored because the transaction doen't exist. +/// Supports cursors on auto commit functions so we don't need to null check and check error. this will always +/// return false for the transaction has errored because the transaction doen't exist. /// internal sealed class NullTransaction : IInternalAsyncTransaction { @@ -30,7 +30,7 @@ internal sealed class NullTransaction : IInternalAsyncTransaction private NullTransaction() { } - + public void Dispose() { } @@ -42,7 +42,8 @@ public ValueTask DisposeAsync() public Task RunAsync(string query) { - throw new InvalidOperationException("Something very wrong has happened and an illegal function has been called."); + throw new InvalidOperationException( + "Something very wrong has happened and an illegal function has been called."); } public Task RunAsync(string query, object parameters) @@ -65,6 +66,7 @@ public Task RunAsync(Query query) public TransactionConfig TransactionConfig => throw new InvalidOperationException( "Something very wrong has happened and an illegal function has been called."); + public Task CommitAsync() { throw new InvalidOperationException( diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/CursorEnumerator.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/CursorEnumerator.cs index dde9d5b8f..79fd74518 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/CursorEnumerator.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/CursorEnumerator.cs @@ -1,7 +1,5 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. +// Neo4j Sweden AB [https://neo4j.com] // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs index 9df090ac2..675db4ee4 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs @@ -22,8 +22,8 @@ namespace Neo4j.Driver.Internal.Result; internal sealed class Record : IRecord { private readonly IReadOnlyDictionary _fieldLookup; - private readonly IReadOnlyDictionary _invariantFieldLookup; private readonly object[] _fieldValues; + private readonly IReadOnlyDictionary _invariantFieldLookup; private IReadOnlyList _keys; public Record( @@ -36,19 +36,19 @@ public Record( _fieldValues = values; } - /// + /// public object this[int index] => _fieldValues[index]; /// public object this[string key] => _fieldValues[_fieldLookup[key]]; - /// + /// public T Get(string key) { return _fieldValues[_fieldLookup[key]].As(); } - /// + /// public bool TryGet(string key, out T value) { if (_fieldLookup.TryGetValue(key, out var index)) @@ -61,13 +61,13 @@ public bool TryGet(string key, out T value) return false; } - /// + /// public T GetCaseInsensitive(string key) { return _fieldValues[_invariantFieldLookup[key]].As(); } - /// + /// public bool TryGetCaseInsensitive(string key, out T value) { if (_invariantFieldLookup.TryGetValue(key, out var index)) @@ -80,33 +80,42 @@ public bool TryGetCaseInsensitive(string key, out T value) return false; } - /// + /// public IReadOnlyList Keys => _keys ??= _fieldLookup.Keys.ToList(); - /// + /// public IReadOnlyDictionary Values => this; - /// - bool IReadOnlyDictionary.ContainsKey(string key) => _fieldLookup.ContainsKey(key); + /// + bool IReadOnlyDictionary.ContainsKey(string key) + { + return _fieldLookup.ContainsKey(key); + } - /// - bool IReadOnlyDictionary.TryGetValue(string key, out object value) => TryGet(key, out value); + /// + bool IReadOnlyDictionary.TryGetValue(string key, out object value) + { + return TryGet(key, out value); + } - /// + /// IEnumerable IReadOnlyDictionary.Keys => Keys; - /// + /// IEnumerable IReadOnlyDictionary.Values => _fieldValues; - /// + /// IEnumerator> IEnumerable>.GetEnumerator() { return Keys.Select((key, i) => new KeyValuePair(key, _fieldValues[i])).GetEnumerator(); } - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable>)this).GetEnumerator(); + } - /// + /// int IReadOnlyCollection>.Count => _fieldLookup.Count; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultCursor.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultCursor.cs index 511337fbf..d8c0482d2 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultCursor.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultCursor.cs @@ -22,11 +22,10 @@ namespace Neo4j.Driver.Internal.Result; internal sealed class ResultCursor : IInternalResultCursor, IAsyncEnumerator { - private bool _isConsumed; - private readonly IResultStream _resultStream; private bool _atEnd; private IRecord _current; + private bool _isConsumed; private Task _keys; private IRecord _peeked; @@ -93,7 +92,6 @@ public Task ConsumeAsync() return _summary; } - public async Task PeekAsync() { AssertNotConsumed(); @@ -115,7 +113,6 @@ public async Task PeekAsync() _atEnd = true; return null; - } public Task FetchAsync() diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/Summary/GqlStatusObject.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/Summary/GqlStatusObject.cs index de3de131e..132ed27f7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/Summary/GqlStatusObject.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/Summary/GqlStatusObject.cs @@ -30,7 +30,19 @@ internal sealed record GqlStatusObject( bool IsNotification) : IGqlStatusObject { - private GqlStatusObject(string gqlStatus, string description): this( + internal static readonly IGqlStatusObject OmittedResult = new GqlStatusObject( + "00001", + "note: successful completion - omitted result"); + + internal static IGqlStatusObject Success = new GqlStatusObject( + "00000", + "note: successful completion"); + + internal static readonly IGqlStatusObject NoData = new GqlStatusObject( + "02000", + "note: no data"); + + private GqlStatusObject(string gqlStatus, string description) : this( gqlStatus, description, null, @@ -55,6 +67,13 @@ private GqlStatusObject(string gqlStatus, string description): this( public NotificationClassification Classification => ClassificationFrom(RawClassification); + public NotificationSeverity Severity => Notification.ParseSeverity(RawSeverity); + + public IReadOnlyDictionary DiagnosticRecord { get; } = + DiagnosticRecord ?? throw new ArgumentNullException(nameof(DiagnosticRecord)); + + public string RawDiagnosticRecord => DiagnosticRecord.ToContentString(); + private NotificationClassification ClassificationFrom(string rawClassification) { return rawClassification?.ToLowerInvariant() switch @@ -71,25 +90,4 @@ private NotificationClassification ClassificationFrom(string rawClassification) _ => NotificationClassification.Unknown }; } - - public NotificationSeverity Severity => Notification.ParseSeverity(RawSeverity); - - public IReadOnlyDictionary DiagnosticRecord { get; } = - DiagnosticRecord ?? throw new ArgumentNullException(nameof(DiagnosticRecord)); - - public string RawDiagnosticRecord => DiagnosticRecord.ToContentString(); - - internal static readonly IGqlStatusObject OmittedResult = new GqlStatusObject( - "00001", - "note: successful completion - omitted result"); - - internal static IGqlStatusObject Success = new GqlStatusObject( - "00000", - "note: successful completion"); - - - internal static readonly IGqlStatusObject NoData = new GqlStatusObject( - "02000", - "note: no data" - ); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterConnectionPool.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterConnectionPool.cs index 75d150d4b..cecc42235 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterConnectionPool.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterConnectionPool.cs @@ -132,6 +132,34 @@ public int NumberOfInUseConnections(Uri uri) return 0; } + public int TotalNumberOfConnections() + { + return _pools.Values.Sum(pool => pool.TotalNumberOfConnections); + } + + /// + public bool ConnectionCausesCacheDisable(IConnection connection) + { + var totalSsrEnabled = _pools.Values.Sum(pool => pool.NumberOfConnectionsWithSsrEnabled); + var totalSsrDisabled = _pools.Values.Sum(pool => pool.NumberOfConnectionsWithSsrDisabled); + return !connection.SsrEnabled && totalSsrEnabled > 0 && totalSsrDisabled == 1; + } + + /// + public bool CanUseHomeDbCache() + { + // sum the number of connections in all the pools with ssr enabled/disabled + var ssrEnabledCount = 0; + var ssrDisabledCount = 0; + foreach (var pool in _pools.Values) + { + ssrEnabledCount += pool.NumberOfConnectionsWithSsrEnabled; + ssrDisabledCount += pool.NumberOfConnectionsWithSsrDisabled; + } + + return ssrEnabledCount >= 1 && ssrDisabledCount == 0; + } + public ValueTask DisposeAsync() { return new ValueTask(CloseAsync()); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterDiscovery.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterDiscovery.cs index 9c996e5e1..3aae383db 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterDiscovery.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ClusterDiscovery.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.Util; namespace Neo4j.Driver.Internal.Routing; @@ -30,9 +31,14 @@ public async Task DiscoverAsync( IConnection connection, string database, SessionConfig sessionConfig, - Bookmarks bookmarks) + Bookmarks bookmarks, + IHomeDbCache homeDbCache) { - var routingTable = await connection.GetRoutingTableAsync(database, sessionConfig, bookmarks) + var routingTable = await connection.GetRoutingTableAsync( + database, + sessionConfig, + bookmarks, + homeDbCache) .ConfigureAwait(false); return ParseDiscoveryResult(routingTable); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IClusterConnectionPool.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IClusterConnectionPool.cs index 9493570f6..c14441a0c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IClusterConnectionPool.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IClusterConnectionPool.cs @@ -42,4 +42,10 @@ Task AcquireAsync( // Get number of in-use connections for the uri int NumberOfInUseConnections(Uri uri); + + bool CanUseHomeDbCache(); + int TotalNumberOfConnections(); + + // Check if the connection causes the cache to be disabled + bool ConnectionCausesCacheDisable(IConnection connection); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IDiscovery.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IDiscovery.cs index cc2f41a18..76217fadf 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IDiscovery.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IDiscovery.cs @@ -13,8 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; namespace Neo4j.Driver.Internal.Routing; @@ -24,5 +27,6 @@ Task DiscoverAsync( IConnection connection, string database, SessionConfig sessionConfig, - Bookmarks bookmarks); + Bookmarks bookmarks, + IHomeDbCache homeDbCache); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ILoadBalancingStrategy.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ILoadBalancingStrategy.cs index 691b9db64..55ac867e1 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ILoadBalancingStrategy.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/ILoadBalancingStrategy.cs @@ -20,6 +20,6 @@ namespace Neo4j.Driver.Internal.Routing; internal interface ILoadBalancingStrategy { - Uri SelectReader(IList knownReaders, string forDatabase); - Uri SelectWriter(IList knownWriters, string forDatabase); + Uri SelectReader(IList knownReaders, string forDatabase, string cachedDatabase = null); + Uri SelectWriter(IList knownWriters, string forDatabase, string cachedDatabase = null); } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IRoutingTableManager.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IRoutingTableManager.cs index 390351f80..b2d974d1d 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IRoutingTableManager.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/IRoutingTableManager.cs @@ -23,6 +23,7 @@ internal interface IRoutingTableManager Task EnsureRoutingTableForModeAsync( AccessMode mode, string database, + bool isCachedHomeDb, SessionConfig sessionConfig, Bookmarks bookmark); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LeastConnectedLoadBalancingStrategy.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LeastConnectedLoadBalancingStrategy.cs index 8c64e15c1..e5925aaa8 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LeastConnectedLoadBalancingStrategy.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LeastConnectedLoadBalancingStrategy.cs @@ -31,21 +31,22 @@ public LeastConnectedLoadBalancingStrategy(IClusterConnectionPool connectionPool _logger = logger; } - public Uri SelectReader(IList knownReaders, string forDatabase) + public Uri SelectReader(IList knownReaders, string forDatabase, string cachedDatabase = null) { - return Select(knownReaders, _readersIndex, forDatabase, "reader"); + return Select(knownReaders, _readersIndex, forDatabase, "reader", cachedDatabase); } - public Uri SelectWriter(IList knownWriters, string forDatabase) + public Uri SelectWriter(IList knownWriters, string forDatabase, string cachedDatabase = null) { - return Select(knownWriters, _writersIndex, forDatabase, "writer"); + return Select(knownWriters, _writersIndex, forDatabase, "writer", cachedDatabase); } private Uri Select( IList addresses, RoundRobinArrayIndex roundRobinIndex, string forDatabase, - string addressType) + string addressType, + string cachedDatabase) // this db only used for logging { var count = addresses.Count; if (count == 0) @@ -84,8 +85,14 @@ private Uri Select( } } while (index != startIndex); - LogDebug( - $"Selected {addressType} for database '{forDatabase}' with least connected address: '{leastConnectedAddress}' and active connections: {leastActiveConnections}"); + string logMessage = cachedDatabase == null + ? $"Selected {addressType} for database '{forDatabase}' with least connected address: " + + $"'{leastConnectedAddress}' and active connections: {leastActiveConnections}" + : $"Selected {addressType} for database '{forDatabase}' (using cached routing table for " + + $"'{cachedDatabase}') with least connected address: '{leastConnectedAddress}' and active " + + $"connections: {leastActiveConnections}"; + + LogDebug(logMessage); return leastConnectedAddress; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs index c108b472d..7765d9614 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; +using Neo4j.Driver.Internal.HomeDbCaching; using Neo4j.Driver.Internal.Logging; using static Neo4j.Driver.Internal.Util.ConnectionContext; @@ -49,21 +50,21 @@ public LoadBalancer( _logger = driverContext.Logger; _initialServerAddressProvider = new InitialServerAddressProvider(parsedUri, driverContext.Config.Resolver); - _routingTableManager = new RoutingTableManager(_initialServerAddressProvider, this, _logger); + _routingTableManager = new RoutingTableManager(_initialServerAddressProvider, this, DriverContext, _logger); _loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy( _clusterConnectionPool, _logger); } - - /// - /// TEST ONLY. - /// + + /// TEST ONLY. /// /// internal LoadBalancer( IClusterConnectionPool clusterConnPool, - IRoutingTableManager routingTableManager) + IRoutingTableManager routingTableManager, + DriverContext driverContext = null) { + DriverContext = driverContext; _logger = NullLogger.Instance; _clusterConnectionPool = clusterConnPool; _routingTableManager = routingTableManager; @@ -94,7 +95,7 @@ public async Task AcquireAsync( string database, SessionConfig sessionConfig, Bookmarks bookmarks, - bool forceAuth = false) + bool forceAuth) { if (IsClosed) { @@ -103,9 +104,21 @@ public async Task AcquireAsync( "Failed to acquire a new connection as the driver has already been disposed."); } + _logger.Debug($"LoadBalancer - Acquiring connection for '{database}'"); var conn = await AcquireConnectionAsync(mode, database, sessionConfig, bookmarks, forceAuth) .ConfigureAwait(false); + //If a non ssr connection is detected then the connection is not used and returned to the pool. Connection + //acquisition is then repeated with the cache not being used. + if (_clusterConnectionPool.ConnectionCausesCacheDisable(conn)) + { + _logger.Debug($"LoadBalancer - Mixed cluster detected, some connections have no SSR. Re-acquiring " + + $"connection without homeDB cache"); + await conn.CloseAsync().ConfigureAwait(false); + conn = await AcquireConnectionAsync(mode, database, sessionConfig, bookmarks, forceAuth) + .ConfigureAwait(false); + } + if (IsClosed) { throw new ObjectDisposedException( @@ -140,6 +153,9 @@ public async Task VerifyConnectivityAndGetInfoAsync() "ensure the database is running and that there is a working network connection to it."); } + /// + public bool IsDirectDriver => false; + public DriverContext DriverContext { get; } public Task SupportsMultiDbAsync() @@ -223,8 +239,32 @@ private async Task AcquireConnectionAsync( Bookmarks bookmarks, bool forceAuth) { + var cachedDatabaseUsed = false; + var databaseForRouting = sessionConfig?.Database ?? database; + var cacheKey = HomeDbCacheKeyProvider.GetCacheKey(null, sessionConfig); + + if (string.IsNullOrWhiteSpace(databaseForRouting) && _clusterConnectionPool.CanUseHomeDbCache()) + { + _logger.Debug($"Checking cached home database for {cacheKey}"); + cachedDatabaseUsed = DriverContext.HomeDbCache.TryGetCached(cacheKey, out databaseForRouting); + + if (cachedDatabaseUsed) + { + _logger.Debug($"Using cached home database {databaseForRouting} for {cacheKey}"); + } + else + { + _logger.Debug($"No cached home database found for {cacheKey}"); + } + } + var routingTable = await _routingTableManager - .EnsureRoutingTableForModeAsync(mode, database, sessionConfig, bookmarks) + .EnsureRoutingTableForModeAsync( + mode, + databaseForRouting, + cachedDatabaseUsed, + sessionConfig, + bookmarks) .ConfigureAwait(false); while (true) @@ -234,11 +274,11 @@ private async Task AcquireConnectionAsync( switch (mode) { case AccessMode.Read: - uri = _loadBalancingStrategy.SelectReader(routingTable.Readers, database); + uri = _loadBalancingStrategy.SelectReader(routingTable.Readers, database, databaseForRouting); break; case AccessMode.Write: - uri = _loadBalancingStrategy.SelectWriter(routingTable.Writers, database); + uri = _loadBalancingStrategy.SelectWriter(routingTable.Writers, database, databaseForRouting); break; default: @@ -259,7 +299,7 @@ private async Task AcquireConnectionAsync( bookmarks, forceAuth) .ConfigureAwait(false); - + if (conn != null) { return conn; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/RoutingTableManager.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/RoutingTableManager.cs index a5910906c..6dd413847 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/RoutingTableManager.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/RoutingTableManager.cs @@ -25,29 +25,32 @@ namespace Neo4j.Driver.Internal.Routing; internal class RoutingTableManager : IRoutingTableManager { private readonly IDiscovery _discovery; + private readonly DriverContext _driverContext; private readonly IInitialServerAddressProvider _initialServerAddressProvider; private readonly ILogger _logger; private readonly IClusterConnectionPoolManager _poolManager; private readonly ConcurrentDictionary _routingTableLocks = new(); - + private readonly TimeSpan _routingTablePurgeDelay; private readonly ConcurrentDictionary _routingTables = new(); public RoutingTableManager( IInitialServerAddressProvider initialServerAddressProvider, IClusterConnectionPoolManager poolManager, + DriverContext driverContext, ILogger logger) { _initialServerAddressProvider = initialServerAddressProvider; _discovery = new ClusterDiscovery(); _poolManager = poolManager; + _driverContext = driverContext; _logger = logger; // Default value. _routingTablePurgeDelay = TimeSpan.FromSeconds(30); } - + //Test Method public RoutingTableManager( IInitialServerAddressProvider initialServerAddressProvider, @@ -72,10 +75,11 @@ public RoutingTableManager( public async Task EnsureRoutingTableForModeAsync( AccessMode mode, string database, + bool isCachedHomeDb, SessionConfig sessionConfig, Bookmarks bookmarks) { - database = database ?? string.Empty; + database ??= string.Empty; var semaphore = GetLock(database); @@ -89,7 +93,11 @@ public async Task EnsureRoutingTableForModeAsync( return existingTable; } - var refreshedTable = await UpdateRoutingTableAsync(mode, database, sessionConfig, bookmarks) + var refreshedTable = await UpdateRoutingTableAsync( + mode, + isCachedHomeDb ? "" : database, + sessionConfig, + bookmarks) .ConfigureAwait(false); await UpdateAsync(refreshedTable).ConfigureAwait(false); @@ -111,7 +119,7 @@ public async Task GetServerInfoAsync(Uri uri, string database) throw new ServiceUnavailableException("Could not create connection"); } - var rt = await _discovery.DiscoverAsync(conn, null, null, null) + var rt = await _discovery.DiscoverAsync(conn, null, null, null, _driverContext.HomeDbCache) .ConfigureAwait(false); await conn.CloseAsync().ConfigureAwait(false); @@ -314,7 +322,12 @@ internal async Task UpdateRoutingTableAsync( try { var newRoutingTable = - await _discovery.DiscoverAsync(conn, database, sessionConfig, bookmarks) + await _discovery.DiscoverAsync( + conn, + database, + sessionConfig, + bookmarks, + _driverContext?.HomeDbCache) .ConfigureAwait(false); if (!newRoutingTable.IsStale(mode)) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Types/Node.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Types/Node.cs index 1d9b7f39a..6cbd8bf8c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Types/Node.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Types/Node.cs @@ -44,13 +44,13 @@ public Node(long id, string elementId, IReadOnlyList labels, IReadOnlyDi public string ElementId { get; } public IReadOnlyList Labels { get; } - /// + /// public T Get(string key) { return Properties[key].As(); } - /// + /// public bool TryGet(string key, out T value) { if (Properties.TryGetValue(key, out var obj)) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Types/Relationship.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Types/Relationship.cs index a92886508..a94c445a3 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Types/Relationship.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Types/Relationship.cs @@ -79,13 +79,13 @@ public Relationship( public IReadOnlyDictionary Properties { get; } public object this[string key] => Properties[key]; - /// + /// public T Get(string key) { return Properties[key].As(); } - /// + /// public bool TryGet(string key, out T value) { if (Properties.TryGetValue(key, out var obj)) diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Util/Neo4jUri.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Util/Neo4jUri.cs index b3288be83..c4357c218 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Util/Neo4jUri.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Util/Neo4jUri.cs @@ -21,7 +21,7 @@ namespace Neo4j.Driver.Internal.Util; internal static class Neo4jUri { public const int DefaultBoltPort = 7687; - + public static bool IsSimpleUriScheme(Uri uri) { var scheme = uri.Scheme.ToLower(); @@ -154,6 +154,7 @@ public static Uri BoltRoutingUri(string address) { builder.Port = DefaultBoltPort; } + return builder.Uri; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Util/PipeReaderMemoryPool.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Util/PipeReaderMemoryPool.cs index f10b116f0..e96ca2abd 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Util/PipeReaderMemoryPool.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Util/PipeReaderMemoryPool.cs @@ -18,14 +18,12 @@ namespace Neo4j.Driver.Internal.Util; -/// -/// Simple memory pool based on the .NET's Pool. -/// +/// Simple memory pool based on the .NET's Pool. internal sealed class PipeReaderMemoryPool : MemoryPool { private readonly int _defaultSize; private readonly ArrayPool _pool; - + public PipeReaderMemoryPool(int defaultBufferSize, int maxPooledBufferSize) { _defaultSize = defaultBufferSize; @@ -40,12 +38,15 @@ public override IMemoryOwner Rent(int minimumBufferSize = -1) { minimumBufferSize = _defaultSize; } - + if (minimumBufferSize < 0 || minimumBufferSize > MaxBufferSize) { - throw new ArgumentOutOfRangeException(nameof(minimumBufferSize), minimumBufferSize, "requested size is invalid"); + throw new ArgumentOutOfRangeException( + nameof(minimumBufferSize), + minimumBufferSize, + "requested size is invalid"); } - + return new PooledMemory(minimumBufferSize, _pool); } @@ -55,8 +56,8 @@ protected override void Dispose(bool disposing) private sealed class PooledMemory : IMemoryOwner { - private byte[] _array; private readonly ArrayPool _pool; + private byte[] _array; public PooledMemory(int size, ArrayPool pool) { @@ -81,7 +82,7 @@ public Memory Memory public void Dispose() { var array = _array; - + if (array == null) { return; diff --git a/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj b/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj index 3526c728f..4d5b8fa9c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj +++ b/Neo4j.Driver/Neo4j.Driver/Neo4j.Driver.csproj @@ -1,5 +1,5 @@  - + Neo4j.Driver Neo4j.Driver$(PackageIdSuffix) @@ -39,11 +39,11 @@ 2 - - + + - + diff --git a/Neo4j.Driver/Neo4j.Driver/Preview/GqlErrors/IGqlErrorPreview.cs b/Neo4j.Driver/Neo4j.Driver/Preview/GqlErrors/IGqlErrorPreview.cs index eca5c2fbc..4cbb7c569 100644 --- a/Neo4j.Driver/Neo4j.Driver/Preview/GqlErrors/IGqlErrorPreview.cs +++ b/Neo4j.Driver/Neo4j.Driver/Preview/GqlErrors/IGqlErrorPreview.cs @@ -17,34 +17,24 @@ namespace Neo4j.Driver.Preview.GqlErrors; -/// -/// Allows users to preview the GQL error functionality. This is a preview feature and may change in the future. -/// +/// Allows users to preview the GQL error functionality. This is a preview feature and may change in the future. public interface IGqlErrorPreview { - /// - /// Gets or sets the GQL status of the exception. - /// + /// Gets or sets the GQL status of the exception. public string GqlStatus { get; } - /// - /// Gets or sets the GQL status description of the exception. - /// + /// Gets or sets the GQL status description of the exception. public string GqlStatusDescription { get; } - /// - /// Gets or sets the GQL classification of the exception. - /// + /// Gets or sets the GQL classification of the exception. public string GqlClassification { get; } - /// - /// The raw classification as received from the server. - /// + /// The raw classification as received from the server. public string GqlRawClassification { get; } /// - /// GqlDiagnosticRecord returns further information about the status for diagnostic purposes. - /// GqlDiagnosticRecord is part of the GQL compliant errors preview feature. + /// GqlDiagnosticRecord returns further information about the status for diagnostic purposes. GqlDiagnosticRecord + /// is part of the GQL compliant errors preview feature. /// public Dictionary GqlDiagnosticRecord { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenAndExpiration.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenAndExpiration.cs index e5b36c61d..9bab5e5c1 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenAndExpiration.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenAndExpiration.cs @@ -18,14 +18,10 @@ namespace Neo4j.Driver; -/// -/// Represents an auth token and its expiration. -/// +/// Represents an auth token and its expiration. public record AuthTokenAndExpiration { - /// - /// Initializes a new instance of . - /// + /// Initializes a new instance of . /// The auth token. /// The date and time when the token expires. public AuthTokenAndExpiration(IAuthToken token, DateTime? expiry = default) @@ -34,15 +30,13 @@ public AuthTokenAndExpiration(IAuthToken token, DateTime? expiry = default) Expiry = expiry ?? DateTime.MaxValue; } - /// - /// Initializes a new instance of . - /// + /// Initializes a new instance of . /// The auth token. /// The number of milliseconds after which the token expires. public AuthTokenAndExpiration(IAuthToken token, int expiresInMs) { - this.Token = token; - this.Expiry = DateTimeProvider.StaticInstance.Now().AddMilliseconds(expiresInMs); + Token = token; + Expiry = DateTimeProvider.StaticInstance.Now().AddMilliseconds(expiresInMs); } /// The auth token. @@ -51,14 +45,12 @@ public AuthTokenAndExpiration(IAuthToken token, int expiresInMs) /// The date and time when the token expires. public DateTime Expiry { get; init; } - /// - /// Deconstructs the into its components. - /// + /// Deconstructs the into its components. /// The auth token. /// The date and time when the token expires. public void Deconstruct(out IAuthToken token, out DateTime? expiry) { - token = this.Token; - expiry = this.Expiry; + token = Token; + expiry = Expiry; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenManagers.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenManagers.cs index 9d98be700..b663ba5e3 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenManagers.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokenManagers.cs @@ -67,9 +67,9 @@ async ValueTask TokenProviderAsync() } /// - /// An implementation of that will call the provided async function whenever - /// a token is needed. It will cache the token and will only call the function when a new token is needed or - /// the existing cached token has expired. + /// An implementation of that will call the provided async function whenever a + /// token is needed. It will cache the token and will only call the function when a new token is needed or the existing + /// cached token has expired. /// /// A function that will be called when a new token is needed. /// The that will call the provided function when a new token is needed. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokens.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokens.cs index 0f285206d..085698107 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokens.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/AuthTokens.cs @@ -13,10 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Generic; using Neo4j.Driver.Internal.Auth; -using static Neo4j.Driver.Internal.Auth.AuthToken; namespace Neo4j.Driver; @@ -27,7 +25,7 @@ namespace Neo4j.Driver; /// /// /// -public class AuthTokens +public static class AuthTokens { /// /// Gets an authentication token that can be used to connect to Neo4j instances with auth disabled. This will only @@ -36,7 +34,7 @@ public class AuthTokens /// /// /// - public static IAuthToken None => new AuthToken(new Dictionary { { SchemeKey, "none" } }); + public static IAuthToken None => new AuthToken(new Dictionary { { AuthToken.SchemeKey, "none" } }); /// The basic authentication scheme, using a username and a password. /// This is the "principal", identifying who this token represents. @@ -47,7 +45,7 @@ public class AuthTokens /// public static IAuthToken Basic(string username, string password) { - return Basic(username, password, null); + return new BasicAuthToken(username, password); } /// The basic authentication scheme, using a username and a password. @@ -59,43 +57,22 @@ public static IAuthToken Basic(string username, string password) /// /// An authentication token that can be used to connect to Neo4j. /// - /// + /// /// public static IAuthToken Basic(string username, string password, string realm) { - var token = new Dictionary - { - { SchemeKey, "basic" }, - { PrincipalKey, username }, - { CredentialsKey, password } - }; - - if (realm != null) - { - token.Add(RealmKey, realm); - } - - return new AuthToken(token); + return new BasicAuthToken(username, password, realm); } /// The kerberos authentication scheme, using a base64 encoded ticket. /// A base64 encoded service ticket. /// an authentication token that can be used to connect to Neo4j. /// - /// + /// /// public static IAuthToken Kerberos(string base64EncodedTicket) { - var token = new Dictionary - { - { SchemeKey, "kerberos" }, - { PrincipalKey, string.Empty }, //This empty string is required for backwards compatibility. - { CredentialsKey, base64EncodedTicket } - }; - - return new AuthToken(token); + return new KerberosAuthToken(base64EncodedTicket); } /// @@ -103,8 +80,7 @@ public static IAuthToken Kerberos(string base64EncodedTicket) /// work if authentication is disabled on the Neo4j Instance we are connecting to. /// /// - /// + /// /// /// This is used to identify who this token represents. /// This is credentials authenticating the principal. @@ -113,7 +89,7 @@ public static IAuthToken Kerberos(string base64EncodedTicket) /// An authentication token that can be used to connect to Neo4j. public static IAuthToken Custom(string principal, string credentials, string realm, string scheme) { - return Custom(principal, credentials, realm, scheme, null); + return new CustomAuthToken(principal, credentials, realm, scheme); } /// @@ -121,8 +97,7 @@ public static IAuthToken Custom(string principal, string credentials, string rea /// work if authentication is disabled on the Neo4j Instance we are connecting to. /// /// - /// + /// /// /// This is used to identify who this token represents. /// This is credentials authenticating the principal. @@ -140,33 +115,7 @@ public static IAuthToken Custom( string scheme, Dictionary parameters) { - var token = new Dictionary(); - if (principal is not null) - { - token.Add(PrincipalKey, principal); - } - - if (!string.IsNullOrEmpty(scheme)) - { - token.Add(SchemeKey, scheme); - } - - if (!string.IsNullOrEmpty(credentials)) - { - token.Add(CredentialsKey, credentials); - } - - if (!string.IsNullOrEmpty(realm)) - { - token.Add(RealmKey, realm); - } - - if (parameters is not null) - { - token.Add(ParametersKey, parameters); - } - - return new AuthToken(token); + return new CustomAuthToken(principal, credentials, realm, scheme, parameters); } /// @@ -180,17 +129,6 @@ public static IAuthToken Custom( /// An authentication token that can be used to connect to Neo4j. public static IAuthToken Bearer(string token) { - if (string.IsNullOrEmpty(token)) - { - throw new ArgumentException("Bearer token cannot be null or an empty string"); - } - - var authtoken = new Dictionary - { - { SchemeKey, "bearer" }, - { CredentialsKey, token } - }; - - return new AuthToken(authtoken); + return new BearerAuthToken(token); } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/ClientCertificateProviders.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/ClientCertificateProviders.cs index 4b66488f3..cd628dbcc 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/ClientCertificateProviders.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/ClientCertificateProviders.cs @@ -18,14 +18,10 @@ namespace Neo4j.Driver; -/// -/// This class provides static methods to create basic client certificate providers. -/// +/// This class provides static methods to create basic client certificate providers. public static class ClientCertificateProviders { - /// - /// Creates a new static client certificate provider with the given certificate. - /// + /// Creates a new static client certificate provider with the given certificate. /// The certificate. /// The new static client certificate provider. public static IClientCertificateProvider Static(X509Certificate certificate) @@ -33,9 +29,7 @@ public static IClientCertificateProvider Static(X509Certificate certificate) return new StaticClientCertificateProvider(certificate); } - /// - /// Creates a new rotating client certificate provider with the given certificate as the initial certificate. - /// + /// Creates a new rotating client certificate provider with the given certificate as the initial certificate. /// The initial certificate. /// The new rotating client certificate provider. public static IRotatingClientCertificateProvider Rotating(X509Certificate certificate) diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/IAuthTokenManager.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/IAuthTokenManager.cs index fee550b41..33d83de14 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/IAuthTokenManager.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/IAuthTokenManager.cs @@ -19,8 +19,8 @@ namespace Neo4j.Driver; /// -/// Common interface for components that can provide auth tokens. For pre-baked implementations -/// of this interface, see . +/// Common interface for components that can provide auth tokens. For pre-baked implementations of this interface, +/// see . /// public interface IAuthTokenManager { @@ -33,16 +33,13 @@ public interface IAuthTokenManager ValueTask GetTokenAsync(CancellationToken cancellationToken = default); /// - /// Handles an error notification thrown by the server if a security error happened. - ///

- /// This will be called when driver throws a . + /// Handles an error notification thrown by the server if a security error happened.

This will be called when + /// driver throws a . ///

/// The token. /// The security exception thrown by the server. /// A cancellation token that can be used to cancel the asynchronous operation. - /// - /// A representing the asynchronous operation. - /// + /// A representing the asynchronous operation. ValueTask HandleSecurityExceptionAsync( IAuthToken token, SecurityException exception, diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/IClientCertificateProvider.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/IClientCertificateProvider.cs index f15da4b50..a22f7169b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/IClientCertificateProvider.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/IClientCertificateProvider.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -23,28 +23,28 @@ namespace Neo4j.Driver; /// /// The driver will call to get the latest certificate to use for new connections. /// -/// The certificate is only used as a second factor for authentication authenticating the client. -/// The DBMS user still needs to authenticate with an authentication token. +/// The certificate is only used as a second factor for authentication authenticating the client. The DBMS user still needs +/// to authenticate with an authentication token. /// -/// All implementations of this interface must be thread-safe and non-blocking for caller threads. -/// For instance, IO operations must not be done on the calling thread. +/// All implementations of this interface must be thread-safe and non-blocking for caller threads. For instance, IO +/// operations must not be done on the calling thread. /// -/// Note that the work done in the methods of this interface count towards the connectionAcquisition. -/// Should fetching the certificate be particularly slow, it might be necessary to increase the timeout. +/// Note that the work done in the methods of this interface count towards the connectionAcquisition. Should fetching the +/// certificate be particularly slow, it might be necessary to increase the timeout. ///
public interface IClientCertificateProvider { /// - /// Returns the certificate to use for new connections. Must be thread-safe. The driver will call this - /// method every time it makes a new connection. + /// Returns the certificate to use for new connections. Must be thread-safe. The driver will call this method + /// every time it makes a new connection. /// /// The certificate to use for new connections. /// - /// If a new certificate is returned, existing connections using the previous certificate will not be - /// affected. The new certificate will only be used for new connections. + /// If a new certificate is returned, existing connections using the previous certificate will not be affected. + /// The new certificate will only be used for new connections. /// - /// The work done in this method counts towards the connectionAcquisitionTimeout. Should fetching the - /// certificate be particularly slow, it might be necessary to increase the timeout. + /// The work done in this method counts towards the connectionAcquisitionTimeout. Should fetching the certificate be + /// particularly slow, it might be necessary to increase the timeout. /// /// This method must be thread safe. /// diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/IRotatingClientCertificateProvider.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/IRotatingClientCertificateProvider.cs index 385122bf4..1bee36d6d 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/IRotatingClientCertificateProvider.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/IRotatingClientCertificateProvider.cs @@ -18,16 +18,14 @@ namespace Neo4j.Driver; /// -/// This interface allows for the implementation of a client certificate provider that can be updated with -/// new certificates. +/// This interface allows for the implementation of a client certificate provider that can be updated with new +/// certificates. /// public interface IRotatingClientCertificateProvider : IClientCertificateProvider { - /// - /// Updates the certificate stored in the provider. + /// Updates the certificate stored in the provider. /// - /// To be called by user-code when a new client certificate is available. This method must be thread-safe. - /// + /// To be called by user-code when a new client certificate is available. This method must be thread-safe. /// The new certificate. void UpdateCertificate(X509Certificate certificate); } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Auth/ITlsNegotiator.cs b/Neo4j.Driver/Neo4j.Driver/Public/Auth/ITlsNegotiator.cs index e239264d2..19d2166fb 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Auth/ITlsNegotiator.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Auth/ITlsNegotiator.cs @@ -19,21 +19,15 @@ namespace Neo4j.Driver; -/// -/// Defines a method that negotiates a TLS connection. -/// +/// Defines a method that negotiates a TLS connection. public interface ITlsNegotiator { - /// - /// Return a secured stream for the given URI and stream. - /// + /// Return a secured stream for the given URI and stream. /// The URI being connected to. /// The stream to negotiate the TLS connection on. /// SslStream NegotiateTls(Uri uri, Stream stream); } -/// -/// A delegate that negotiates a TLS connection. -/// +/// A delegate that negotiates a TLS connection. public delegate SslStream NegotiateTlsDelegate(Uri uri, Stream stream); diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Config.cs b/Neo4j.Driver/Neo4j.Driver/Public/Config.cs index f07003ad5..72704e660 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Config.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Config.cs @@ -47,14 +47,6 @@ namespace Neo4j.Driver; /// public class Config { - static Config() - { - var version = Assembly.GetExecutingAssembly().GetName().Version; - DefaultUserAgent = $"neo4j-dotnet/{version.Major.ToString()}.{version.Minor.ToString()}"; - } - - internal static string DefaultUserAgent { get; } - /// This const defines the value of infinite in terms of configuration properties. public const int Infinite = -1; @@ -63,6 +55,14 @@ static Config() private int _maxIdleConnPoolSize = Infinite; + static Config() + { + var version = Assembly.GetExecutingAssembly().GetName().Version; + DefaultUserAgent = $"neo4j-dotnet/{version.Major.ToString()}.{version.Minor.ToString()}"; + } + + internal static string DefaultUserAgent { get; } + /// Create an instance of to build a . internal static ConfigBuilder Builder => new(new Config()); @@ -117,26 +117,26 @@ public int MaxIdleConnectionPoolSize /// The maximum waiting time to either acquire an idle connection from the pool when connection pool is full or /// create a new connection when pool is not full. /// - /// Note that if there is a client certificate provider set, the time taken to fetch the certificate will be - /// included in the connection acquisition timeout, so if fetching the certificate is particularly slow, it might - /// be necessary to increase the timeout. + /// Note that if there is a client certificate provider set, the time taken to fetch the certificate will be included in + /// the connection acquisition timeout, so if fetching the certificate is particularly slow, it might be necessary to + /// increase the timeout. ///
public TimeSpan ConnectionAcquisitionTimeout { get; internal set; } = TimeSpan.FromMinutes(1); - + /// /// Pooled connections that have been idle in the pool for longer than this timeout will be tested before they are - /// used again, to ensure they are still live. If this option is set too low, an additional network call will - /// be incurred when acquiring a connection, which causes a performance hit. + /// used again, to ensure they are still live. If this option is set too low, an additional network call will be incurred + /// when acquiring a connection, which causes a performance hit. /// - /// If this is set high, you may receive sessions that are backed by no longer live connections, which will lead - /// to exceptions in your application. Assuming the database is running, these exceptions will go away if you - /// retry acquiring sessions. + /// If this is set high, you may receive sessions that are backed by no longer live connections, which will lead to + /// exceptions in your application. Assuming the database is running, these exceptions will go away if you retry acquiring + /// sessions. /// /// Hence, this parameter tunes a balance between the likelihood of your application seeing connection problems, and /// performance. /// - /// You normally should not need to tune this parameter. No connection liveness check is done by default. - /// Value 0 means connections will always be tested for validity. Values less than 0 are not allowed. + /// You normally should not need to tune this parameter. No connection liveness check is done by default. Value 0 means + /// connections will always be tested for validity. Values less than 0 are not allowed. /// public TimeSpan? ConnectionLivenessThreshold { get; internal set; } @@ -223,36 +223,28 @@ public int MaxIdleConnectionPoolSize public INotificationsConfig NotificationsConfig { get; internal set; } /// - /// The configuration for whether the driver attempts to send telemetry data.
- /// The telemetry collected covers high level usage of the driver and does not include any queries or - /// parameters.
- /// Current collected metrics: + /// The configuration for whether the driver attempts to send telemetry data.
The telemetry collected covers high + /// level usage of the driver and does not include any queries or parameters.
Current collected metrics: /// /// Which method was used to start a transaction. /// - /// Telemetry metrics are sent via Bolt to the uri provided when creating the driver instance or servers that make up - /// the cluster members and Neo4j makes no attempt to collect these usage metrics from outside of AuraDB - /// (Neo4j's cloud offering).
- /// Users can configure Neo4j server's collection collection behavior of client drivers telemetry data and log the - /// telemetry data for diagnostics purposes.
- /// By default the driver allows the collection of this telemetry. + /// Telemetry metrics are sent via Bolt to the uri provided when creating the driver instance or servers that make up the + /// cluster members and Neo4j makes no attempt to collect these usage metrics from outside of AuraDB (Neo4j's cloud + /// offering).
Users can configure Neo4j server's collection collection behavior of client drivers telemetry data and + /// log the telemetry data for diagnostics purposes.
By default the driver allows the collection of this telemetry. ///
public bool TelemetryDisabled { get; set; } - /// - /// The configuration for the driver's underlying message reading from the network. - /// + /// The configuration for the driver's underlying message reading from the network. public MessageReaderConfig MessageReaderConfig { get; internal set; } /// - /// A certificate provider that will be used to provide the client certificate when - /// a new connection is established. + /// A certificate provider that will be used to provide the client certificate when a new connection is + /// established. /// public IClientCertificateProvider ClientCertificateProvider { get; internal set; } - /// - /// The TLS version to use when establishing a connection. - /// + /// The TLS version to use when establishing a connection. public SslProtocols TlsVersion { get; internal set; } = SslProtocols.Tls12; /// @@ -262,54 +254,61 @@ public int MaxIdleConnectionPoolSize public ITlsNegotiator TlsNegotiator { get; internal set; } } -/// -/// The configuration for the driver's underlying message reading from the network. -/// +/// The configuration for the driver's underlying message reading from the network. public sealed class MessageReaderConfig { /// - /// Constructs a new instance of .
- /// The configuration for the driver's underlying message reading from the network.
- /// Using this constructor overrides the and . + /// Constructs a new instance of .
The configuration for the driver's + /// underlying message reading from the network.
Using this constructor overrides the + /// and . ///
- /// The memory pool for creating buffers when reading messages. The PipeReader will borrow - /// memory from the pool of at least ReadBufferSize size. The message reader can request larger memory blocks to - /// host an entire message. User code can provide an implementation for monitoring; by default, the driver will - /// allocate a new array pool that does not take advantage of shared memory pools. - /// The minimum buffer size to use when renting memory from the pool. - ///
The default value is 32,768. - /// The maximum buffer size to use when renting memory from neo4j's default pool. - ///
The default is 131,072. + /// + /// The memory pool for creating buffers when reading messages. The PipeReader will borrow memory + /// from the pool of at least ReadBufferSize size. The message reader can request larger memory blocks to host an entire + /// message. User code can provide an implementation for monitoring; by default, the driver will allocate a new array pool + /// that does not take advantage of shared memory pools. + /// + /// + /// The minimum buffer size to use when renting memory from the pool.
The default value is + /// 32,768. + /// + /// + /// The maximum buffer size to use when renting memory from neo4j's default pool.
+ /// The default is 131,072. + /// /// /// /// /// - /// To optimize the memory usage of the driver pass .NET's shared memory pool() as - /// the , this should only be used when there is complete trust over the usage of - /// shared memory buffers in the application as other components may be using the same memory pool. + /// To optimize the memory usage of the driver pass .NET's shared memory pool() + /// as the , this should only be used when there is complete trust over the usage of shared + /// memory buffers in the application as other components may be using the same memory pool. /// /// /// The will define it's own maximum pooled buffer size, but must be able to provide - /// an memory object upto the limit 2146435071 bytes. The will not be observed - /// when the is passed.. - /// - /// - /// Note using a small value for could cause a degradation in performance. + /// an memory object upto the limit 2146435071 bytes. The will not be observed when + /// the is passed.. /// + /// Note using a small value for could cause a degradation in performance. /// - /// If is less than 1 or greater than 2146435071 + /// If is less than 1 or greater than + /// 2146435071 /// /// - /// If is less than or greater than 2146435071 + /// If is less than + /// or greater than 2146435071 /// public MessageReaderConfig(MemoryPool memoryPool = null, int minBufferSize = -1, int maxPooledBufferSize = -1) { const int maxArrayLength = 2146435071; if (minBufferSize is < -1 or 0 or > maxArrayLength) { - throw new ArgumentOutOfRangeException(nameof(minBufferSize), minBufferSize, + throw new ArgumentOutOfRangeException( + nameof(minBufferSize), + minBufferSize, "Minimum buffer size must be between 1 and 2146435071, leave as -1 to use default."); } + MinBufferSize = minBufferSize == -1 ? Constants.DefaultReadBufferSize : MinBufferSize; if (maxPooledBufferSize != -1 && (maxPooledBufferSize < MinBufferSize || maxPooledBufferSize > maxArrayLength)) { @@ -318,11 +317,14 @@ public MessageReaderConfig(MemoryPool memoryPool = null, int minBufferSize maxPooledBufferSize, $"Max pooled buffer size buffer size must be greater than minBufferSize({MinBufferSize}), leave as -1 to use default."); } - + DisablePipelinedMessageReader = false; - MemoryPool = memoryPool ?? new PipeReaderMemoryPool(MinBufferSize, - maxPooledBufferSize == -1 ? Constants.MaxReadBufferSize : maxPooledBufferSize); - StreamPipeReaderOptions = new(MemoryPool, MinBufferSize, leaveOpen: true); + MemoryPool = memoryPool ?? + new PipeReaderMemoryPool( + MinBufferSize, + maxPooledBufferSize == -1 ? Constants.MaxReadBufferSize : maxPooledBufferSize); + + StreamPipeReaderOptions = new StreamPipeReaderOptions(MemoryPool, MinBufferSize, leaveOpen: true); } internal MessageReaderConfig(Config config) @@ -330,27 +332,28 @@ internal MessageReaderConfig(Config config) DisablePipelinedMessageReader = false; MinBufferSize = config.DefaultReadBufferSize; MemoryPool = new PipeReaderMemoryPool(config.DefaultReadBufferSize, config.MaxReadBufferSize); - StreamPipeReaderOptions = new(MemoryPool, config.DefaultReadBufferSize, leaveOpen: true); + StreamPipeReaderOptions = new StreamPipeReaderOptions( + MemoryPool, + config.DefaultReadBufferSize, + leaveOpen: true); } /// - /// As of 5.15, the driver has migrated the underlying message reading mechanism utilizing ; - /// this optimizes the reading and memory usage of the driver, and setting this to true will revert the driver to - /// the legacy message reader. + /// As of 5.15, the driver has migrated the underlying message reading mechanism utilizing + /// ; this optimizes the reading and memory usage of the driver, and setting this to true will + /// revert the driver to the legacy message reader. /// internal bool DisablePipelinedMessageReader { get; } - + /// /// The memory pool for creating buffers when reading messages. The PipeReader will borrow memory from the pool of - /// at least size. The message reader can request larger memory blocks to host - /// an entire message. User code can provide an implementation for monitoring; by default, the driver will allocate - /// a new array pool that does not take advantage of shared memory pools. + /// at least size. The message reader can request larger memory blocks to host an entire + /// message. User code can provide an implementation for monitoring; by default, the driver will allocate a new array pool + /// that does not take advantage of shared memory pools. /// public MemoryPool MemoryPool { get; } - - /// - /// The minimum buffer size to use when renting memory from the pool. The default value is 65,539. - /// + + /// The minimum buffer size to use when renting memory from the pool. The default value is 65,539. public int MinBufferSize { get; } internal StreamPipeReaderOptions StreamPipeReaderOptions { get; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/ConfigBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Public/ConfigBuilder.cs index 0a84edaa2..1b1874d3a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/ConfigBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/ConfigBuilder.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using Neo4j.Driver.Internal.Auth; using Neo4j.Driver.Internal.Logging; @@ -105,9 +106,9 @@ public ConfigBuilder WithMaxConnectionPoolSize(int size) /// Sets the maximum connection acquisition timeout for waiting for a connection to become available in idle /// connection pool when is reached. /// - /// Note that if a client certificate is provided using , the - /// the connection acquisition timeout will be running while the client certificate is being fetched, so - /// if the client certificate fetching is slow, it might be necessary to increase the timeout. + /// Note that if a client certificate is provided using , the the connection + /// acquisition timeout will be running while the client certificate is being fetched, so if the client certificate + /// fetching is slow, it might be necessary to increase the timeout. ///
/// The connection acquisition timeout. /// A instance for further configuration options. @@ -450,19 +451,15 @@ public ConfigBuilder WithNotifications( } /// - /// Disables the driver sending any telemetry data.
- /// The telemetry collected covers high level usage of the driver and does not include any queries or - /// parameters.
- /// Current collected metrics: + /// Disables the driver sending any telemetry data.
The telemetry collected covers high level usage of the driver and + /// does not include any queries or parameters.
Current collected metrics: /// /// Which method was used to start a transaction. /// - /// Telemetry metrics are sent via Bolt to the uri provided when creating a driver instance or servers that make up - /// the cluster members and Neo4j makes no attempt to collect these usage metrics from outside of AuraDB - /// (Neo4j's cloud offering).
- /// Users can configure Neo4j server's collection behavior of client drivers telemetry data and log the - /// telemetry data for diagnostics purposes.
- /// By default the driver allows the collection of this telemetry. + /// Telemetry metrics are sent via Bolt to the uri provided when creating a driver instance or servers that make up the + /// cluster members and Neo4j makes no attempt to collect these usage metrics from outside of AuraDB (Neo4j's cloud + /// offering).
Users can configure Neo4j server's collection behavior of client drivers telemetry data and log the + /// telemetry data for diagnostics purposes.
By default the driver allows the collection of this telemetry. ///
/// A instance for further configuration options. public ConfigBuilder WithTelemetryDisabled() @@ -471,9 +468,7 @@ public ConfigBuilder WithTelemetryDisabled() return this; } - /// - /// Sets the config to use in the driver. - /// + /// Sets the config to use in the driver. /// A instance for further configuration options. public ConfigBuilder WithMessageReaderConfig(MessageReaderConfig config) { @@ -483,23 +478,21 @@ public ConfigBuilder WithMessageReaderConfig(MessageReaderConfig config) /// /// Sets the connection liveness timeout. Pooled connections that have been idle in the pool for longer than this - /// timeout will be tested before they are used again, to ensure they are still live. If this option is set too low, - /// an additional network call will be incurred when acquiring a connection, which causes a performance hit. + /// timeout will be tested before they are used again, to ensure they are still live. If this option is set too low, an + /// additional network call will be incurred when acquiring a connection, which causes a performance hit. /// - /// If this is set high, you may receive sessions that are backed by no longer live connections, which will lead - /// to exceptions in your application. Assuming the database is running, these exceptions will go away if you - /// retry acquiring sessions. + /// If this is set high, you may receive sessions that are backed by no longer live connections, which will lead to + /// exceptions in your application. Assuming the database is running, these exceptions will go away if you retry acquiring + /// sessions. /// /// Hence, this parameter tunes a balance between the likelihood of your application seeing connection problems, and /// performance. /// - /// You normally should not need to tune this parameter. No connection liveness check is done by default. - /// Value 0 means connections will always be tested for validity. Values less than 0 are not allowed. + /// You normally should not need to tune this parameter. No connection liveness check is done by default. Value 0 means + /// connections will always be tested for validity. Values less than 0 are not allowed. /// /// The liveness timeout. - /// - /// When is less than . - /// + /// When is less than . /// A instance for further configuration options. public ConfigBuilder WithConnectionLivenessCheckTimeout(TimeSpan timeout) { @@ -535,19 +528,17 @@ public ConfigBuilder WithClientCertificateProvider(IClientCertificateProvider cl /// A instance for further configuration options. public ConfigBuilder WithTls13() { - _config.TlsVersion = System.Security.Authentication.SslProtocols.Tls13; + _config.TlsVersion = SslProtocols.Tls13; return this; } #endif - /// - /// Sets a custom to use when establishing a TLS connection. - /// + /// Sets a custom to use when establishing a TLS connection. /// The to use. /// A instance for further configuration options. /// - /// This option may compromise your application’s security if used improperly. - /// Its usage is strongly discouraged and comes without any guarantees. + /// This option may compromise your application’s security if used improperly. Its usage is strongly discouraged + /// and comes without any guarantees. /// public ConfigBuilder WithTlsNegotiator(ITlsNegotiator tlsNegotiator) { @@ -555,14 +546,12 @@ public ConfigBuilder WithTlsNegotiator(ITlsNegotiator tlsNegotiator) return this; } - /// - /// Sets the custom to call when establishing a TLS connection. - /// + /// Sets the custom to call when establishing a TLS connection. /// The to use. /// A instance for further configuration options. /// - /// This option may compromise your application’s security if used improperly. - /// Its usage is strongly discouraged and comes without any guarantees. + /// This option may compromise your application’s security if used improperly. Its usage is strongly discouraged + /// and comes without any guarantees. /// public ConfigBuilder WithTlsNegotiator(NegotiateTlsDelegate negotiateTls) { @@ -570,14 +559,12 @@ public ConfigBuilder WithTlsNegotiator(NegotiateTlsDelegate negotiateTls) return this; } - /// - /// Sets the type of custom to use when establishing a TLS connection. - /// + /// Sets the type of custom to use when establishing a TLS connection. /// The to use. /// A instance for further configuration options. /// - /// This option may compromise your application’s security if used improperly. - /// Its usage is strongly discouraged and comes without any guarantees. + /// This option may compromise your application’s security if used improperly. Its usage is strongly discouraged + /// and comes without any guarantees. /// public ConfigBuilder WithTlsNegotiator() where T : ITlsNegotiator, new() { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthenticationException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthenticationException.cs index f984c4461..03bbcb27c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthenticationException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthenticationException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -23,16 +21,14 @@ namespace Neo4j.Driver; /// -/// Failed to authenticate the client to the server due to bad credentials -/// To recover from this error, close the current driver and restart with the correct credentials +/// Failed to authenticate the client to the server due to bad credentials To recover from this error, close the +/// current driver and restart with the correct credentials /// [DataContract] [ErrorCode("Neo.ClientError.Security.Unauthorized")] public class AuthenticationException : SecurityException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public AuthenticationException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthorizationException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthorizationException.cs index 3c70a7043..59b63943b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthorizationException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/AuthorizationException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -21,18 +19,11 @@ namespace Neo4j.Driver; -/// -/// The authorization information maintained on the server has expired. The client should reconnect. -/// +/// The authorization information maintained on the server has expired. The client should reconnect. [ErrorCode("Neo.ClientError.Security.AuthorizationExpired")] public class AuthorizationException : SecurityException { - /// - public override bool IsRetriable => true; - - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public AuthorizationException(string message) : base(message) { @@ -42,4 +33,7 @@ internal AuthorizationException(FailureMessage failureMessage, Exception innerEx : base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => true; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ClientException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ClientException.cs index 671f99f7b..78976d209 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ClientException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ClientException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -23,40 +21,32 @@ namespace Neo4j.Driver; /// -/// A indicates that the client has carried out an operation incorrectly. -/// The error code provided can be used to determine further detail for the problem. +/// A indicates that the client has carried out an operation incorrectly. The error +/// code provided can be used to determine further detail for the problem. /// [DataContract] [ErrorCode("Neo.ClientError.*")] public class ClientException : Neo4jException { - /// - /// Create a new . - /// + /// Create a new . public ClientException() { } - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ClientException(string message) : base(message) { } - /// - /// Create a new with an error code and an error message. - /// + /// Create a new with an error code and an error message. /// The error code. /// The error message. public ClientException(string code, string message) : base(code, message) { } - /// - /// Create a new with an error message and an exception. - /// + /// Create a new with an error message and an exception. /// The error message. /// The inner exception. public ClientException(string message, Exception innerException) @@ -64,9 +54,7 @@ public ClientException(string message, Exception innerException) { } - /// - /// Create a new with an error code, an error message and an exception. - /// + /// Create a new with an error code, an error message and an exception. /// The error code. /// The error message. /// The inner exception. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ConnectionReadTimeoutException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ConnectionReadTimeoutException.cs index 919874055..fb912b575 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ConnectionReadTimeoutException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ConnectionReadTimeoutException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,25 +20,19 @@ namespace Neo4j.Driver; /// -/// A indicates that the driver timed out trying to read from the network socket. +/// A indicates that the driver timed out trying to read from the +/// network socket. /// [DataContract] public class ConnectionReadTimeoutException : Neo4jException { - /// - public override bool IsRetriable => true; - - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ConnectionReadTimeoutException(string message) : base(message) { } - /// - /// Create a new with an error message and an exception. - /// + /// Create a new with an error message and an exception. /// The error message. /// The inner exception. public ConnectionReadTimeoutException(string message, Exception innerException) : base(message, innerException) @@ -51,4 +43,7 @@ internal ConnectionReadTimeoutException(FailureMessage failureMessage, Exception : base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => true; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/DatabaseException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/DatabaseException.cs index 872b63643..443775400 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/DatabaseException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/DatabaseException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -23,40 +21,32 @@ namespace Neo4j.Driver; /// -/// A indicates that there is a problem within the underlying database. -/// The error code provided can be used to determine further detail for the problem. +/// A indicates that there is a problem within the underlying database. The error +/// code provided can be used to determine further detail for the problem. /// [DataContract] [ErrorCode("*")] // mop up any errors that haven't already been handled public class DatabaseException : Neo4jException { - /// - /// Create a new . - /// + /// Create a new . public DatabaseException() { } - /// - /// Create a new with an error error message. - /// + /// Create a new with an error error message. /// The error message. public DatabaseException(string message) : base(string.Empty, message) { } - /// - /// Create a new with an error code and an error message. - /// + /// Create a new with an error code and an error message. /// The error code. /// The error message. public DatabaseException(string code, string message) : base(code, message) { } - /// - /// Create a new with an error code, an error message and an exception. - /// + /// Create a new with an error code, an error message and an exception. /// The error code. /// The error message. /// The inner exception which caused this error. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/FatalDiscoveryException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/FatalDiscoveryException.cs index e4430a2be..2811ea836 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/FatalDiscoveryException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/FatalDiscoveryException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -30,9 +28,7 @@ namespace Neo4j.Driver; [ErrorCode("Neo.ClientError.Database.DatabaseNotFound")] public class FatalDiscoveryException : ClientException { - /// - /// Create a new with an error code and an error message. - /// + /// Create a new with an error code and an error message. /// The error message. public FatalDiscoveryException(string message) : base(message) diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ForbiddenException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ForbiddenException.cs index 8a9b1d926..ac9edb3a7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ForbiddenException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ForbiddenException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -22,16 +20,12 @@ namespace Neo4j.Driver; -/// -/// This operation is forbidden. -/// +/// This operation is forbidden. [DataContract] [ErrorCode("Neo.ClientError.Security.Forbidden")] public class ForbiddenException : SecurityException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ForbiddenException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkException.cs index dd5c6afb3..d7d2643fe 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -21,15 +19,11 @@ namespace Neo4j.Driver; -/// -/// The provided bookmark is invalid. To recover from this a new session needs to be created. -/// +/// The provided bookmark is invalid. To recover from this a new session needs to be created. [ErrorCode("Neo.ClientError.Transaction.InvalidBookmark")] public class InvalidBookmarkException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public InvalidBookmarkException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkMixtureException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkMixtureException.cs index 5214e9b7d..b7f43bf65 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkMixtureException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/InvalidBookmarkMixtureException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -21,15 +19,11 @@ namespace Neo4j.Driver; -/// -/// The provided bookmark is invalid. To recover from this a new session needs to be created. -/// +/// The provided bookmark is invalid. To recover from this a new session needs to be created. [ErrorCode("Neo.ClientError.Transaction.InvalidBookmarkMixture")] public class InvalidBookmarkMixtureException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public InvalidBookmarkMixtureException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/Neo4jException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/Neo4jException.cs index cb0b09b98..314aebd02 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/Neo4jException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/Neo4jException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -23,52 +21,24 @@ namespace Neo4j.Driver; -/// -/// The base class for all Neo4j exceptions. -/// +/// The base class for all Neo4j exceptions. [DataContract] public class Neo4jException : Exception, IGqlErrorPreview { - private readonly string _gqlStatus; - private readonly string _gqlStatusDescription; private readonly string _gqlClassification; - private readonly string _gqlRawClassification; private readonly Dictionary _gqlDiagnosticRecord; + private readonly string _gqlRawClassification; + private readonly string _gqlStatus; + private readonly string _gqlStatusDescription; - /// - string IGqlErrorPreview.GqlStatus => _gqlStatus; - - /// - string IGqlErrorPreview.GqlStatusDescription => _gqlStatusDescription; - - /// - string IGqlErrorPreview.GqlClassification => _gqlClassification; - - /// - string IGqlErrorPreview.GqlRawClassification => _gqlRawClassification; - - /// - Dictionary IGqlErrorPreview.GqlDiagnosticRecord => _gqlDiagnosticRecord; - - /// - /// Gets whether the exception retriable or not. - /// - public virtual bool IsRetriable => false; - - /// - /// Gets or sets the code of a Neo4j exception. - /// - public string Code { get; set; } - - /// - /// Create a new - /// + /// Create a new public Neo4jException() { } /// - /// Initializes a new instance of the class using the specified . + /// Initializes a new instance of the class using the specified + /// . /// /// The failure message containing error details. /// The inner exception. @@ -83,28 +53,13 @@ internal Neo4jException(FailureMessage failureMessage, Exception innerException _gqlDiagnosticRecord = failureMessage.GqlDiagnosticRecord; } - internal static Neo4jException Create(FailureMessage failureMessage) - { - Exception innerException = null; - if (failureMessage.GqlCause is not null) - { - innerException = Create(failureMessage.GqlCause); - } - - return new Neo4jException(failureMessage, innerException); - } - - /// - /// Create a new with an error message - /// + /// Create a new with an error message /// The error message. public Neo4jException(string message) : this(null, message) { } - /// - /// Create a new with an error code and an error message - /// + /// Create a new with an error code and an error message /// The error code. /// The error message public Neo4jException(string code, string message) @@ -113,9 +68,7 @@ public Neo4jException(string code, string message) Code = code; } - /// - /// Create a new with an error message and an exception. - /// + /// Create a new with an error message and an exception. /// The error message. /// The inner exception public Neo4jException(string message, Exception innerException) @@ -123,9 +76,7 @@ public Neo4jException(string message, Exception innerException) { } - /// - /// Create a new with an error code, an error message and an exception. - /// + /// Create a new with an error code, an error message and an exception. /// The error code. /// The error message. /// The inner exception. @@ -134,4 +85,36 @@ public Neo4jException(string code, string message, Exception innerException) { Code = code; } + + /// Gets whether the exception retriable or not. + public virtual bool IsRetriable => false; + + /// Gets or sets the code of a Neo4j exception. + public string Code { get; set; } + + /// + string IGqlErrorPreview.GqlStatus => _gqlStatus; + + /// + string IGqlErrorPreview.GqlStatusDescription => _gqlStatusDescription; + + /// + string IGqlErrorPreview.GqlClassification => _gqlClassification; + + /// + string IGqlErrorPreview.GqlRawClassification => _gqlRawClassification; + + /// + Dictionary IGqlErrorPreview.GqlDiagnosticRecord => _gqlDiagnosticRecord; + + internal static Neo4jException Create(FailureMessage failureMessage) + { + Exception innerException = null; + if (failureMessage.GqlCause is not null) + { + innerException = Create(failureMessage.GqlCause); + } + + return new Neo4jException(failureMessage, innerException); + } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ProtocolException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ProtocolException.cs index 7db439e70..8091914a3 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ProtocolException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ProtocolException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -23,16 +21,14 @@ namespace Neo4j.Driver; /// -/// There was a bolt protocol violation of the contract between the driver and the server. -/// When seen this error, contact driver developers. +/// There was a bolt protocol violation of the contract between the driver and the server. When seen this error, +/// contact driver developers. /// [DataContract] [ErrorCode("Neo.ClientError.Request.Invalid*")] public class ProtocolException : Neo4jException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ProtocolException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ResultConsumedException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ResultConsumedException.cs index f11960b44..f99129d4a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ResultConsumedException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ResultConsumedException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,16 +20,14 @@ namespace Neo4j.Driver; /// -/// The result has already been consumed either by explicit consume call, -/// or by termination of session or transaction where the result was obtained. -/// Once a result is consumed, the records in the result is not accessible anymore. +/// The result has already been consumed either by explicit consume call, or by termination of session or +/// transaction where the result was obtained. Once a result is consumed, the records in the result is not accessible +/// anymore. /// [DataContract] public class ResultConsumedException : ClientException { - /// - /// Create a new with an error message - /// + /// Create a new with an error message /// The error message public ResultConsumedException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SecurityException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SecurityException.cs index 2dfbc52ab..4b9fcd585 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SecurityException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SecurityException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -22,8 +20,8 @@ namespace Neo4j.Driver; /// -/// Failed to connect the driver to the server due to security errors -/// When this type of error happens, recreation of the driver might be required. +/// Failed to connect the driver to the server due to security errors When this type of error happens, recreation +/// of the driver might be required. /// [DataContract] public class SecurityException : Neo4jException @@ -31,31 +29,26 @@ public class SecurityException : Neo4jException internal bool Notified = false; internal bool Retriable = false; - internal SecurityException(FailureMessage failureMessage, Exception innerException) : base(failureMessage, + internal SecurityException(FailureMessage failureMessage, Exception innerException) : base( + failureMessage, innerException) { } - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public SecurityException(string message) : base(message) { } - /// - /// Create a new with an error code and an error message. - /// + /// Create a new with an error code and an error message. /// The error code. /// The error message. public SecurityException(string code, string message) : base(code, message) { } - /// - /// Create a new with an error message and an exception. - /// + /// Create a new with an error message and an exception. /// The error message. /// The inner exception. public SecurityException(string message, Exception innerException) : base(message, innerException) @@ -63,8 +56,8 @@ public SecurityException(string message, Exception innerException) : base(messag } /// - /// Whether or not the exception is retriable. If the exception is retriable, the driver will try to - /// re-run the operation that caused the exception. + /// Whether or not the exception is retriable. If the exception is retriable, the driver will try to re-run the + /// operation that caused the exception. /// public override bool IsRetriable => Retriable; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ServiceUnavailableException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ServiceUnavailableException.cs index e928dfdee..08cf236b1 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ServiceUnavailableException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ServiceUnavailableException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -21,26 +19,17 @@ namespace Neo4j.Driver; -/// -/// A indicates that the driver cannot communicate with the cluster. -/// +/// A indicates that the driver cannot communicate with the cluster. [DataContract] public class ServiceUnavailableException : Neo4jException { - /// - public override bool IsRetriable => true; - - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ServiceUnavailableException(string message) : base(message) { } - /// - /// Create a new with an error message and an exception. - /// + /// Create a new with an error message and an exception. /// The error message. /// The inner exception. public ServiceUnavailableException(string message, Exception innerException) : base(message, innerException) @@ -51,4 +40,7 @@ internal ServiceUnavailableException(FailureMessage failureMessage, Exception in : base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => true; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SessionExpiredException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SessionExpiredException.cs index c1cd03650..60982fa31 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SessionExpiredException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/SessionExpiredException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,28 +20,20 @@ namespace Neo4j.Driver; /// -/// A indicates that the session can no longer satisfy the criteria under which it was acquired, -/// e.g. a server no longer accepts write requests. -/// -/// A new session needs to be acquired from the driver and all actions taken on the expired session must be replayed. +/// A indicates that the session can no longer satisfy the criteria under +/// which it was acquired, e.g. a server no longer accepts write requests. A new session needs to be acquired from the +/// driver and all actions taken on the expired session must be replayed. /// [DataContract] public class SessionExpiredException : Neo4jException { - /// - public override bool IsRetriable => true; - - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public SessionExpiredException(string message) : base(message) { } - /// - /// Create a new with an error message and an exception. - /// + /// Create a new with an error message and an exception. /// The error message. /// The inner exception. public SessionExpiredException(string message, Exception innerException) : base(message, innerException) @@ -54,4 +44,7 @@ internal SessionExpiredException(FailureMessage failureMessage, Exception innerE : base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => true; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/StatementArgumentException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/StatementArgumentException.cs index 9ae1e5601..60a6ce9a7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/StatementArgumentException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/StatementArgumentException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -22,16 +20,12 @@ namespace Neo4j.Driver; -/// -/// A generic argument error has occurred. To recover from this a new session needs to be created. -/// +/// A generic argument error has occurred. To recover from this a new session needs to be created. [DataContract] [ErrorCode("Neo.ClientError.Statement.ArgumentError")] public class StatementArgumentException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public StatementArgumentException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TokenExpiredException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TokenExpiredException.cs index bb6e8915a..1b98f5d6b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TokenExpiredException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TokenExpiredException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -22,19 +20,16 @@ namespace Neo4j.Driver; /// -/// The provided token has expired. The current driver instance is considered invalid. It should not -/// be used anymore. The client must create a new driver instance with a valid token. +/// The provided token has expired. The current driver instance is considered invalid. It should not be used +/// anymore. The client must create a new driver instance with a valid token. /// [ErrorCode("Neo.ClientError.Security.TokenExpired")] public class TokenExpiredException : SecurityException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public TokenExpiredException(string message) : base(message) { - } internal TokenExpiredException(FailureMessage failureMessage, Exception innerException) diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionClosedException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionClosedException.cs index 16649ed70..b779af9bd 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionClosedException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionClosedException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,15 +20,13 @@ namespace Neo4j.Driver; /// -/// The exception that is thrown when calling or -/// on an that has already been closed. +/// The exception that is thrown when calling or +/// on an that has already been closed. /// [DataContract] public class TransactionClosedException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message public TransactionClosedException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionNestingException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionNestingException.cs index 684444b8a..69c2e2b1c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionNestingException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionNestingException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,18 +20,15 @@ namespace Neo4j.Driver; /// -/// An attempt to BeginTransaction has been made before the sessions existing transaction -/// has been consumed or rolled back. e.g. An attempt to nest transactions has occurred. -/// A session can only have a single transaction at a time. +/// An attempt to BeginTransaction has been made before the sessions existing transaction has been consumed or +/// rolled back. e.g. An attempt to nest transactions has occurred. A session can only have a single transaction at a time. /// [DataContract] public class TransactionNestingException : ClientException -{ - /// - /// Create a new with an error message - /// +{ + /// Create a new with an error message /// The error message - public TransactionNestingException(string message) : base(message) + public TransactionNestingException(string message) : base(message) { } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionTerminatedException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionTerminatedException.cs index 7c70703e2..6532168b5 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionTerminatedException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransactionTerminatedException.cs @@ -20,16 +20,12 @@ namespace Neo4j.Driver; /// -/// The exception that is thrown when trying to further interact with a terminated transaction. -/// Transactions are terminated when they incur errors.
-/// If created by the driver the will be null. +/// The exception that is thrown when trying to further interact with a terminated transaction. Transactions are +/// terminated when they incur errors.
If created by the driver the will be null. ///
[DataContract] public sealed class TransactionTerminatedException : ClientException { - /// - public override bool IsRetriable => (InnerException as Neo4jException)?.IsRetriable ?? false; - internal TransactionTerminatedException(Exception inner) : base((inner as Neo4jException)?.Code, inner.Message, inner) { @@ -39,4 +35,7 @@ internal TransactionTerminatedException(FailureMessage failureMessage, Exception base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => (InnerException as Neo4jException)?.IsRetriable ?? false; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransientException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransientException.cs index d02f7b21c..57eef1584 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransientException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TransientException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -23,36 +21,27 @@ namespace Neo4j.Driver; /// -/// A signals a failed operation that may be able to succeed -/// if this operation is retried without any intervention by application-level functionality. -/// The error code provided can be used to determine further details for the problem. +/// A signals a failed operation that may be able to succeed if this operation is +/// retried without any intervention by application-level functionality. The error code provided can be used to determine +/// further details for the problem. /// [DataContract] [ErrorCode("Neo.TransientError.*")] public class TransientException : Neo4jException { - /// - public override bool IsRetriable => true; - - /// - /// Create a new . - /// + /// Create a new . public TransientException() { } - /// - /// Create a new with an error code and an error message. - /// + /// Create a new with an error code and an error message. /// The error code. /// The error message. public TransientException(string code, string message) : base(code, message) { } - /// - /// Create a new with an error code, an error message and an exception. - /// + /// Create a new with an error code, an error message and an exception. /// The error code. /// The error message. /// The inner exception which caused this error. @@ -65,4 +54,7 @@ internal TransientException(FailureMessage failureMessage, Exception innerExcept : base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => true; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TypeException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TypeException.cs index fcdd2c411..6f3ab4e3f 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TypeException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/TypeException.cs @@ -1,14 +1,12 @@ // 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. +// Neo4j Sweden AB [https://neo4j.com] +// +// 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. @@ -22,16 +20,12 @@ namespace Neo4j.Driver; -/// -/// An error occurred related to data typing. -/// +/// An error occurred related to data typing. [DataContract] [ErrorCode("Neo.ClientError.Statement.TypeError")] public class TypeException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public TypeException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnknownSecurityException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnknownSecurityException.cs index 039460351..62730b891 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnknownSecurityException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnknownSecurityException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,16 +20,12 @@ namespace Neo4j.Driver; -/// -/// An unknown security error occurred. -/// +/// An unknown security error occurred. [DataContract] [ErrorCode("Neo.ClientError.Security.*")] public class UnknownSecurityException : SecurityException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public UnknownSecurityException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnsupportedFeatureException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnsupportedFeatureException.cs index b9f4c716c..1fedd9faf 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnsupportedFeatureException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/UnsupportedFeatureException.cs @@ -26,12 +26,7 @@ namespace Neo4j.Driver; [DataContract] public class UnsupportedFeatureException : ClientException { - /// - public override bool IsRetriable => false; - - /// - /// Creates a new with an error message. - /// + /// Creates a new with an error message. /// The error message internal UnsupportedFeatureException(string message) : base(message) { @@ -41,4 +36,7 @@ internal UnsupportedFeatureException(FailureMessage failureMessage, Exception in : base(failureMessage, innerException) { } + + /// + public override bool IsRetriable => false; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueOverflowException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueOverflowException.cs index 0d9cffff9..7cfcc0783 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueOverflowException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueOverflowException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,15 +20,13 @@ namespace Neo4j.Driver; /// -/// A value retrieved from the database cannot be represented with the type to be converted, and will -/// cause working with a modified data. +/// A value retrieved from the database cannot be represented with the type to be converted, and will cause +/// working with a modified data. /// [DataContract] public class ValueOverflowException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ValueOverflowException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueTruncationException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueTruncationException.cs index 612f34b9d..9d97c9452 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueTruncationException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Exceptions/ValueTruncationException.cs @@ -1,10 +1,8 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] +// Neo4j Sweden AB [https://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. +// 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 @@ -22,15 +20,13 @@ namespace Neo4j.Driver; /// -/// A value retrieved from the database needs to be truncated for this conversion to work, and will -/// cause working with a modified data. +/// A value retrieved from the database needs to be truncated for this conversion to work, and will cause working +/// with a modified data. /// [DataContract] public class ValueTruncationException : ClientException { - /// - /// Create a new with an error message. - /// + /// Create a new with an error message. /// The error message. public ValueTruncationException(string message) : base(message) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/ExecuteQuery/QueryConfig.cs b/Neo4j.Driver/Neo4j.Driver/Public/ExecuteQuery/QueryConfig.cs index 3f95d3e26..3bd99c446 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/ExecuteQuery/QueryConfig.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/ExecuteQuery/QueryConfig.cs @@ -76,9 +76,7 @@ public QueryConfig( /// Enables or disables the use of an for causal chaining. public bool EnableBookmarkManager { get; } - /// - /// Auth token to use for the session executing the query. - /// + /// Auth token to use for the session executing the query. public IAuthToken AuthToken { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Extensions/ResultCursorExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Extensions/ResultCursorExtensions.cs index 509661793..de078412a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Extensions/ResultCursorExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Extensions/ResultCursorExtensions.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; + // ReSharper disable NotDisposedResource namespace Neo4j.Driver; @@ -92,12 +93,16 @@ public static async Task SingleAsync(this IResultCursor result, FuncPull all records in the result stream into memory and return in a list. /// The result stream. - /// Optional, the driver has no knowledge of the expected result size so use the - /// default constructor: . a capacity can be provided to cost of - /// extending this list. + /// + /// Optional, the driver has no knowledge of the expected result size so use the default + /// constructor: . a capacity can be provided to cost of extending this list. + /// /// A cancellation token that can be used to cancel the asynchronous operation. /// A list with all records in the result stream. - public static async Task> ToListAsync(this IResultCursor result, int initialCapacity = 0, CancellationToken cancellationToken = default) + public static async Task> ToListAsync( + this IResultCursor result, + int initialCapacity = 0, + CancellationToken cancellationToken = default) { if (result == null) { @@ -129,11 +134,14 @@ public static Task> ToListAsync(this IResultCursor result, Cancell /// The return type of the list /// The result stream. /// The operation to carry out on each record. - /// Optional, the driver has no knowledge of the expected result size so use the - /// default constructor: . a capacity can be provided to cost of - /// extending this list. + /// + /// Optional, the driver has no knowledge of the expected result size so use the default + /// constructor: . a capacity can be provided to cost of extending this list. + /// /// A list of collected operation result. - public static async Task> ToListAsync(this IResultCursor result, Func operation, + public static async Task> ToListAsync( + this IResultCursor result, + Func operation, int initialCapacity = 0) { if (result == null) diff --git a/Neo4j.Driver/Neo4j.Driver/Public/GraphDatabase.cs b/Neo4j.Driver/Neo4j.Driver/Public/GraphDatabase.cs index f567903c0..e0af035e5 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/GraphDatabase.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/GraphDatabase.cs @@ -242,7 +242,7 @@ public static IDriver Driver(Uri uri, IAuthTokenManager authTokenManager, Action var builder = Config.Builder; action?.Invoke(builder); var config = builder.Build(); - + var context = new DriverContext(uri, authTokenManager, config); var connectionFactory = new PooledConnectionFactory(context); diff --git a/Neo4j.Driver/Neo4j.Driver/Public/IRecord.cs b/Neo4j.Driver/Neo4j.Driver/Public/IRecord.cs index f0ea88148..6fb9cbbc2 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/IRecord.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/IRecord.cs @@ -25,25 +25,26 @@ public interface IRecord : IReadOnlyDictionary /// The value specified with the given index. object this[int index] { get; } + /// Gets the key and value pairs in a . + new IReadOnlyDictionary Values { get; } + + /// Gets the keys in a . + new IReadOnlyList Keys { get; } + /// Gets the value specified by the given key and converts it to the given type. /// The key. /// The type to convert to. /// The converted value. T Get(string key); - /// - /// Tries to get the value specified by the given key and converts it to the given type. - /// + /// Tries to get the value specified by the given key and converts it to the given type. /// The key. /// The value, if the key was found. /// The type to convert to. /// true if the value is found; false otherwise. - bool TryGet (string key, out T value); + bool TryGet(string key, out T value); - /// - /// Gets the value specified by the given key and converts it to the given type. The key is not case - /// sensitive. - /// + /// Gets the value specified by the given key and converts it to the given type. The key is not case sensitive. /// The key. /// The type to convert to. /// The converted value. @@ -58,10 +59,4 @@ public interface IRecord : IReadOnlyDictionary /// The type to convert to. /// true if the value is found; false otherwise. bool TryGetCaseInsensitive(string key, out T value); - - /// Gets the key and value pairs in a . - new IReadOnlyDictionary Values { get; } - - /// Gets the keys in a . - new IReadOnlyList Keys { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/IResultCursor.cs b/Neo4j.Driver/Neo4j.Driver/Public/IResultCursor.cs index e0847ffe4..4428bab38 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/IResultCursor.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/IResultCursor.cs @@ -24,7 +24,10 @@ namespace Neo4j.Driver; /// Provides access to the result as an asynchronous stream of s. The records in the result /// is lazily retrieved and can be visited only once in a sequential order. /// -/// Calling will enumerate the entire stream. +/// +/// Calling will enumerate the +/// entire stream. +/// public interface IResultCursor : IAsyncEnumerable { /// Returns the current record that has already been read via . @@ -51,8 +54,8 @@ public interface IResultCursor : IAsyncEnumerable /// Asynchronously gets the after streaming the whole records to the client. If the /// records in the result are not fully consumed, then calling this method will discard all remaining records to yield the /// summary. If you want to obtain the summary without discarding the records, consider buffering the unconsumed result - /// using . If all records in the records stream are already - /// consumed, then this method will return the summary directly. + /// using . If all records in the records + /// stream are already consumed, then this method will return the summary directly. /// /// A task returning the result summary of the running query. Task ConsumeAsync(); diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/AsyncEnumerableExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/AsyncEnumerableExtensions.cs index 78af97421..28a339a6a 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/AsyncEnumerableExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/AsyncEnumerableExtensions.cs @@ -20,16 +20,13 @@ namespace Neo4j.Driver.Mapping; -/// -/// Contains extension methods for . -/// +/// Contains extension methods for . public static class AsyncEnumerableExtensions { /// - /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object. - /// If no custom mapper is defined for type , the default - /// mapper will be used. + /// Materializes the into a list of objects of type , by + /// mapping each record in the enumerable to an object. If no custom mapper is defined for type , + /// the default mapper will be used. /// /// /// The asynchronous source of records. @@ -50,10 +47,10 @@ public static async ValueTask> ToListAsync( } /// - /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object of the - /// same type as . This object could be anononymously typed. - /// If no custom mapper is defined for type , the default mapper will be used. + /// Materializes the into a list of objects of type , by + /// mapping each record in the enumerable to an object of the same type as . This object could + /// be anononymously typed. If no custom mapper is defined for type , the default mapper will be + /// used. /// /// /// The asynchronous source of records. @@ -76,8 +73,8 @@ public static async ValueTask> ToListFromBlueprintAsync( } /// - /// Converts the to an of objects of type - /// , by mapping each record in the enumerable to an object. If no custom mapper is defined + /// Converts the to an of objects of + /// type , by mapping each record in the enumerable to an object. If no custom mapper is defined /// for type , the default mapper will be used. /// /// The asynchronous source of records. @@ -95,10 +92,10 @@ public static async IAsyncEnumerable AsObjectsAsync( } /// - /// Converts the to an of objects of type - /// , by mapping each record in the enumerable to an object of the - /// same type as . This object could be anonymously typed. - /// If no custom mapper is defined for type , the default mapper will be used. + /// Converts the to an of objects of + /// type , by mapping each record in the enumerable to an object of the same type as + /// . This object could be anonymously typed. If no custom mapper is defined for type + /// , the default mapper will be used. /// /// The asynchronous source of records. /// An object of type to use as a blueprint for mapping. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/BuiltMapper.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/BuiltMapper.cs index c0a564246..3b42f24f8 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/BuiltMapper.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/BuiltMapper.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -25,9 +23,9 @@ namespace Neo4j.Driver.Mapping; internal class BuiltMapper : IRecordMapper { private readonly IMappableValueProvider _mappableValueProvider = new MappableValueProvider(); + private readonly List> _propertyMappings = new(); private Func _wholeObjectMapping; - private readonly List> _propertyMappings = new(); public HashSet MappedSetters { get; } = []; public T Map(IRecord record) @@ -156,7 +154,7 @@ void MapFromRecord(T obj, IRecord record) { propertySetter.Invoke(obj, [mappableValue]); } - else if(!optional) + else if (!optional) { // throw because we couldn't find a value for the property var propertyName = propertySetter.Name.Substring("set_".Length); diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateAsyncEnumerableExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateAsyncEnumerableExtensions.cs index 33017b58f..1ca53e624 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateAsyncEnumerableExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateAsyncEnumerableExtensions.cs @@ -22,8 +22,8 @@ namespace Neo4j.Driver.Mapping; /// -/// Provides extension methods for materializing into lists of objects -/// by mapping each record in the enumerable to an object using provided delegates. +/// Provides extension methods for materializing into lists of objects by +/// mapping each record in the enumerable to an object using provided delegates. /// public static class DelegateAsyncEnumerableExtensions { @@ -44,8 +44,8 @@ private static async ValueTask> ToListAsyncImpl( /// /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -63,8 +63,8 @@ public static ValueTask> ToListAsync( /// /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -83,8 +83,8 @@ public static ValueTask> ToListAsync( /// /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -104,8 +104,8 @@ public static ValueTask> ToListAsync /// /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -126,8 +126,8 @@ public static ValueTask> ToListAsync /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -149,8 +149,8 @@ public static ValueTask> ToListAsync /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -173,8 +173,8 @@ public static ValueTask> ToListAsync /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -198,8 +198,8 @@ public static ValueTask> ToListAsync /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -224,8 +224,8 @@ public static ValueTask> ToListAsync /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. @@ -251,8 +251,8 @@ public static ValueTask> ToListAsync /// Materializes the into a list of objects of type - /// , by mapping each record in the enumerable to an object using - /// the provided delegate. + /// , by mapping each record in the enumerable to an object using the provided + /// delegate. /// /// The asynchronous source of records. /// The delegate to map each record to an object. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateExecutableQueryMappingExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateExecutableQueryMappingExtensions.cs index 5a05bb85e..c38f0935e 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateExecutableQueryMappingExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateExecutableQueryMappingExtensions.cs @@ -28,8 +28,8 @@ namespace Neo4j.Driver.Mapping; public static class DelegateExecutableQueryMappingExtensions { /// - /// Add this method to an method chain to map the results to objects - /// using a delegate as part of the query execution. + /// Add this method to an method chain to map the results to objects using + /// a delegate as part of the query execution. /// /// /// The task that will return the records. @@ -45,9 +45,7 @@ public static async Task> AsObjectsAsync( return records.Result.Select(r => r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -62,9 +60,7 @@ public static async Task> AsObjectsAsync return records.Result.Select(r => r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -80,9 +76,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -99,9 +93,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -119,9 +111,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -140,9 +130,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -162,9 +150,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -185,9 +171,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. @@ -209,9 +193,7 @@ public static async Task> AsObjectsAsync r.AsObject(map)).ToList(); } - /// - /// Maps the results to objects using a delegate as part of the query execution. - /// + /// Maps the results to objects using a delegate as part of the query execution. /// The task that will return the records. /// The delegate to use to map the records to objects. /// The type to map to. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateMappingRecordExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateMappingRecordExtensions.cs index 1aba3af15..e9f990e0c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateMappingRecordExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DelegateMappingRecordExtensions.cs @@ -17,22 +17,19 @@ namespace Neo4j.Driver.Mapping; -/// -/// Contains extensions for mapping records to objects using delegates. -/// +/// Contains extensions for mapping records to objects using delegates. public static class DelegateMappingRecordExtensions { - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with key a, the following code will map the record to an object with property - /// a of type int: + /// Given a record with key a, the following code will map the record to an object with property a of type + /// int: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject((int a) => new { a }); @@ -46,14 +43,13 @@ public static TResult AsObject(this IRecord record, Func(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// /// Given a record with keys a and b, the following code will map the record to an object with properties /// a of type int and b of type string: @@ -73,17 +69,16 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b and c, the following code will map the record to an object with properties - /// a of type int, b of type string and c of type bool: + /// Given a record with keys a, b and c, the following code will map the record to an object with + /// properties a of type int, b of type string and c of type bool: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject((int a, string b, bool c) => new { a, b, c }); @@ -99,17 +94,16 @@ public static TResult AsObject(this IRecord record, Func(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c and d, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool and d of type double: + /// Given a record with keys a, b, c and d, the following code will map the record to an object + /// with properties a of type int, b of type string, c of type bool and d of type double: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject((int a, string b, bool c, double d) => new { a, b, c, d }); @@ -128,17 +122,17 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c, d and e, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool, d of type double and e of type long: + /// Given a record with keys a, b, c, d and e, the following code will map the record to + /// an object with properties a of type int, b of type string, c of type bool, d of type double + /// and e of type long: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject((int a, string b, bool c, double d, long e) => new { a, b, c, d, e }); @@ -158,17 +152,17 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c, d, e and f, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool, d of type double, e of type long and f of type float: + /// Given a record with keys a, b, c, d, e and f, the following code will map the + /// record to an object with properties a of type int, b of type string, c of type bool, d of + /// type double, e of type long and f of type float: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject((int a, string b, bool c, double d, long e, float f) => new { a, b, c, d, e, f }); @@ -189,17 +183,17 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c, d, e, f and g, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool, d of type double, e of type long, f of type float and g of type decimal: + /// Given a record with keys a, b, c, d, e, f and g, the following code + /// will map the record to an object with properties a of type int, b of type string, c of type bool, + /// d of type double, e of type long, f of type float and g of type decimal: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject( @@ -223,17 +217,18 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c, d, e, f, g and h, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool, d of type double, e of type long, f of type float, g of type decimal and h of type byte: + /// Given a record with keys a, b, c, d, e, f, g and h, the + /// following code will map the record to an object with properties a of type int, b of type string, c + /// of type bool, d of type double, e of type long, f of type float, g of type decimal and + /// h of type byte: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject( @@ -258,17 +253,18 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c, d, e, f, g, h and i, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool, d of type double, e of type long, f of type float, g of type decimal, h of type byte and i of type short: + /// Given a record with keys a, b, c, d, e, f, g, h and i, + /// the following code will map the record to an object with properties a of type int, b of type string, + /// c of type bool, d of type double, e of type long, f of type float, g of type + /// decimal, h of type byte and i of type short: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject( @@ -294,17 +290,18 @@ public static TResult AsObject( return DelegateMapper.MapWithMethodInfo(record, map.Method, map.Target); } - /// - /// Converts the record to an object using the given delegate. - /// + /// Converts the record to an object using the given delegate. /// The record to convert. - /// A delegate that defines the mapping from the record to the result object. The names - /// of the parameters accepted by the delegate will be used to lookup values in the record, those values - /// will be converted to the types of the parameters, and then the delegate will be invoked to create the result - /// object. + /// + /// A delegate that defines the mapping from the record to the result object. The names of the parameters + /// accepted by the delegate will be used to lookup values in the record, those values will be converted to the types of + /// the parameters, and then the delegate will be invoked to create the result object. + /// /// - /// Given a record with keys a, b, c, d, e, f, g, h, i and j, the following code will map the record to an object with properties - /// a of type int, b of type string, c of type bool, d of type double, e of type long, f of type float, g of type decimal, h of type byte, i of type short and j of type ushort: + /// Given a record with keys a, b, c, d, e, f, g, h, i and + /// j, the following code will map the record to an object with properties a of type int, b of type + /// string, c of type bool, d of type double, e of type long, f of type float, g of type + /// decimal, h of type byte, i of type short and j of type ushort: /// /// var record = ...; // obtain a record somehow /// var result = record.AsObject( diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DictAsRecord.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DictAsRecord.cs index d84706d56..8e6ed6ca9 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DictAsRecord.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/DictAsRecord.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -24,7 +22,6 @@ namespace Neo4j.Driver.Mapping; internal sealed class DictAsRecord : IRecord { - private readonly IReadOnlyDictionary _caseSensitiveDict; private readonly IReadOnlyDictionary _caseInsensitiveDict; public DictAsRecord(object dict, IRecord record) @@ -37,7 +34,7 @@ public DictAsRecord(object dict, IRecord record) }; // fill the case-sensitive dictionary - _caseSensitiveDict = readOnlyDictionary.ToDictionary( + Values = readOnlyDictionary.ToDictionary( kvp => kvp.Key, kvp => kvp.Value); @@ -51,19 +48,21 @@ public DictAsRecord(object dict, IRecord record) public IRecord Record { get; } - public object this[int index] => _caseSensitiveDict.TryGetValue(_caseInsensitiveDict.Keys.ElementAt(index), out var obj) ? obj : null; - public object this[string key] => _caseSensitiveDict.TryGetValue(key, out var obj) ? obj : null; + public object this[int index] => + Values.TryGetValue(_caseInsensitiveDict.Keys.ElementAt(index), out var obj) ? obj : null; - /// + public object this[string key] => Values.TryGetValue(key, out var obj) ? obj : null; + + /// public T Get(string key) { - return _caseSensitiveDict[key].As(); + return Values[key].As(); } - /// + /// public bool TryGet(string key, out T value) { - if (_caseSensitiveDict.TryGetValue(key, out var obj)) + if (Values.TryGetValue(key, out var obj)) { value = obj.As(); return true; @@ -73,13 +72,13 @@ public bool TryGet(string key, out T value) return false; } - /// + /// public T GetCaseInsensitive(string key) { return _caseInsensitiveDict[key].As(); } - /// + /// public bool TryGetCaseInsensitive(string key, out T value) { if (_caseInsensitiveDict.TryGetValue(key, out var obj)) @@ -92,39 +91,45 @@ public bool TryGetCaseInsensitive(string key, out T value) return false; } - public bool TryGetValueByCaseInsensitiveKey(string key, out object value) - { - return _caseInsensitiveDict.TryGetValue(key, out value); - } + public IReadOnlyDictionary Values { get; } - public IReadOnlyDictionary Values => _caseSensitiveDict; - public IReadOnlyList Keys => _caseSensitiveDict.Keys.ToList(); + public IReadOnlyList Keys => Values.Keys.ToList(); - /// + /// IEnumerator> IEnumerable>.GetEnumerator() { - return _caseSensitiveDict.GetEnumerator(); + return Values.GetEnumerator(); } - /// + /// IEnumerator IEnumerable.GetEnumerator() { - return _caseSensitiveDict.GetEnumerator(); + return Values.GetEnumerator(); + } + + /// + bool IReadOnlyDictionary.ContainsKey(string key) + { + return Values.ContainsKey(key); } - /// - bool IReadOnlyDictionary.ContainsKey(string key) => _caseSensitiveDict.ContainsKey(key); + /// + bool IReadOnlyDictionary.TryGetValue(string key, out object value) + { + return Values.TryGetValue(key, out value); + } - /// - bool IReadOnlyDictionary.TryGetValue(string key, out object value) => - _caseSensitiveDict.TryGetValue(key, out value); + /// + public int Count => Values.Count; - /// - int IReadOnlyCollection>.Count => _caseSensitiveDict.Count; + /// + IEnumerable IReadOnlyDictionary.Keys => Values.Keys; - /// - IEnumerable IReadOnlyDictionary.Keys => _caseSensitiveDict.Keys; + /// + IEnumerable IReadOnlyDictionary.Values => Values.Values; - /// - IEnumerable IReadOnlyDictionary.Values => _caseSensitiveDict.Values; + public bool TryGetValueByCaseInsensitiveKey(string key, out object value) + { + return _caseInsensitiveDict.TryGetValue(key, out value); + } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityExtensions.cs index dda0338d8..284a74c97 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityExtensions.cs @@ -15,14 +15,10 @@ namespace Neo4j.Driver.Mapping; -/// -/// Contains extensions for entities such as nodes and relationships. -/// +/// Contains extensions for entities such as nodes and relationships. public static class EntityExtensions { - /// - /// Converts the entity to a record. - /// + /// Converts the entity to a record. /// The entity to convert. /// The record. public static IRecord AsRecord(this IEntity entity) diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityMappingInfo.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityMappingInfo.cs index e2d90e644..747f1d38f 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityMappingInfo.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/EntityMappingInfo.cs @@ -1,12 +1,12 @@ // Copyright (c) "Neo4j" // Neo4j Sweden AB [https://neo4j.com] -// +// // 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. @@ -48,7 +48,8 @@ private static EntityMappingInfo GetEntityMappingInfoAffectedByAttributes( { // check for MappingSourceAttribute var sourceAttribute = - provider.GetCustomAttributes(typeof(MappingSourceAttribute), false).FirstOrDefault() as MappingSourceAttribute; + provider.GetCustomAttributes(typeof(MappingSourceAttribute), false).FirstOrDefault() as + MappingSourceAttribute; if (sourceAttribute is not null) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/ExecutableQueryMappingExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/ExecutableQueryMappingExtensions.cs index 739394c6f..6260bfb0b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/ExecutableQueryMappingExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/ExecutableQueryMappingExtensions.cs @@ -21,14 +21,14 @@ namespace Neo4j.Driver.Mapping; /// -/// Contains extensions for using the global mapping system with the driver's -/// methods. +/// Contains extensions for using the global mapping system with the driver's +/// methods. /// public static class ExecutableQueryMappingExtensions { /// - /// Add this method to an method chain to map the results to objects - /// as part of the query execution. + /// Add this method to an method chain to map the results to objects as + /// part of the query execution. /// /// /// The task that will return the records. @@ -42,8 +42,8 @@ public static async Task> AsObjectsAsync( } /// - /// Add this method to an method chain to map the results to objects - /// as part of the query execution. + /// Add this method to an method chain to map the results to objects as + /// part of the query execution. /// /// /// The task that will return the records. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingBuilder.cs index a34d2c60e..e6fe53c5b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingBuilder.cs @@ -18,32 +18,33 @@ namespace Neo4j.Driver.Mapping; -/// -/// Defines a builder for mapping objects from s. -/// +/// Defines a builder for mapping objects from s. /// The type of object to be mapped. public interface IMappingBuilder { /// - /// Applies the default mapping for the object. Later calls to mapping configuration methods will override - /// the default mapping. + /// Applies the default mapping for the object. Later calls to mapping configuration methods will override the + /// default mapping. /// /// This instance for method chaining. IMappingBuilder UseDefaultMapping(); - /// - /// Defines a mapping from a field in the record to a property on the object. - /// + /// Defines a mapping from a field in the record to a property on the object. /// The property to map to. /// The key of the field in the record. - /// A value indicating the type of value to be mapped from the specified field. + /// A value indicating the type of value to be mapped from the specified field. + /// + /// An optional converter function to convert the value from the field value to the type of the + /// property. /// - /// An optional converter function to convert the value from the field value - /// to the type of the property. - /// A value indicating whether the mapping is optional. If true, the mapping will not - /// throw an exception if the field is not present in the record. - /// The type of the property being mapped. This type will be inferred from the - /// parameter. + /// + /// A value indicating whether the mapping is optional. If true, the mapping will not throw an + /// exception if the field is not present in the record. + /// + /// + /// The type of the property being mapped. This type will be inferred from the + /// parameter. + /// /// This instance for method chaining. IMappingBuilder Map( Expression> destination, @@ -52,24 +53,23 @@ IMappingBuilder Map( Func converter = null, bool optional = false); - /// - /// Defines a mapping directly from the record to a property on the object. - /// + /// Defines a mapping directly from the record to a property on the object. /// The property to map to. - /// A function that accepts an and returns the value to be - /// stored in the property. - /// The type of the property being mapped. This type will be inferred from the - /// parameter. + /// + /// A function that accepts an and returns the value to be stored in the + /// property. + /// + /// + /// The type of the property being mapped. This type will be inferred from the + /// parameter. + /// /// This instance for method chaining. IMappingBuilder Map( Expression> destination, Func valueGetter); - /// - /// Defines a mapping from a record directly to an entire object. - /// - /// A function that accepts an and returns the mapped - /// object. + /// Defines a mapping from a record directly to an entire object. + /// A function that accepts an and returns the mapped object. /// This instance for method chaining. IMappingBuilder MapWholeObject(Func mappingFunction); } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingProvider.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingProvider.cs index 0a33e0a6f..9fd64be8b 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingProvider.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IMappingProvider.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -17,14 +15,10 @@ namespace Neo4j.Driver.Mapping; -/// -/// Interface to be implemented by a class that provides mappers to the mapping system. -/// +/// Interface to be implemented by a class that provides mappers to the mapping system. public interface IMappingProvider { - /// - /// This method is called on mapping providers to allow them to register their mappers with the mapping system. - /// + /// This method is called on mapping providers to allow them to register their mappers with the mapping system. /// The registry in which mappers should be registered. void CreateMappers(IMappingRegistry registry); } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IRecordMapper.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IRecordMapper.cs index fe942b0ad..9fdae76c8 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IRecordMapper.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/IRecordMapper.cs @@ -15,15 +15,11 @@ namespace Neo4j.Driver.Mapping; -/// -/// Interface to be implemented by a class that maps records to objects of type . -/// +/// Interface to be implemented by a class that maps records to objects of type . /// The type of object to which records will be mapped. public interface IRecordMapper { - /// - /// Maps the given record to an object of type . - /// + /// Maps the given record to an object of type . /// The record to map. /// The mapped object. T Map(IRecord record); diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingBuilder.cs index c2b83acf6..1a6885f68 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingBuilder.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -21,20 +19,11 @@ namespace Neo4j.Driver.Mapping; -/// -/// This class wraps the and exposes a fluent API for building the mapper. -/// +/// This class wraps the and exposes a fluent API for building the mapper. internal class MappingBuilder : IMappingBuilder { private readonly BuiltMapper _builtMapper = new(); - internal void Map( - MethodInfo propertySetter, - EntityMappingInfo entityMappingInfo) - { - _builtMapper.AddMappingBySetter(propertySetter, entityMappingInfo); - } - public IMappingBuilder Map( Expression> destination, string path, @@ -65,16 +54,23 @@ public IMappingBuilder MapWholeObject(Func mappingFunction) return this; } - internal IMappingBuilder UseConstructor(ConstructorInfo constructor) + /// + public IMappingBuilder UseDefaultMapping() { - _builtMapper.AddConstructorMapping(constructor); + _builtMapper.AddWholeObjectMapping(r => DefaultMapper.Get(_builtMapper.MappedSetters).Map(r)); return this; } - /// - public IMappingBuilder UseDefaultMapping() + internal void Map( + MethodInfo propertySetter, + EntityMappingInfo entityMappingInfo) { - _builtMapper.AddWholeObjectMapping(r => DefaultMapper.Get(_builtMapper.MappedSetters).Map(r)); + _builtMapper.AddMappingBySetter(propertySetter, entityMappingInfo); + } + + internal IMappingBuilder UseConstructor(ConstructorInfo constructor) + { + _builtMapper.AddConstructorMapping(constructor); return this; } @@ -84,8 +80,8 @@ internal IRecordMapper Build() } /// - /// Given an expression that represents a property (e.g. o => o.Name), - /// return the for the setter for that property. + /// Given an expression that represents a property (e.g. o => o.Name), return the + /// for the setter for that property. /// private static MethodInfo GetPropertySetter(Expression> destination) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingConstructorAttribute.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingConstructorAttribute.cs index 8d0657798..c56541340 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingConstructorAttribute.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingConstructorAttribute.cs @@ -17,11 +17,8 @@ namespace Neo4j.Driver.Mapping; -/// -/// Indicates that the constructor should be used when mapping a record to an object. -/// +/// Indicates that the constructor should be used when mapping a record to an object. [AttributeUsage(AttributeTargets.Constructor)] public class MappingConstructorAttribute : Attribute { - } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingFailedException.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingFailedException.cs index 6620d6a21..65c4d3f43 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingFailedException.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingFailedException.cs @@ -17,9 +17,7 @@ namespace Neo4j.Driver.Mapping; -/// -/// The exception that is thrown when the mapping of a record to a target type failed. -/// +/// The exception that is thrown when the mapping of a record to a target type failed. public class MappingFailedException : Neo4jException { /// diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingIgnoredAttribute.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingIgnoredAttribute.cs index ccf40e4be..7ae32c7df 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingIgnoredAttribute.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingIgnoredAttribute.cs @@ -18,11 +18,10 @@ namespace Neo4j.Driver.Mapping; /// -/// Instructs the default object mapper not to attempt to map any value to this property. -/// This attribute does not affect custom-defined mappers. +/// Instructs the default object mapper not to attempt to map any value to this property. This attribute does not +/// affect custom-defined mappers. /// [AttributeUsage(AttributeTargets.Property)] public class MappingIgnoredAttribute : Attribute { - } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingOptionalAttribute.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingOptionalAttribute.cs index e7fab78e6..e93bf2a75 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingOptionalAttribute.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingOptionalAttribute.cs @@ -18,9 +18,9 @@ namespace Neo4j.Driver.Mapping; /// -/// If a property or is decorated with this attribute, it will be considered optional. The -/// mapper will not throw an exception if it cannot find the named value in the record. This -/// attribute will have no effect when using custom-defined mappers. +/// If a property or is decorated with this attribute, it will be considered optional. The mapper will not throw +/// an exception if it cannot find the named value in the record. This attribute will have no effect when using +/// custom-defined mappers. /// [AttributeUsage(AttributeTargets.Property)] public class MappingOptionalAttribute : Attribute @@ -28,17 +28,14 @@ public class MappingOptionalAttribute : Attribute } /// -/// If a property is decorated with this attribute, it will be considered optional. The -/// mapper will not throw an exception if it cannot find the named value in the record; instead, -/// it will use the default value provided. This attribute will have no effect when using -/// custom-defined mappers. +/// If a property is decorated with this attribute, it will be considered optional. The mapper will not throw an +/// exception if it cannot find the named value in the record; instead, it will use the default value provided. This +/// attribute will have no effect when using custom-defined mappers. /// /// The default value to use if the property is not present in the record. [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)] public class MappingDefaultValueAttribute(object defaultValue) : MappingOptionalAttribute { - /// - /// The default value to use if the property is not present in the record. - /// + /// The default value to use if the property is not present in the record. public object DefaultValue => defaultValue; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceAttribute.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceAttribute.cs index 6be4529ff..313a9c3bb 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceAttribute.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceAttribute.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -19,48 +17,42 @@ namespace Neo4j.Driver.Mapping; -/// -/// Represents a mapping from an entity itself rather than any of its properties. -/// +/// Represents a mapping from an entity itself rather than any of its properties. public enum EntityMappingSource { - /// - /// The value of the specified property will be used as the value. - /// + /// The value of the specified property will be used as the value. Property, /// - /// If the value of the specified property is a relationship, then the relationship type will be used as the value. - /// Otherwise, the property will be ignored. + /// If the value of the specified property is a relationship, then the relationship type will be used as the + /// value. Otherwise, the property will be ignored. /// RelationshipType, /// - /// If the value of the specified property is a node, then the labels will be used as the value. If the destination - /// property is a string, then the labels will be joined with a comma. If the destination property is a list, then - /// the labels will be added to the list. Otherwise, the property will be ignored. + /// If the value of the specified property is a node, then the labels will be used as the value. If the + /// destination property is a string, then the labels will be joined with a comma. If the destination property is a list, + /// then the labels will be added to the list. Otherwise, the property will be ignored. /// NodeLabel } /// /// Instructs the default mapper to use a different field than the property name when mapping a value to the -/// marked property. This attribute does not affect custom-defined mappers. A path may consist of the name of the -/// field to be mapped, or a dot-separated path to a nested field. +/// marked property. This attribute does not affect custom-defined mappers. A path may consist of the name of the field to +/// be mapped, or a dot-separated path to a nested field. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)] public class MappingSourceAttribute : Attribute { - internal EntityMappingInfo EntityMappingInfo { get; private set; } - /// /// Instructs the default mapper to use a different field than the property name when mapping a value to the /// marked property. /// /// /// Identifier for the value in the field in the record. If the path is a dot-separated path, then the - /// first part of the path is the key for the entity (or dictionary) field in the record, and the - /// last part is the key within that entity or dictionary. + /// first part of the path is the key for the entity (or dictionary) field in the record, and the last part is the key + /// within that entity or dictionary. /// public MappingSourceAttribute(string path) { @@ -73,12 +65,14 @@ public MappingSourceAttribute(string path) /// /// /// Identifier for the value in the field in the record. If the path is a dot-separated path, then the - /// first part of the path is the key for the entity (or dictionary) field in the record, and the - /// last part is the key within that entity or dictionary. + /// first part of the path is the key for the entity (or dictionary) field in the record, and the last part is the key + /// within that entity or dictionary. /// /// The source of the value to be mapped. public MappingSourceAttribute(string key, EntityMappingSource entityMappingSource) { EntityMappingInfo = new EntityMappingInfo(key, entityMappingSource); } + + internal EntityMappingInfo EntityMappingInfo { get; private set; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceDelegateBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceDelegateBuilder.cs index 45f43bcb9..633037133 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceDelegateBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/MappingSourceDelegateBuilder.cs @@ -24,7 +24,7 @@ internal interface IMappingSourceDelegateBuilder internal class MappingSourceDelegateBuilder : IMappingSourceDelegateBuilder { - private IRecordPathFinder _pathFinder = new RecordPathFinder(); + private readonly IRecordPathFinder _pathFinder = new RecordPathFinder(); public MappingValueDelegate GetMappingDelegate(EntityMappingInfo entityMappingInfo) { diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordExtensions.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordExtensions.cs index 619fd5f53..198ea878e 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordExtensions.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordExtensions.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -19,14 +17,10 @@ namespace Neo4j.Driver.Mapping; -/// -/// Contains extensions for accessing values simply from records and entities. -/// +/// Contains extensions for accessing values simply from records and entities. public static class RecordExtensions { - /// - /// Converts the record to an object of the given type according to the global mapping configuration. - /// + /// Converts the record to an object of the given type according to the global mapping configuration. /// /// The record to convert. /// The type to map to. @@ -36,9 +30,7 @@ public static T AsObject(this IRecord record) return RecordObjectMapping.Map(record); } - /// - /// Converts the record to an object of the given type according to the global mapping configuration. - /// + /// Converts the record to an object of the given type according to the global mapping configuration. /// /// The record to convert. /// The type to map to. @@ -49,12 +41,14 @@ public static object AsObject(this IRecord record, Type objectType) } /// - /// Converts the record to an object of the same type as the given blueprint according to the global - /// mapping configuration. + /// Converts the record to an object of the same type as the given blueprint according to the global mapping + /// configuration. /// /// The record to convert. - /// An object to be used as a blueprint for the mapping. This could be an object of an - /// anonymous type. + /// + /// An object to be used as a blueprint for the mapping. This could be an object of an anonymous + /// type. + /// /// The type that will be mapped to. /// The mapped object. public static T AsObjectFromBlueprint(this IRecord record, T blueprint) diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordObjectMapping.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordObjectMapping.cs index 958bd7880..e84903cd1 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordObjectMapping.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordObjectMapping.cs @@ -1,14 +1,12 @@ // Copyright (c) "Neo4j" -// Neo4j Sweden AB [http://neo4j.com] -// -// This file is part of Neo4j. -// +// Neo4j Sweden AB [https://neo4j.com] +// // 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. @@ -21,16 +19,14 @@ namespace Neo4j.Driver.Mapping; -/// -/// Contains methods for registering a mapping with the global mapping configuration. -/// +/// Contains methods for registering a mapping with the global mapping configuration. public interface IMappingRegistry { - /// - /// Registers a mapping for the given type. - /// - /// This method will be called, passing a parameter that contains - /// a fluent API for defining the mapping. + /// Registers a mapping for the given type. + /// + /// This method will be called, passing a parameter that contains a fluent API for defining + /// the mapping. + /// /// The type to be mapped. /// This instance for method chaining. IMappingRegistry RegisterMapping(Action> mappingBuilder); @@ -42,20 +38,49 @@ internal interface IRecordObjectMapping TResult MapFromBlueprint(IRecord record, TResult blueprint); } -/// -/// Controls global record mapping configuration. -/// +/// Controls global record mapping configuration. public class RecordObjectMapping : IMappingRegistry, IRecordObjectMapping { - internal static RecordObjectMapping Instance { get; private set; } = new(); + private readonly Dictionary _mapMethods = new(); private readonly Dictionary _mappers = new(); - private readonly Dictionary _mapMethods = new(); private RecordObjectMapping() { } + internal static RecordObjectMapping Instance { get; private set; } = new(); + + IMappingRegistry IMappingRegistry.RegisterMapping(Action> mappingBuilder) + { + var builder = new MappingBuilder(); + mappingBuilder(builder); + var mapper = builder.Build(); + Register(mapper); + return this; + } + + object IRecordObjectMapping.Map(IRecord record, Type type) + { + var mapMethod = Instance.GetMapMethodForType(type); + var mapperForType = GetMapperForType(type); + + try + { + return mapMethod.Invoke(mapperForType, [record]); + } + catch (Exception ex) + { + var inner = ex is TargetInvocationException tie ? tie.InnerException : ex; + throw new MappingFailedException($"Failed to map record to type {type.Name}.", inner); + } + } + + T IRecordObjectMapping.MapFromBlueprint(IRecord record, T blueprint) + { + return (T)Map(record, typeof(T)); + } + internal static void Reset() { // discard the current instance and create a new one, which will have no mappers registered @@ -63,13 +88,9 @@ internal static void Reset() DefaultMapper.Reset(); } - /// - /// Registers a single record mapper. This will replace any existing mapper for the same type. - /// - /// The mapper. This must implement for the type - /// to be mapped. - /// The provided does not implement - /// IRecordMapper{T}. + /// Registers a single record mapper. This will replace any existing mapper for the same type. + /// The mapper. This must implement for the type to be mapped. + /// The provided does not implement IRecordMapper{T}. public static void Register(IRecordMapper mapper) { Instance._mappers[typeof(T)] = mapper; @@ -88,9 +109,7 @@ private static object GetMapperForType(Type type) return closedGenericMethod.Invoke(null, [null]); } - /// - /// Maps a record to an object of the given type according to the global mapping configuration. - /// + /// Maps a record to an object of the given type according to the global mapping configuration. /// The record to be mapped. /// The type of object to be mapped. /// The mapped object. @@ -101,8 +120,8 @@ public static T Map(IRecord record) } /// - /// Registers a mapping provider. This will call on the - /// provider, allowing it to register any mappers it wishes. + /// Registers a mapping provider. This will call on the provider, + /// allowing it to register any mappers it wishes. /// /// The type of the mapping provider. public static void RegisterProvider() where T : IMappingProvider, new() @@ -111,8 +130,8 @@ public static T Map(IRecord record) } /// - /// Registers a mapping provider. This will call on the - /// provider, allowing it to register any mappers it wishes. + /// Registers a mapping provider. This will call on the provider, + /// allowing it to register any mappers it wishes. /// /// public static void RegisterProvider(IMappingProvider provider) @@ -120,15 +139,6 @@ public static void RegisterProvider(IMappingProvider provider) provider.CreateMappers(Instance); } - IMappingRegistry IMappingRegistry.RegisterMapping(Action> mappingBuilder) - { - var builder = new MappingBuilder(); - mappingBuilder(builder); - var mapper = builder.Build(); - Register(mapper); - return this; - } - private MethodInfo GetMapMethodForType(Type type) { if (_mapMethods.TryGetValue(type, out var method)) @@ -142,9 +152,7 @@ private MethodInfo GetMapMethodForType(Type type) return mapMethod; } - /// - /// Maps a record to an object of the given type according to the global mapping configuration. - /// + /// Maps a record to an object of the given type according to the global mapping configuration. /// The record to be mapped. /// The type of object to be mapped. /// The mapped object. @@ -153,37 +161,16 @@ public static object Map(IRecord record, Type type) return ((IRecordObjectMapping)Instance).Map(record, type); } - /// - /// Maps a record to a new object of the same type as the provided blueprint object. - /// + /// Maps a record to a new object of the same type as the provided blueprint object. /// The record to be mapped. - /// An object of the type to be mapped, used to determine the type of the - /// object to be created. Any values in the properties of the blueprint object will be discarded. + /// + /// An object of the type to be mapped, used to determine the type of the object to be created. Any + /// values in the properties of the blueprint object will be discarded. + /// /// The type of object that will be mapped. /// The mapped object. public static T MapFromBlueprint(IRecord record, T blueprint) { return ((IRecordObjectMapping)Instance).MapFromBlueprint(record, blueprint); } - - object IRecordObjectMapping.Map(IRecord record, Type type) - { - var mapMethod = Instance.GetMapMethodForType(type); - var mapperForType = GetMapperForType(type); - - try - { - return mapMethod.Invoke(mapperForType, [record]); - } - catch (Exception ex) - { - var inner = ex is TargetInvocationException tie ? tie.InnerException : ex; - throw new MappingFailedException($"Failed to map record to type {type.Name}.", inner); - } - } - - T IRecordObjectMapping.MapFromBlueprint(IRecord record, T blueprint) - { - return (T)Map(record, typeof(T)); - } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordPathFinder.cs b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordPathFinder.cs index 0d79044b1..32ec7c522 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordPathFinder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Mapping/RecordPathFinder.cs @@ -24,12 +24,12 @@ internal interface IRecordPathFinder internal class RecordPathFinder : IRecordPathFinder { - /// + /// public bool TryGetValueByPath(IRecord record, string path, out object value) { value = null; - if(record is null) + if (record is null) { return false; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/SessionConfig.cs b/Neo4j.Driver/Neo4j.Driver/Public/SessionConfig.cs index 0909bbcde..81068a965 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/SessionConfig.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/SessionConfig.cs @@ -16,7 +16,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Neo4j.Driver.Internal; +using Neo4j.Driver.Internal.Auth; using Neo4j.Driver.Internal.Types; namespace Neo4j.Driver; @@ -46,11 +48,6 @@ internal SessionConfig(string impersonatedUser) : this() _impersonatedUser = impersonatedUser; } - internal SessionConfig(IAuthToken authToken) : this() - { - AuthToken = authToken; - } - internal static SessionConfigBuilder Builder => new(new SessionConfig()); /// Gets the target database name for queries executed within the constructed session. @@ -84,9 +81,16 @@ internal SessionConfig(IAuthToken authToken) : this() /// /// /// - public string Database { get; internal set; } + public string Database + { + get => _database; + internal set => _database = string.IsNullOrWhiteSpace(value) ? null : value; + } - /// + private string _database; + + +/// /// The type of access required by the constructed session. This is used to route the requests originating from this /// session instance to the correct server in a clustered environment. /// @@ -153,6 +157,14 @@ public string ImpersonatedUser /// /// public INotificationsConfig NotificationsConfig { get; internal set; } + + internal DriverContext DriverContext { get; set; } + + internal Action OnPinDatabase { get; set; } + internal void PinDatabase(string database) + { + OnPinDatabase?.Invoke(database); + } } /// The builder to build a . diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Summary/IGqlStatusObject.cs b/Neo4j.Driver/Neo4j.Driver/Public/Summary/IGqlStatusObject.cs index 0457e4605..a96124962 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Summary/IGqlStatusObject.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Summary/IGqlStatusObject.cs @@ -13,41 +13,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Generic; namespace Neo4j.Driver; /// -/// This is a preview API, This API may change between minor revisions.
-/// The GQL-status object as defined by the GQL standard. Returned by +/// This is a preview API, This API may change between minor revisions.
The GQL-status object as defined by +/// the GQL standard. Returned by ///
-/// +/// /// 5.23.0 public interface IGqlStatusObject { - /// - /// Returns the GQLSTATUS as defined by the GQL standard. - /// + /// Returns the GQLSTATUS as defined by the GQL standard. /// The GQLSTATUS value. string GqlStatus { get; } - /// - /// The GQLSTATUS description. - /// + /// The GQLSTATUS description. /// The GQLSTATUS description. string StatusDescription { get; } - + /// - /// Gets the position in the query where the instance points to. Not all notifications - /// have a unique position to point to and in that case the position would be set to all 0s. + /// Gets the position in the query where the instance points to. Not all + /// notifications have a unique position to point to and in that case the position would be set to all 0s. /// IInputPosition Position { get; } /// Gets the parsed of the instance. NotificationClassification Classification { get; } - /// Gets the unparsed string value for of the instance. + /// + /// Gets the unparsed string value for of the + /// instance. + /// string RawClassification { get; } /// Gets the parsed of the instance. @@ -56,14 +54,10 @@ public interface IGqlStatusObject /// Gets the unparsed string value for of the instance. string RawSeverity { get; } - /// - /// Gets the GQL Status diagnostic record. - /// + /// Gets the GQL Status diagnostic record. /// The diagnostic record as a dictionary. IReadOnlyDictionary DiagnosticRecord { get; } - /// - /// Gets the GQL Status as string representation - /// + /// Gets the GQL Status as string representation string RawDiagnosticRecord { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Summary/IResultSummary.cs b/Neo4j.Driver/Neo4j.Driver/Public/Summary/IResultSummary.cs index 5d5efd316..d710c3314 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Summary/IResultSummary.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Summary/IResultSummary.cs @@ -69,15 +69,15 @@ public interface IResultSummary /// notifications do not affect the execution of a query. ///
IList Notifications { get; } - + /// - /// This is a preview API, This API may change between minor revisions.
- /// Gets the GQL statuses produced by the server when executing a statement or query. + /// This is a preview API, This API may change between minor revisions.
Gets the GQL statuses produced by the + /// server when executing a statement or query. ///
/// For more information about GQL Statuses /// 5.23.0 IList GqlStatusObjects { get; } - + /// /// The time it took the server to make the result available for consumption. Default to -00:00:00.001 if /// the server version does not support this field in summary. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationCategory.cs b/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationCategory.cs index 1873c8a8e..48e84ec89 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationCategory.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationCategory.cs @@ -45,14 +45,10 @@ public enum NotificationCategory /// The result of the query or command indicates a potential security issue. Security, - /// - /// Information provided while executing database and server related commands. - /// + /// Information provided while executing database and server related commands. Topology, - /// - /// Information provided while managing indexes and constraints. - /// + /// Information provided while managing indexes and constraints. Schema, /// Notification not covered by other categories. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationClassification.cs b/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationClassification.cs index 0ad2fef9f..0299a37ff 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationClassification.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Summary/NotificationClassification.cs @@ -13,14 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; - namespace Neo4j.Driver; /// -/// This is a preview API, This API may change between minor revisions.
-/// Represents the classification of server notifications surfaced by .
Used in -/// conjunction with . +/// This is a preview API, This API may change between minor revisions.
Represents the classification of +/// server notifications surfaced by .
Used in conjunction with +/// . ///
/// 5.23.0 public enum NotificationClassification @@ -49,14 +47,10 @@ public enum NotificationClassification /// The result of the query or command indicates a potential security issue. Security, - /// - /// Topology notifications provide additional information related to managing databases and servers. - /// + /// Topology notifications provide additional information related to managing databases and servers. Topology, - /// - /// Schema notifications provide additional information related to managing indexes and constraints. - /// + /// Schema notifications provide additional information related to managing indexes and constraints. Schema, /// Notification not covered by other categories. diff --git a/Neo4j.Driver/Neo4j.Driver/Public/TransactionConfig.cs b/Neo4j.Driver/Neo4j.Driver/Public/TransactionConfig.cs index 39f574739..e78144098 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/TransactionConfig.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/TransactionConfig.cs @@ -20,8 +20,8 @@ namespace Neo4j.Driver; /// -/// Configuration object containing settings for explicit and auto-commit transactions. Leave the fields unmodified to use -/// server side transaction configurations. +/// Configuration object containing settings for explicit and auto-commit transactions. Leave the fields +/// unmodified to use server side transaction configurations. /// public sealed class TransactionConfig { @@ -54,7 +54,7 @@ internal set _timeout = value; return; } - + if (value.Value < TimeSpan.Zero) { throw new ArgumentOutOfRangeException( @@ -137,11 +137,11 @@ public TransactionConfigBuilder WithTimeout(TimeSpan? timeout) if (timeout.Value < TimeSpan.Zero) { - throw new ArgumentOutOfRangeException( + throw new ArgumentOutOfRangeException( nameof(timeout), "Transaction timeout should not be negative."); } - + if (timeout.Value.Ticks % TimeSpan.TicksPerMillisecond == 0) { _config.Timeout = timeout; @@ -153,7 +153,7 @@ public TransactionConfigBuilder WithTimeout(TimeSpan? timeout) _logger.Info( $"Transaction timeout {timeout} contains sub-millisecond precision and will be rounded up to {timeSpan}."); } - + return this; } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Types/Category.cs b/Neo4j.Driver/Neo4j.Driver/Public/Types/Category.cs index 2e41faad7..46ab33e68 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Types/Category.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Types/Category.cs @@ -27,9 +27,7 @@ public enum Category /// Returned as Hint, - /// - /// Receive notifications when a query or command mentions entities that are unknown to the system. - /// + /// Receive notifications when a query or command mentions entities that are unknown to the system. /// Returned as Unrecognized, @@ -48,10 +46,7 @@ public enum Category /// Returned as Deprecation, - /// - /// Receive notifications when the result of the query or command indicates a - /// potential security issue. - /// + /// Receive notifications when the result of the query or command indicates a potential security issue. /// Returned as Security, diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Types/Classification.cs b/Neo4j.Driver/Neo4j.Driver/Public/Types/Classification.cs index f5ebca28e..145ed5cab 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Types/Classification.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Types/Classification.cs @@ -13,8 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; - namespace Neo4j.Driver; /// @@ -83,7 +81,8 @@ public enum Classification Topology, /// Receive notifications related to managing indexes and constraints. - /// Returned as in + /// + /// Returned as in /// and as in . /// Schema, diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Types/IEntity.cs b/Neo4j.Driver/Neo4j.Driver/Public/Types/IEntity.cs index 4187f7959..21a084830 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Types/IEntity.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Types/IEntity.cs @@ -29,28 +29,29 @@ public interface IEntity /// The value specified by the given key in . object this[string key] { get; } - /// Gets the value that has the specified key in and casts it to - /// the specified type. + /// Gets the properties of the entity. + IReadOnlyDictionary Properties { get; } + + /// Get the identity as a number. + [Obsolete("Replaced with ElementId, will be removed in 6.0")] + long Id { get; } + + /// Get the identity as a . + string ElementId { get; } + + /// Gets the value that has the specified key in and casts it to the specified type. /// The key. /// The type to cast the value to. /// The value specified by the given key in . T Get(string key); - /// Tries to get the value that has the specified key in and casts it to - /// the specified type. + /// + /// Tries to get the value that has the specified key in and casts it to the specified + /// type. + /// /// The key. /// The value if it exists. /// The type to cast the value to. /// true if the value exists, false otherwise. bool TryGet(string key, out T value); - - /// Gets the properties of the entity. - IReadOnlyDictionary Properties { get; } - - /// Get the identity as a number. - [Obsolete("Replaced with ElementId, will be removed in 6.0")] - long Id { get; } - - /// Get the identity as a . - string ElementId { get; } } diff --git a/Neo4j.Driver/Neo4j.Driver/Public/Types/ZonedDateTime.cs b/Neo4j.Driver/Neo4j.Driver/Public/Types/ZonedDateTime.cs index b76c5ff67..5302d9bb7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Public/Types/ZonedDateTime.cs +++ b/Neo4j.Driver/Neo4j.Driver/Public/Types/ZonedDateTime.cs @@ -157,20 +157,23 @@ public ZonedDateTime(DateTimeOffset dateTimeOffset) } /// Initializes a new instance of from given value. - /// Date time instance, If is the instance will set as true. + /// + /// Date time instance, If is the + /// instance will set as true. + /// /// TimeSpan to offset datetime by, will be converted into . /// - /// When is Utc, this instance's date time components (...) - /// will be offset by . + /// When is Utc, this instance's date time components (...) will be + /// offset by . /// /// - /// When is Local, this instance's date time components (...) - /// will be the same as . + /// When is Local, this instance's date time components (...) will + /// be the same as . /// /// - /// When is , this instance's date time components - /// (...) will be the same as - /// but will be true. + /// When is , this instance's date time + /// components (...) will be the same as but will be + /// true. /// public ZonedDateTime(DateTime dateTime, TimeSpan offset) { @@ -213,20 +216,23 @@ public ZonedDateTime(DateTime dateTime, TimeSpan offset) } /// Initializes a new instance of from given value. - /// Date time instance, If is the instance will set as true. + /// + /// Date time instance, If is the + /// instance will set as true. + /// /// Seconds to offset datetime by, will be converted into . /// - /// When is Utc, this instance's date time components (...) - /// will be offset by . + /// When is Utc, this instance's date time components (...) will be + /// offset by . /// /// - /// When is Local, this instance's date time components (...) - /// will be the same as . + /// When is Local, this instance's date time components (...) will + /// be the same as . /// /// - /// When is , this instance's date time components - /// (...) will be the same as - /// but will be true. + /// When is , this instance's date time + /// components (...) will be the same as but will be + /// true. /// public ZonedDateTime(DateTime dateTime, int offsetSeconds) { @@ -277,13 +283,16 @@ public ZonedDateTime(DateTime dateTime, int offsetSeconds) /// /// Zone name, if zone name is not known by the operating system and 's /// is not , the driver can not correctly set - /// which is required for server versions 5+, 4.4.12+, 4.3.19+.
- /// if 's is , - /// the will be caught, unless one of the local values (..) is accessed and can be sent to the server. + /// which is required for server versions 5+, 4.4.12+, 4.3.19+.
if + /// 's is , the + /// will be caught, unless one of the local values (..) is + /// accessed and can be sent to the server. /// - /// if zone name is not known by the operating system and 's - /// is not , the driver can not correctly set - /// which is required for server versions 5+, 4.4.12+, 4.3.19+. + /// + /// if zone name is not known by the operating system and + /// 's is not , the driver can not + /// correctly set which is required for server versions 5+, 4.4.12+, 4.3.19+. + /// public ZonedDateTime(DateTime dateTime, string zoneId) { Zone = zoneId != null ? Zone.Of(zoneId) : throw new ArgumentNullException(nameof(zoneId)); @@ -408,9 +417,7 @@ public ZonedDateTime( } } - /// - /// Deprecated Constructor for internal use only. - /// + /// Deprecated Constructor for internal use only. internal ZonedDateTime(IHasDateTimeComponents dateTime, Zone zone) { zone = zone ?? throw new ArgumentNullException(nameof(zone)); @@ -461,22 +468,21 @@ internal ZonedDateTime(IHasDateTimeComponents dateTime, Zone zone) } } } + /// - /// The is not recognized by the driver, or operating system.
- /// Attempting to call or will raise an . + /// The is not recognized by the driver, or operating system.
Attempting to call + /// or will raise an . ///
public bool UnknownZoneInfo { get; set; } - /// - /// Gets why this instance is has set as true. - /// + /// Gets why this instance is has set as true. public AmbiguityReason Reason { get; private set; } = AmbiguityReason.None; /// - /// Gets if this instance is has a possible ambiguity.
- /// If the date specified is near a daylight saving it could have been misinterpreted.
- /// The most common cause for this will be because the used to construct this instance did not have a specified as . - /// In order to reliably look up the offsets at a given time in a timezone we require a monotonic datetime.
+ /// Gets if this instance is has a possible ambiguity.
If the date specified is near a daylight saving it + /// could have been misinterpreted.
The most common cause for this will be because the used to + /// construct this instance did not have a specified as . In + /// order to reliably look up the offsets at a given time in a timezone we require a monotonic datetime.
///
public bool Ambiguous { get; private set; } @@ -544,9 +550,9 @@ public DateTime UtcDateTime } /// - /// Gets the offset from UTC in seconds.
- /// if is of type this will return .
- /// otherwise is of type and this will return the UTC offset at the exact point of time of . + /// Gets the offset from UTC in seconds.
if is of type this will + /// return .
otherwise is of type and + /// this will return the UTC offset at the exact point of time of . ///
/// If is not known locally. public int OffsetSeconds => _offsetSeconds ?? LookupOffsetFromZone(); diff --git a/README.md b/README.md index 19737fa26..5f568d63e 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,129 @@ # Neo4j .NET Driver -This repository contains the official [Neo4j](https://neo4j.com/) driver for .NET. + +This repository contains the official [Neo4j](https://neo4j.com/) driver for .NET. ### [API Docs](https://neo4j.com/docs/api/dotnet-driver/current/) | [Driver Manual](https://neo4j.com/docs/dotnet-manual/current/) | [Example Web App](https://github.com/neo4j-examples/movies-dotnetcore-bolt) | [Change Log](https://github.com/neo4j/neo4j-dotnet-driver/wiki/5.X-Change-Log) + This document covers the usage of the driver; for contribution guidance, see [Contributing](./CONTRIBUTING.md). ## Installation + Neo4j publishes its .NET libraries to NuGet with the following targets: -- [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0), for more info: https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0 + +- [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0), for more + info: https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0 - [.NET 6.0](https://learn.microsoft.com/en-us/dotnet/api/?view=net-6.0) - [.NET 8.0](https://learn.microsoft.com/en-us/dotnet/api/?view=net-8.0) as of 5.17 To add the latest [NuGet package](https://www.nuget.org/packages/Neo4j.Driver): + ```posh > dotnet add package Neo4j.Driver ``` ### Versions -Starting with 5.0, the Neo4j drivers moved to a monthly release cadence. A new minor version is released on the last Thursday of each month to maintain versioning consistency with the core product (Neo4j DBMS), which also has moved to a monthly cadence. -As a policy, Neo4j will not release patch versions except on rare occasions. Bug fixes and updates will go into the latest minor version; users should upgrade to a later version to patch bug fixes. Driver upgrades within a major version will never contain breaking API changes, excluding the `Neo4j.Driver.Preview` namespace reserved for the preview of features. +Starting with 5.0, the Neo4j drivers moved to a monthly release cadence. A new minor version is released on the last +Thursday of each month to maintain versioning consistency with the core product (Neo4j DBMS), which also has moved to a +monthly cadence. + +As a policy, Neo4j will not release patch versions except on rare occasions. Bug fixes and updates will go into the +latest minor version; users should upgrade to a later version to patch bug fixes. Driver upgrades within a major version +will never contain breaking API changes, excluding the `Neo4j.Driver.Preview` namespace reserved for the preview of +features. See also: https://neo4j.com/developer/kb/neo4j-supported-versions/ ### Synchronous and Reactive driver extensions -* [Neo4j.Driver.Simple](https://www.nuget.org/packages/Neo4j.Driver.Simple/) exposes synchronous APIs on the driver's IDriver interface. -* [Neo4j.Driver.Reactive](https://www.nuget.org/packages/Neo4j.Driver.Reactive/) exposes reactive APIs on the driver's IDriver interface. + +* [Neo4j.Driver.Simple](https://www.nuget.org/packages/Neo4j.Driver.Simple/) exposes synchronous APIs on the driver's + IDriver interface. +* [Neo4j.Driver.Reactive](https://www.nuget.org/packages/Neo4j.Driver.Reactive/) exposes reactive APIs on the driver's + IDriver interface. ### Strong-named -A [strong-named](https://learn.microsoft.com/en-us/dotnet/standard/assembly/strong-named) version of each driver package is available on NuGet [Neo4j.Driver.Signed](https://www.nuget.org/packages/Neo4j.Driver.Signed). The strong-named packages contain the same version of -their respective packages with strong-name compliance. _Consider using the strong-named version only if your project is strong-named or requires strong-named dependencies._ + +A [strong-named](https://learn.microsoft.com/en-us/dotnet/standard/assembly/strong-named) version of each driver package +is available on NuGet [Neo4j.Driver.Signed](https://www.nuget.org/packages/Neo4j.Driver.Signed). The strong-named +packages contain the same version of +their respective packages with strong-name compliance. _Consider using the strong-named version only if your project is +strong-named or requires strong-named dependencies._ To add the strong-named version of the driver to your project using the NuGet Package Manager: + ```posh > Install-Package Neo4j.Driver.Signed ``` ## Getting started + ### Connecting to a Neo4j database: + ```csharp using Neo4j.Driver; await using var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "password")); ``` + There are a few points to highlight when adding the driver to your project: -* Each `IDriver` instance maintains a pool of connections inside; as a result, use a single driver instance per application. -* Sessions and transactions do not open new connections if a free one is in the driver's connection pool; this makes both resources cheap to create and close. -* The driver is thread-safe and made to be used across an application. Sessions and transactions are not thread-safe; using a session or transaction concurrently will result in undefined behavior. + +* Each `IDriver` instance maintains a pool of connections inside; as a result, use a single driver instance per + application. +* Sessions and transactions do not open new connections if a free one is in the driver's connection pool; this makes + both resources cheap to create and close. +* The driver is thread-safe and made to be used across an application. Sessions and transactions are not thread-safe; + using a session or transaction concurrently will result in undefined behavior. ### Verifying connectivity: + ```csharp await driver.VerifyConnectivityAsync(); ``` -To ensure the credentials and URLs specified when creating the driver, you can call `VerifyConnectivityAsync` on the driver instance. If either configuration is wrong, the Task will result in an exception. + +To ensure the credentials and URLs specified when creating the driver, you can call `VerifyConnectivityAsync` on the +driver instance. If either configuration is wrong, the Task will result in an exception. ### Executing a single query transaction: + ```csharp await driver.ExecutableQuery("CREATE (:Node{id: 0})") .WithConfig(new QueryConfig(database:"neo4j")) .ExecuteAsync(); ``` -As of version 5.10, The .NET driver includes a fluent querying API on the driver's IDriver interface. The fluent API is the most concise API for executing single query transactions. It avoids the boilerplate that comes with handling complex problems, such as results that exceed memory or multi-query transactions. + +As of version 5.10, The .NET driver includes a fluent querying API on the driver's IDriver interface. The fluent API is +the most concise API for executing single query transactions. It avoids the boilerplate that comes with handling complex +problems, such as results that exceed memory or multi-query transactions. ### Remember to specify a database. + ```csharp .WithConfig(new QueryConfig(database:"neo4j")) ``` -Always specify the database when you know which database the transaction should execute against. By setting the database parameter, the driver avoids a roundtrip and concurrency machinery associated with negotiating a home database. + +Always specify the database when you know which database the transaction should execute against. By setting the database +parameter, the driver avoids a roundtrip and concurrency machinery associated with negotiating a home database. ### Getting Results + ```csharp var response = await driver.ExecutableQuery("MATCH (n:Node) RETURN n.id as id") .WithConfig(dbConfig) .ExecuteAsync(); ``` -The response from the fluent APIs is an [EagerResult](https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.EagerResult-1.html)> unless we use other APIs; more on that later. + +The response from the fluent APIs is +an [EagerResult](https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.EagerResult-1.html)< +IReadOnlyList<[IRecord](https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.IRecord.html)>> unless we use +other APIs; more on that later. EagerResult comprises of the following: + - All records materialized(`Result`). - keys returned from the query(`Keys`). -- a [query summary](https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.IResultSummary.html)(`Summary`). +- a [query summary](https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.IResultSummary.html)(`Summary`). #### Decomposing EagerResult + ```csharp var (result, _, _) = await driver.ExecutableQuery(query) .WithConfig(dbConfig) @@ -87,9 +131,11 @@ var (result, _, _) = await driver.ExecutableQuery(query) foreach (var record in result) Console.WriteLine($"node: {record["id"]}") ``` -EagerResult allows you to discard unneeded values with decomposition for an expressive API. + +EagerResult allows you to discard unneeded values with decomposition for an expressive API. #### Mapping + ```csharp var (result, _, _) = await driver.ExecutableQuery(query) .WithConfig(dbConfig) @@ -127,14 +173,15 @@ string name = record["name"].As(); ``` #### Temporal Types - Date and Time -The mapping among the Cypher temporal types, driver types, and convertible CLR temporal types - DateTime, TimeSpan and DateTimeOffset - (via `IConvertible` interface) are as follows: - -| Cypher Type | Driver Type | Convertible CLR Type | -|:-------------:|:-------------:|:--------------------:| -| Date | LocalDate |DateTime, DateOnly(.NET6+)| -| Time | OffsetTime |TimeOnly(.NET6+)| -| LocalTime | LocalTime | TimeSpan, DateTime | -| DateTime | ZonedDateTime | DateTimeOffset | -| LocalDateTime | LocalDateTime | DateTime | -| Duration | Duration | --- | +The mapping among the Cypher temporal types, driver types, and convertible CLR temporal types - DateTime, TimeSpan and +DateTimeOffset - (via `IConvertible` interface) are as follows: + +| Cypher Type | Driver Type | Convertible CLR Type | +|:-------------:|:-------------:|:--------------------------:| +| Date | LocalDate | DateTime, DateOnly(.NET6+) | +| Time | OffsetTime | TimeOnly(.NET6+) | +| LocalTime | LocalTime | TimeSpan, DateTime | +| DateTime | ZonedDateTime | DateTimeOffset | +| LocalDateTime | LocalDateTime | DateTime | +| Duration | Duration | --- |