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

Fix transaction isolation level to a configurable setting #207

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// -----------------------------------------------------------------------

using System;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -32,6 +33,9 @@ public abstract class TestSettings
public abstract string TableMapping { get; }

public virtual string? SchemaName { get; } = null;

public virtual IsolationLevel ReadIsolationLevel => IsolationLevel.Unspecified;
public virtual IsolationLevel WriteIsolationLevel => IsolationLevel.Unspecified;
}

public abstract class DataCompatibilitySpecBase<T> : IAsyncLifetime where T : ITestContainer, new()
Expand Down Expand Up @@ -81,6 +85,9 @@ protected Configuration.Config Config()
tag-write-mode = Csv
delete-compatibility-mode = true

read-isolation-level = {Settings.ReadIsolationLevel.ToHocon()}
write-isolation-level = {Settings.WriteIsolationLevel.ToHocon()}

# Testing for https://github.com/akkadotnet/Akka.Persistence.Sql/pull/117#discussion_r1027345449
batch-size = 3
db-round-trip-max-batch-size = 6
Expand All @@ -100,6 +107,9 @@ protected Configuration.Config Config()
table-mapping = {Settings.TableMapping}
tag-read-mode = Csv

read-isolation-level = {Settings.ReadIsolationLevel.ToHocon()}
write-isolation-level = {Settings.WriteIsolationLevel.ToHocon()}

# Testing for https://github.com/akkadotnet/Akka.Persistence.Sql/pull/117#discussion_r1027345449
batch-size = 3
replay-batch-size = 6
Expand All @@ -115,6 +125,9 @@ protected Configuration.Config Config()
table-mapping = {Settings.TableMapping}
auto-initialize = off

read-isolation-level = {Settings.ReadIsolationLevel.ToHocon()}
write-isolation-level = {Settings.WriteIsolationLevel.ToHocon()}

{(Settings.SchemaName is { } ? @$"
{Settings.TableMapping} {{
schema-name = {Settings.SchemaName}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// -----------------------------------------------------------------------
// <copyright file="DebuggingHelpers.cs" company="Akka.NET Project">
// Copyright (C) 2013-2023 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System.Diagnostics;
using LinqToDB.Data;
using Xunit.Abstractions;

namespace Akka.Persistence.Sql.Data.Compatibility.Tests.Internal
{
public static class DebuggingHelpers
{
public static void SetupTraceDump(ITestOutputHelper outputHelper)
{
DataConnection.TurnTraceSwitchOn(TraceLevel.Verbose);

DataConnection.WriteTraceLine = (message, category, level) =>
outputHelper.WriteLine($"[{level}] {message} {category}");
/*
DataConnection.OnTrace = info =>
{
try
{
if (info.TraceInfoStep == TraceInfoStep.BeforeExecute)
{
outputHelper.WriteLine(info.SqlText);
}
else if (info.TraceLevel == TraceLevel.Error)
{
var sb = new StringBuilder();

for (var ex = info.Exception; ex != null; ex = ex.InnerException)
{
sb
.AppendLine()
.AppendLine("/>>")
.AppendLine($"Exception: {ex.GetType()}")
.AppendLine($"Message : {ex.Message}")
.AppendLine(ex.StackTrace)
.AppendLine("<</");
}

outputHelper.WriteLine(sb.ToString());
}
else if (info.RecordsAffected != null)
{
outputHelper.WriteLine(
$"-- Execution time: {info.ExecutionTime}. Records affected: {info.RecordsAffected}.\r\n");
}
else
{
outputHelper.WriteLine(
$"-- Execution time: {info.ExecutionTime}\r\n");
}
}
catch (InvalidOperationException)
{
// This will sometimes get thrown because of async and ITestOutputHelper interactions.
}
};
*/
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// -----------------------------------------------------------------------
// <copyright file="Extensions.cs" company="Akka.NET Project">
// Copyright (C) 2013-2023 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Data;
using Akka.Hosting;

namespace Akka.Persistence.Sql.Data.Compatibility.Tests.Internal
{
public static class Extensions
{
public static string ToHocon(this IsolationLevel? level)
{
if (level is null)
throw new ArgumentNullException(nameof(level));

return level.Value.ToHocon();
}

public static string ToHocon(this IsolationLevel level)
=> level switch
{
IsolationLevel.Unspecified => "unspecified".ToHocon(),
IsolationLevel.ReadCommitted => "read-committed".ToHocon(),
IsolationLevel.ReadUncommitted => "read-uncommitted".ToHocon(),
IsolationLevel.RepeatableRead => "repeatable-read".ToHocon(),
IsolationLevel.Serializable => "serializable".ToHocon(),
IsolationLevel.Snapshot => "snapshot".ToHocon(),
IsolationLevel.Chaos => "chaos".ToHocon(),
_ => throw new IndexOutOfRangeException($"Unknown IsolationLevel value: {level}"),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// </copyright>
// -----------------------------------------------------------------------

using System.Data;

namespace Akka.Persistence.Sql.Data.Compatibility.Tests.MySql
{
public sealed class MySqlSpecSettings : TestSettings
Expand All @@ -15,5 +17,9 @@ private MySqlSpecSettings() { }
public override string ProviderName => LinqToDB.ProviderName.MySql;

public override string TableMapping => "mysql";

public override IsolationLevel ReadIsolationLevel => IsolationLevel.ReadCommitted;

public override IsolationLevel WriteIsolationLevel => IsolationLevel.ReadCommitted;
}
}
8 changes: 8 additions & 0 deletions src/Akka.Persistence.Sql.Hosting.Tests/JournalSettingsSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
// </copyright>
// -----------------------------------------------------------------------

using System.Data;
using Akka.Configuration;
using Akka.Persistence.Sql.Config;
using Akka.Persistence.Sql.Extensions;
using FluentAssertions;
using LanguageExt.UnitsOfMeasure;
using Xunit;
Expand Down Expand Up @@ -41,6 +43,8 @@ public void DefaultOptionsTest()
actualConfig.GetString("table-mapping").Should().Be(defaultConfig.GetString("table-mapping"));
actualConfig.GetString("serializer").Should().Be(defaultConfig.GetString("serializer"));
actualConfig.GetBoolean("auto-initialize").Should().Be(defaultConfig.GetBoolean("auto-initialize"));
actualConfig.GetIsolationLevel("read-isolation-level").Should().Be(defaultConfig.GetIsolationLevel("read-isolation-level"));
actualConfig.GetIsolationLevel("write-isolation-level").Should().Be(defaultConfig.GetIsolationLevel("write-isolation-level"));
actualConfig.GetString("default.schema-name").Should().Be(defaultConfig.GetString("default.schema-name"));

var defaultJournalConfig = defaultConfig.GetConfig("default.journal");
Expand Down Expand Up @@ -86,6 +90,8 @@ public void ModifiedOptionsTest()
ProviderName = "b",
QueryRefreshInterval = 5.Seconds(),
Serializer = "hyperion",
ReadIsolationLevel = IsolationLevel.Snapshot,
WriteIsolationLevel = IsolationLevel.Snapshot,
DatabaseOptions = new JournalDatabaseOptions(DatabaseMapping.SqlServer)
{
SchemaName = "schema",
Expand Down Expand Up @@ -133,6 +139,8 @@ public void ModifiedOptionsTest()
config.ConnectionString.Should().Be("a");
config.ProviderName.Should().Be("b");
config.DefaultSerializer.Should().Be("hyperion");
config.ReadIsolationLevel.Should().Be(IsolationLevel.Snapshot);
config.WriteIsolationLevel.Should().Be(IsolationLevel.Snapshot);

config.TableConfig.SchemaName.Should().Be("schema");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
// </copyright>
// -----------------------------------------------------------------------

using System.Data;
using Akka.Configuration;
using Akka.Persistence.Sql.Config;
using Akka.Persistence.Sql.Extensions;
using FluentAssertions;
using Xunit;

Expand Down Expand Up @@ -40,6 +42,8 @@ public void DefaultOptionsTest()
actualConfig.GetString("table-mapping").Should().Be(defaultConfig.GetString("table-mapping"));
actualConfig.GetString("serializer").Should().Be(defaultConfig.GetString("serializer"));
actualConfig.GetBoolean("auto-initialize").Should().Be(defaultConfig.GetBoolean("auto-initialize"));
actualConfig.GetIsolationLevel("read-isolation-level").Should().Be(defaultConfig.GetIsolationLevel("read-isolation-level"));
actualConfig.GetIsolationLevel("write-isolation-level").Should().Be(defaultConfig.GetIsolationLevel("write-isolation-level"));
actualConfig.GetString("default.schema-name").Should().Be(defaultConfig.GetString("default.schema-name"));

var defaultSnapshotConfig = defaultConfig.GetConfig("default.snapshot");
Expand All @@ -63,6 +67,8 @@ public void ModifiedOptionsTest()
ConnectionString = "a",
ProviderName = "b",
Serializer = "hyperion",
ReadIsolationLevel = IsolationLevel.Snapshot,
WriteIsolationLevel = IsolationLevel.Snapshot,
DatabaseOptions = new SnapshotDatabaseOptions(DatabaseMapping.SqlServer)
{
SchemaName = "schema",
Expand All @@ -89,6 +95,8 @@ public void ModifiedOptionsTest()
config.ConnectionString.Should().Be("a");
config.ProviderName.Should().Be("b");
config.DefaultSerializer.Should().Be("hyperion");
config.ReadIsolationLevel.Should().Be(IsolationLevel.Snapshot);
config.WriteIsolationLevel.Should().Be(IsolationLevel.Snapshot);

config.TableConfig.SchemaName.Should().Be("schema");

Expand Down
34 changes: 34 additions & 0 deletions src/Akka.Persistence.Sql.Hosting/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// -----------------------------------------------------------------------
// <copyright file="Extensions.cs" company="Akka.NET Project">
// Copyright (C) 2013-2023 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Data;
using Akka.Hosting;

namespace Akka.Persistence.Sql.Hosting
{
public static class Extensions
{
public static string ToHocon(this IsolationLevel? level)
{
if (level is null)
throw new ArgumentNullException(nameof(level));

return level switch
{
IsolationLevel.Unspecified => "unspecified".ToHocon(),
IsolationLevel.ReadCommitted => "read-committed".ToHocon(),
IsolationLevel.ReadUncommitted => "read-uncommitted".ToHocon(),
IsolationLevel.RepeatableRead => "repeatable-read".ToHocon(),
IsolationLevel.Serializable => "serializable".ToHocon(),
IsolationLevel.Snapshot => "snapshot".ToHocon(),
IsolationLevel.Chaos => "chaos".ToHocon(),
_ => throw new IndexOutOfRangeException($"Unknown IsolationLevel value: {level}"),
};
}

}
}
10 changes: 1 addition & 9 deletions src/Akka.Persistence.Sql.Hosting/HostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Akka.Hosting;
using Akka.Persistence.Hosting;
using Akka.Persistence.Sql.Config;
using Akka.Persistence.Sql.Hosting;

namespace Akka.Persistence.Sql.Hosting
{
Expand Down Expand Up @@ -67,16 +66,9 @@ public static class HostingExtensions
/// </para>
/// <b>Default</b>: <c>true</c>
/// </param>
/// <param name="journalDatabaseMapping">
/// <para>
/// The database options to modify database table column mapping for this journal.
/// </para>
/// <b>NOTE</b>: This is used primarily for backward compatibility,
/// you leave this empty for greenfield projects.
/// </param>
/// <param name="databaseMapping">
/// <para>
/// The database options to modify database table column mapping for this journal.
/// The <see cref="DatabaseMapping"/> to modify database table column mapping for this journal.
/// </para>
/// <b>NOTE</b>: This is used primarily for backward compatibility,
/// you leave this empty for greenfield projects.
Expand Down
33 changes: 33 additions & 0 deletions src/Akka.Persistence.Sql.Hosting/SqlJournalOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// -----------------------------------------------------------------------

using System;
using System.Data;
using System.Text;
using Akka.Hosting;
using Akka.Persistence.Hosting;
Expand Down Expand Up @@ -89,6 +90,28 @@ public SqlJournalOptions(bool isDefaultPlugin, string identifier = "sql") : base
/// </summary>
public bool? DeleteCompatibilityMode { get; set; }

/// <summary>
/// <para>
/// The isolation level of all database read query.
/// </para>
/// <para>
/// Isolation level documentation can be read
/// <a href="https://learn.microsoft.com/en-us/dotnet/api/system.data.isolationlevel?#fields">here</a>
/// </para>
/// </summary>
public IsolationLevel? ReadIsolationLevel { get; set; }

/// <summary>
/// <para>
/// The isolation level of all database write query.
/// </para>
/// <para>
/// Isolation level documentation can be read
/// <a href="https://learn.microsoft.com/en-us/dotnet/api/system.data.isolationlevel?#fields">here</a>
/// </para>
/// </summary>
public IsolationLevel? WriteIsolationLevel { get; set; }

protected override Configuration.Config InternalDefaultConfig => Default;

protected override StringBuilder Build(StringBuilder sb)
Expand All @@ -111,6 +134,12 @@ protected override StringBuilder Build(StringBuilder sb)
if (DatabaseOptions is { })
sb.AppendLine($"table-mapping = {DatabaseOptions.Mapping.Name().ToHocon()}");

if (ReadIsolationLevel is { })
sb.AppendLine($"read-isolation-level = {ReadIsolationLevel.ToHocon()}");

if (WriteIsolationLevel is { })
sb.AppendLine($"write-isolation-level = {WriteIsolationLevel.ToHocon()}");

DatabaseOptions?.Build(sb);

base.Build(sb);
Expand All @@ -123,6 +152,10 @@ protected override StringBuilder Build(StringBuilder sb)

if (TagStorageMode is { })
sb.AppendLine($"tag-read-mode = {TagStorageMode.ToString().ToHocon()}");

if (ReadIsolationLevel is { })
sb.AppendLine($"read-isolation-level = {ReadIsolationLevel.ToHocon()}");

sb.AppendLine("}");
}

Expand Down
Loading