Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce TransactionConfig parameter to query config #754

Merged
merged 7 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@ 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<string, object>()
};

return new QueryConfig(
routingControl,
data.config.database,
data.config.impersonatedUser,
bookmarkManager,
enableBookmarkManager);
enableBookmarkManager,
transactionConfig);
}

public override string Respond()
Expand Down Expand Up @@ -117,5 +124,8 @@ public class ExecuteQueryConfigDto
public string database { get; set; }
public string impersonatedUser { get; set; }
public string bookmarkManagerId { get; set; }
public int? timeout { get; set; }
[JsonConverter(typeof(QueryParameterConverter))]
public Dictionary<string, CypherToNativeObject> txMeta { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public TransactionConfigBuilder ConfigureTxTimeout(TransactionConfigBuilder conf
configBuilder.WithTimeout(timeout);
}
}
catch (ArgumentOutOfRangeException e) when ((timeout ?? 0) < 0 && e.ParamName == "value")
catch (ArgumentOutOfRangeException e) when ((timeout ?? 0) < 0 && e.ParamName == "timeout")
{
throw new DriverExceptionWrapper(e);
}
Expand Down
2 changes: 1 addition & 1 deletion Neo4j.Driver/Neo4j.Driver.Tests/AsyncSessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public async void PipelinedShouldBeginWithoutBlocking()
false,
false);

await session.PipelinedExecuteReadAsync(_ => Task.FromResult(null as EagerResult<IRecord[]>));
await session.PipelinedExecuteReadAsync(_ => Task.FromResult(null as EagerResult<IRecord[]>), new TransactionConfig());

mockProtocol.Verify(
x =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void ShouldHandleSetValues(int major, int minor)
new BoltProtocolVersion(major, minor),
"neo4j",
bookmarks,
new TransactionConfig(txMeta, TimeSpan.FromSeconds(1)),
new TransactionConfig { Metadata = txMeta, Timeout = TimeSpan.FromSeconds(1) },
AccessMode.Read,
new SessionConfig("Douglas Fir"),
null);
Expand Down Expand Up @@ -89,7 +89,7 @@ public void ShouldIgnoreNotificationConfig(int major, int minor)
new BoltProtocolVersion(major, minor),
"neo4j",
bookmarks,
new TransactionConfig(txMeta, TimeSpan.FromSeconds(1)),
new TransactionConfig { Metadata = txMeta, Timeout = TimeSpan.FromSeconds(1) },
AccessMode.Read,
new SessionConfig("Douglas Fir"),
new NotificationsDisabledConfig());
Expand Down Expand Up @@ -122,7 +122,7 @@ public void ShouldHandleSetValuesWithNotifications(int major, int minor)
new BoltProtocolVersion(major, minor),
"neo4j",
bookmarks,
new TransactionConfig(txMeta, TimeSpan.FromSeconds(1)),
new TransactionConfig { Metadata = txMeta, Timeout = TimeSpan.FromSeconds(1) },
AccessMode.Read,
new SessionConfig("Douglas Fir"),
new NotificationsConfig(Severity.Warning, new[] { Category.Generic }));
Expand Down Expand Up @@ -153,12 +153,14 @@ public void ShouldThrowIfBoltVersionLessThan44()
BoltProtocolVersion.V4_3,
"neo4j",
new InternalBookmarks("bm:a"),
new TransactionConfig(
new Dictionary<string, object>
new TransactionConfig
{
Metadata = new Dictionary<string, object>
{
["a"] = "b"
},
TimeSpan.FromSeconds(1)),
Timeout = TimeSpan.FromSeconds(1)
},
AccessMode.Read,
new SessionConfig("Douglas Fir"),
null))
Expand Down
213 changes: 135 additions & 78 deletions Neo4j.Driver/Neo4j.Driver.Tests/TransactionConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,92 +16,149 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using Neo4j.Driver.Internal.Logging;
using Xunit;

namespace Neo4j.Driver.Tests
namespace Neo4j.Driver.Tests;

public class TransactionConfigTests
{
public class TransactionConfigTests
public class TimeoutField
{
public static IEnumerable<object[]> InvalidTimeSpanValues => new[]
{
new object[] { TimeSpan.FromSeconds(-1) },
new object[] { TimeSpan.FromHours(-2) }
};

public static IEnumerable<object[]> ValidTimeSpanValues => new[]
{
new object[] { null },
new object[] { (TimeSpan?)TimeSpan.Zero },
new object[] { (TimeSpan?)TimeSpan.FromMilliseconds(1) },
new object[] { (TimeSpan?)TimeSpan.FromMinutes(30) },
new object[] { (TimeSpan?)TimeSpan.MaxValue }
};

[Fact]
public void ShouldReturnDefaultValueAsNull()
{
var config = new TransactionConfig();

config.Timeout.Should().Be(null);
}

[Theory]
[MemberData(nameof(ValidTimeSpanValues))]
public void ShouldAllowToSetToNewValue(TimeSpan? input)
{
var builder = new TransactionConfigBuilder(null, TransactionConfig.Default);
builder.WithTimeout(input);

var config = builder.Build();

config.Timeout.Should().Be(input);
}

[Theory]
[MemberData(nameof(ValidTimeSpanValues))]
public void ShouldAllowToInitToNewValue(TimeSpan? input)
{
var config = new TransactionConfig { Timeout = input };
config.Timeout.Should().Be(input);
}

[Fact]
public void ShouldRoundUpWithBuilder()
{
var ts = TimeSpan.FromTicks(1);
var builder = new TransactionConfigBuilder(NullLogger.Instance, TransactionConfig.Default);
builder.WithTimeout(ts);

var config = builder.Build();

config.Timeout.Should().Be(TimeSpan.FromMilliseconds(1));
}

[Fact]
public void ShouldRoundUpWithInit()
{
var ts = TimeSpan.FromTicks(1);
var config = new TransactionConfig { Timeout = ts };
config.Timeout.Should().Be(TimeSpan.FromMilliseconds(1));
}

[Theory]
[MemberData(nameof(InvalidTimeSpanValues))]
public void ShouldThrowExceptionIfAssigningValueLessThanZero(TimeSpan input)
{
var error = Record.Exception(
() => new TransactionConfigBuilder(null, TransactionConfig.Default).WithTimeout(input));

error.Should().BeOfType<ArgumentOutOfRangeException>();
error.Message.Should().Contain("not be negative");
}

[Theory]
[MemberData(nameof(InvalidTimeSpanValues))]
public void ShouldThrowExceptionIfInitValueLessThanZero(TimeSpan input)
{
var error = Record.Exception(() => new TransactionConfig { Timeout = input });
error.Should().BeOfType<ArgumentOutOfRangeException>();
error.Message.Should().Contain("not be negative");
}
}

public class MetadataField
{
public class TimeoutField
[Fact]
public void ShouldReturnDefaultValueEmptyDictionary()
{
var config = new TransactionConfig();

config.Metadata.Should().BeEmpty();
}

[Fact]
public void ShouldAllowToSetToNewValue()
{
var builder = new TransactionConfigBuilder(null, TransactionConfig.Default)
.WithMetadata(new Dictionary<string, object> { { "key", "value" } });

var config = builder.Build();

config.Metadata.Should()
.HaveCount(1)
.And.Contain(new KeyValuePair<string, object>("key", "value"));
}

[Fact]
public void ShouldThrowExceptionIfAssigningNull()
{
public static IEnumerable<object[]> InvalidTimeSpanValues => new[]
{
new object[] { TimeSpan.FromSeconds(-1) },
new object[] { TimeSpan.FromHours(-2) }
};

public static IEnumerable<object[]> ValidTimeSpanValues => new[]
{
new object[] { null },
new object[] { (TimeSpan?)TimeSpan.Zero },
new object[] { (TimeSpan?)TimeSpan.FromMilliseconds(1) },
new object[] { (TimeSpan?)TimeSpan.FromMinutes(30) },
new object[] { (TimeSpan?)TimeSpan.MaxValue }
};

[Fact]
public void ShouldReturnDefaultValueAsNull()
{
var config = new TransactionConfig();

config.Timeout.Should().Be(null);
}

[Theory]
[MemberData(nameof(ValidTimeSpanValues))]
public void ShouldAllowToSetToNewValue(TimeSpan? input)
{
var builder = new TransactionConfigBuilder(null, TransactionConfig.Default);
builder.WithTimeout(input);

var config = builder.Build();

config.Timeout.Should().Be(input);
}

[Theory]
[MemberData(nameof(InvalidTimeSpanValues))]
public void ShouldThrowExceptionIfAssigningValueLessThanZero(TimeSpan input)
{
var error = Record.Exception(() => new TransactionConfigBuilder(null, TransactionConfig.Default).WithTimeout(input));

error.Should().BeOfType<ArgumentOutOfRangeException>();
error.Message.Should().Contain("not be negative");
}
var error = Record.Exception(
() => new TransactionConfigBuilder(null, TransactionConfig.Default).WithMetadata(null));

error.Should().BeOfType<ArgumentNullException>();
error.Message.Should().Contain("should not be null");
}

[Fact]
public void ShouldAllowToInitToNewValue()
{
var config = new TransactionConfig { Metadata = new Dictionary<string, object> { ["key"] = "value" } };

config.Metadata.Should()
.HaveCount(1)
.And.Contain(new KeyValuePair<string, object>("key", "value"));
}

public class MetadataField
[Fact]
public void ShouldThrowExceptionIfInitNull()
{
[Fact]
public void ShouldReturnDefaultValueEmptyDictionary()
{
var config = new TransactionConfig();

config.Metadata.Should().BeEmpty();
}

[Fact]
public void ShouldAllowToSetToNewValue()
{
var builder = new TransactionConfigBuilder(null, TransactionConfig.Default)
.WithMetadata(new Dictionary<string, object> { { "key", "value" } });

var config = builder.Build();

config.Metadata.Should()
.HaveCount(1)
.And.Contain(new KeyValuePair<string, object>("key", "value"));
}

[Fact]
public void ShouldThrowExceptionIfAssigningNull()
{
var error = Record.Exception(
() => new TransactionConfigBuilder(null, TransactionConfig.Default).WithMetadata(null));

error.Should().BeOfType<ArgumentNullException>();
error.Message.Should().Contain("should not be null");
}
var error = Record.Exception(() => new TransactionConfig { Metadata = null });

error.Should().BeOfType<ArgumentNullException>();
error.Message.Should().Contain("should not be null");
}
}
}
11 changes: 8 additions & 3 deletions Neo4j.Driver/Neo4j.Driver/ExecuteQuery/QueryConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ public QueryConfig(
string database = null,
string impersonatedUser = null,
IBookmarkManager bookmarkManager = null,
bool enableBookmarkManager = true)
bool enableBookmarkManager = true,
TransactionConfig transactionConfig = null)
{
Routing = routing;
Database = database;
ImpersonatedUser = impersonatedUser;
BookmarkManager = bookmarkManager;
EnableBookmarkManager = enableBookmarkManager;
TransactionConfig = transactionConfig ?? TransactionConfig.Default;
}
/// <summary>Config for the transaction that will be used to execute the query.</summary>
public TransactionConfig TransactionConfig { get; }

/// <summary>Members of the cluster the query can be processed by.</summary>
public RoutingControl Routing { get; }
Expand Down Expand Up @@ -94,8 +98,9 @@ public QueryConfig(
string database = null,
string impersonatedUser = null,
IBookmarkManager bookmarkManager = null,
bool enableBookmarkManager = true)
: base(routing, database, impersonatedUser, bookmarkManager, enableBookmarkManager)
bool enableBookmarkManager = true,
TransactionConfig transactionConfig = null)
: base(routing, database, impersonatedUser, bookmarkManager, enableBookmarkManager, transactionConfig)
{
CursorProcessor = cursorProcessor ?? throw new ArgumentNullException(nameof(cursorProcessor));
}
Expand Down
Loading