From af41f906dc674bbf064ac060e9da35cc60ca5e1d Mon Sep 17 00:00:00 2001 From: Thiago Oliveira Santos Date: Sun, 20 Oct 2024 23:36:35 -0300 Subject: [PATCH] fix: bettering channel flow --- src/Codibre.GrpcSqlProxy.Api/Program.cs | 2 +- .../Impl/ContextInfo.cs | 32 +++++++ .../Impl/SqlProxyBatchQuery.cs | 4 +- .../Impl/SqlProxyClientResponseMonitor.cs | 36 ++++---- .../Impl/SqlProxyClientTunnel.ContextInfo.cs | 35 -------- .../Impl/SqlProxyClientTunnel.cs | 3 +- .../SqlProxyException.cs | 15 +++- .../DictionaryExtensions.cs | 10 ++- .../GrpcSqlProxyClientAsyncLocalTest.cs | 59 +++++++------ .../GrpcSqlProxyClientBatchTest.cs | 85 +++++++++---------- .../GrpcSqlProxyClientTest.cs | 59 +++++++------ test/Codibre.GrpcSqlProxy.Test/TestServer.cs | 31 ++++--- .../xunit.runner.json | 5 ++ 13 files changed, 198 insertions(+), 178 deletions(-) create mode 100644 src/Codibre.GrpcSqlProxy.Client/Impl/ContextInfo.cs delete mode 100644 src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.ContextInfo.cs create mode 100644 test/Codibre.GrpcSqlProxy.Test/xunit.runner.json diff --git a/src/Codibre.GrpcSqlProxy.Api/Program.cs b/src/Codibre.GrpcSqlProxy.Api/Program.cs index 2073613..a7b23ef 100644 --- a/src/Codibre.GrpcSqlProxy.Api/Program.cs +++ b/src/Codibre.GrpcSqlProxy.Api/Program.cs @@ -26,7 +26,7 @@ public static WebApplication GetApp(string[] args) // Configure the HTTP request pipeline. app.MapGrpcService(); app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); - app.Urls.Add("http://localhost:3000"); + app.Urls.Add($"http://localhost:{args.FirstOrDefault() ?? app.Configuration.GetSection("PORT").Value ?? "3000"}"); return app; } } \ No newline at end of file diff --git a/src/Codibre.GrpcSqlProxy.Client/Impl/ContextInfo.cs b/src/Codibre.GrpcSqlProxy.Client/Impl/ContextInfo.cs new file mode 100644 index 0000000..822a7c7 --- /dev/null +++ b/src/Codibre.GrpcSqlProxy.Client/Impl/ContextInfo.cs @@ -0,0 +1,32 @@ +using Codibre.GrpcSqlProxy.Api; +using Grpc.Core; + +namespace Codibre.GrpcSqlProxy.Client.Impl; + +internal class ContextInfo : IDisposable +{ + private readonly Action _clear; + private readonly ExecutionContext? _executionContext; + public bool Disposed { get; private set; } = false; + public bool Transaction { get; set; } = false; + public AsyncDuplexStreamingCall Stream { get; } + public SqlProxyClientResponseMonitor Monitor { get; } + public ContextInfo( + Func> getStream, + Action clear + ) + { + Stream = getStream(); + Monitor = new(Stream); + _clear = clear; + _executionContext = ExecutionContext.Capture(); + } + + public void Dispose() + { + Monitor.Dispose(); + Stream.Dispose(); + Disposed = true; + if (_executionContext is not null) ExecutionContext.Run(_executionContext, (_) => _clear(), null); + } +} \ No newline at end of file diff --git a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyBatchQuery.cs b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyBatchQuery.cs index 52aad87..2bd69f9 100644 --- a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyBatchQuery.cs +++ b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyBatchQuery.cs @@ -204,10 +204,10 @@ public async Task RunInTransaction(Func> else await SendTransaction(); return result; } - catch (Exception) + catch (Exception ex) { if (_transaction.TransactionOpen) await _tunnel.Rollback(); - throw; + throw new SqlProxyException(ex); } finally { diff --git a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientResponseMonitor.cs b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientResponseMonitor.cs index 153d695..5136ea0 100644 --- a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientResponseMonitor.cs +++ b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientResponseMonitor.cs @@ -16,13 +16,11 @@ internal sealed class SqlProxyClientResponseMonitor : IDisposable { private readonly AsyncDuplexStreamingCall _stream; internal readonly ConcurrentDictionary> _responseHooks = new(); - private readonly CancellationTokenSource _cancellationTokenSource = new(); + private CancellationTokenSource _cancellationTokenSource = new(); internal CancellationToken CancellationToken => _cancellationTokenSource.Token; internal event ErrorHandlerEvent? ErrorHandler; - - internal bool Running { get; private set; } = true; internal bool Started { get; private set; } = false; internal SqlProxyClientResponseMonitor( @@ -34,8 +32,6 @@ AsyncDuplexStreamingCall stream internal async void Start() { - if (Started) return; - Started = true; try { await ReadStream(); @@ -43,9 +39,6 @@ internal async void Start() catch (Exception ex) { TreatException(ex); - } - finally - { await CompleteStream(); } } @@ -53,15 +46,13 @@ internal async void Start() private void TreatException(Exception ex) { if ( - ex is not OperationCanceledException - && ErrorHandler is not null - ) ErrorHandler(this, ex); + ex is not OperationCanceledException + && ErrorHandler is not null + ) ErrorHandler(this, ex); } private async Task CompleteStream() { - _cancellationTokenSource.Cancel(); - Running = false; _responseHooks.Clear(); try { @@ -72,25 +63,30 @@ private async Task CompleteStream() { // Ignoring errors due to already closed stream } + _cancellationTokenSource.Cancel(); + _cancellationTokenSource = new(); } private async Task ReadStream() { - while (Running && await _stream.ResponseStream.MoveNext(_cancellationTokenSource.Token)) + if (Started) return; + Started = true; + while (await _stream.ResponseStream.MoveNext(_cancellationTokenSource.Token)) { var response = _stream.ResponseStream.Current; if (response is not null && _responseHooks.TryGetValue(response.Id, out var hook)) { await hook.WriteAsync(response); + if (response.Last == LastEnum.Last) + { + _responseHooks.TryRemove(response.Id, out _); + hook.Complete(); + } } } + Started = false; } internal void AddHook(string id, ChannelWriter writer) => _responseHooks.TryAdd(id, writer); - internal void RemoveHook(string id) => _responseHooks.TryRemove(id, out _); - public void Dispose() - { - Running = false; - _cancellationTokenSource.Cancel(); - } + public void Dispose() => _ = CompleteStream(); } \ No newline at end of file diff --git a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.ContextInfo.cs b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.ContextInfo.cs deleted file mode 100644 index d0ecab5..0000000 --- a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.ContextInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Codibre.GrpcSqlProxy.Api; -using Grpc.Core; - -namespace Codibre.GrpcSqlProxy.Client.Impl; - -public sealed partial class SqlProxyClientTunnel -{ - internal class ContextInfo : IDisposable - { - private readonly Action _clear; - private readonly ExecutionContext? _executionContext; - public bool Disposed { get; private set; } = false; - public bool Transaction { get; set; } = false; - public AsyncDuplexStreamingCall Stream { get; } - public SqlProxyClientResponseMonitor Monitor { get; } - public ContextInfo( - Func> getStream, - Action clear - ) - { - Stream = getStream(); - Monitor = new(Stream); - _clear = clear; - _executionContext = ExecutionContext.Capture(); - } - - public void Dispose() - { - Monitor.Dispose(); - Stream.Dispose(); - Disposed = true; - if (_executionContext is not null) ExecutionContext.Run(_executionContext, (_) => _clear(), null); - } - } -} \ No newline at end of file diff --git a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.cs b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.cs index a0d36ee..fa2ec83 100644 --- a/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.cs +++ b/src/Codibre.GrpcSqlProxy.Client/Impl/SqlProxyClientTunnel.cs @@ -13,7 +13,7 @@ namespace Codibre.GrpcSqlProxy.Client.Impl; -public sealed partial class SqlProxyClientTunnel : ISqlProxyClientTunnel +public sealed class SqlProxyClientTunnel : ISqlProxyClientTunnel { private readonly AsyncLocal _context = new(); private ContextInfo Context @@ -120,7 +120,6 @@ ContextInfo context yield return current; if (current.Last == LastEnum.Last) break; } - context.Monitor.RemoveHook(id); ClearWhenNotInTransaction(context); } diff --git a/src/Codibre.GrpcSqlProxy.Client/SqlProxyException.cs b/src/Codibre.GrpcSqlProxy.Client/SqlProxyException.cs index 7c31d80..b355e27 100644 --- a/src/Codibre.GrpcSqlProxy.Client/SqlProxyException.cs +++ b/src/Codibre.GrpcSqlProxy.Client/SqlProxyException.cs @@ -1,3 +1,16 @@ namespace Codibre.GrpcSqlProxy.Client; -public class SqlProxyException(string message) : Exception(message) { } \ No newline at end of file +public class SqlProxyException : Exception +{ + private readonly string? _stack; + public SqlProxyException(string message) : base(message) + { + _stack = null; + } + public SqlProxyException(Exception ex) : base(ex.Message) + { + _stack = ex.StackTrace; + } + + public override string? StackTrace => _stack ?? base.StackTrace; +} \ No newline at end of file diff --git a/src/Codibre.GrpcSqlProxy.Common/DictionaryExtensions.cs b/src/Codibre.GrpcSqlProxy.Common/DictionaryExtensions.cs index 102af7d..1a930fe 100644 --- a/src/Codibre.GrpcSqlProxy.Common/DictionaryExtensions.cs +++ b/src/Codibre.GrpcSqlProxy.Common/DictionaryExtensions.cs @@ -7,8 +7,14 @@ public static V GetOrSet(this Dictionary dictionary, K key, Func { if (!dictionary.TryGetValue(key, out var result)) { - result = create(); - dictionary[key] = result; + lock (dictionary) + { + if (!dictionary.TryGetValue(key, out result)) + { + result = create(); + dictionary[key] = result; + } + } } return result; diff --git a/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientAsyncLocalTest.cs b/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientAsyncLocalTest.cs index 9e9c50a..67a1701 100644 --- a/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientAsyncLocalTest.cs +++ b/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientAsyncLocalTest.cs @@ -8,7 +8,6 @@ namespace Codibre.GrpcSqlProxy.Test; -[Collection("Sequential")] public class GrpcSqlProxyClientAsyncLocalTest { [Fact] @@ -27,17 +26,17 @@ public async Task Should_Keep_Transaction_Opened() ); // Act - await client.Channel.Execute("DELETE FROM TB_PEDIDO"); + await client.Channel.Execute("DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 400001"); await client.Channel.BeginTransaction(); - await client.Channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (1)"); - var result1 = await client.Channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO"); + await client.Channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (400001)"); + var result1 = await client.Channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 400001"); await client.Channel.Rollback(); - var result2 = await client.Channel.Query("SELECT * FROM TB_PEDIDO").ToArrayAsync(); + var result2 = await client.Channel.Query("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 400001").ToArrayAsync(); // Assert result1.Should().BeOfType(); result2.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 1 }); + result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 400001 }); result2.Should().BeEquivalentTo(Array.Empty()); } @@ -55,17 +54,17 @@ public async Task Should_Inject_SqlProxy_Properly() var client = app.Services.GetRequiredService(); // Act - await client.Channel.Execute("DELETE FROM TB_PEDIDO"); + await client.Channel.Execute("DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 500001"); await client.Channel.BeginTransaction(); - await client.Channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (1)"); - var result1 = await client.Channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO"); + await client.Channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (500001)"); + var result1 = await client.Channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 500001"); await client.Channel.Rollback(); - var result2 = await client.Channel.Query("SELECT * FROM TB_PEDIDO").ToArrayAsync(); + var result2 = await client.Channel.Query("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 500001").ToArrayAsync(); // Assert result1.Should().BeOfType(); result2.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 1 }); + result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 500001 }); result2.Should().BeEquivalentTo(Array.Empty()); } @@ -86,16 +85,16 @@ public async Task Should_Use_Compression() // Act await client.Channel.BeginTransaction(); - await client.Channel.Execute("DELETE FROM TB_PRODUTO"); - await client.Channel.Execute("INSERT INTO TB_PRODUTO (CD_PRODUTO) VALUES (1)"); - var result1 = await client.Channel.QueryFirstOrDefault("SELECT * FROM TB_PRODUTO"); + await client.Channel.Execute("DELETE FROM TB_PRODUTO WHERE CD_PRODUTO = 600001"); + await client.Channel.Execute("INSERT INTO TB_PRODUTO (CD_PRODUTO) VALUES (600001)"); + var result1 = await client.Channel.QueryFirstOrDefault("SELECT * FROM TB_PRODUTO WHERE CD_PRODUTO = 600001"); await client.Channel.Rollback(); - var result2 = await client.Channel.Query("SELECT * FROM TB_PRODUTO").ToArrayAsync(); + var result2 = await client.Channel.Query("SELECT * FROM TB_PRODUTO WHERE CD_PRODUTO = 600001").ToArrayAsync(); // Assert result1.Should().BeOfType(); result2.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 1 }); + result1.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 600001 }); } [Fact] @@ -146,30 +145,30 @@ public async Task Should_Keep_Parallel_Transaction_Opened() // Act using var channel2 = client.CreateChannel(); - await client.Channel.Execute("DELETE FROM TB_PESSOA"); - await client.Channel.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (1)"); - await client.Channel.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (2)"); + await client.Channel.Execute("DELETE FROM TB_PESSOA WHERE CD_PESSOA IN (10, 20, 30, 50)"); + await client.Channel.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (10)"); + await client.Channel.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (20)"); await client.Channel.BeginTransaction(); await channel2.BeginTransaction(); - await client.Channel.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 3 WHERE CD_PESSOA = @Id", new() + await client.Channel.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 30 WHERE CD_PESSOA = @Id", new() { Params = new { - Id = 1 + Id = 10 } }); var result1 = await client.Channel.QueryFirst("SELECT * FROM TB_PESSOA WHERE CD_PESSOA = @Id", new() { Params = new { - Id = 3 + Id = 30 } }); await client.Channel.Rollback(); - await channel2.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 5 WHERE CD_PESSOA = 2"); - var result2 = await channel2.Query("SELECT * FROM TB_PESSOA").ToArrayAsync(); + await channel2.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 50 WHERE CD_PESSOA = 20"); + var result2 = await channel2.Query("SELECT * FROM TB_PESSOA WHERE CD_PESSOA IN (10, 20, 30, 50)").ToArrayAsync(); await channel2.Rollback(); - var result3 = await client.Channel.Query("SELECT * FROM TB_PESSOA", new() + var result3 = await client.Channel.Query("SELECT * FROM TB_PESSOA WHERE CD_PESSOA IN (10, 20, 30, 50)", new() { PacketSize = 1 }).ToArrayAsync(); @@ -178,14 +177,14 @@ public async Task Should_Keep_Parallel_Transaction_Opened() result1.Should().BeOfType(); result2.Should().BeOfType(); result3.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PESSOA { CD_PESSOA = 3 }); + result1.Should().BeEquivalentTo(new TB_PESSOA { CD_PESSOA = 30 }); result2.OrderBy(x => x.CD_PESSOA).ToArray().Should().BeEquivalentTo(new TB_PESSOA[] { - new () { CD_PESSOA = 1 }, - new () { CD_PESSOA = 5 } + new () { CD_PESSOA = 10 }, + new () { CD_PESSOA = 50 } }); result3.OrderBy(x => x.CD_PESSOA).ToArray().Should().BeEquivalentTo(new TB_PESSOA[] { - new () { CD_PESSOA = 1 }, - new () { CD_PESSOA = 2 } + new () { CD_PESSOA = 10 }, + new () { CD_PESSOA = 20 } }); } } \ No newline at end of file diff --git a/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientBatchTest.cs b/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientBatchTest.cs index 9724a6a..7e5c53a 100644 --- a/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientBatchTest.cs +++ b/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientBatchTest.cs @@ -6,9 +6,13 @@ namespace Codibre.GrpcSqlProxy.Test; -[Collection("Sequential")] public class GrpcSqlProxyClientBatchTest { + private IEnumerable GetList() + { + for (var i = 0; i < 3000; i++) yield return i; + } + [Fact] public async Task Should_Run_Transaction_In_Batch() { @@ -28,11 +32,11 @@ public async Task Should_Run_Transaction_In_Batch() using var channel = client.CreateChannel(); await channel.Batch.RunInTransaction(() => { - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES (12345)"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 123456"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES (123456)"); channel.Batch.CancelTransaction(); }); - var result = await channel.Query("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 12345").ToArrayAsync(); + var result = await channel.Query("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 123456").ToArrayAsync(); // Assert result.Should().BeOfType(); @@ -58,11 +62,11 @@ public async Task Should_Run_Transaction_In_One_RoundTrip() using var channel = client.CreateChannel(); await channel.Batch.RunInTransaction(() => { - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 12345"); channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES (12345)"); }); var resultHook = channel.Batch.QueryHook($"SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 12345"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 12345"); await channel.Batch.RunQueries(); var result = resultHook.Result; @@ -92,16 +96,16 @@ public async Task Should_Run_Query_Batch() // Act using var channel = client.CreateChannel(); channel.Batch.AddNoResultScript($"BEGIN TRANSACTION"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PRODUTO"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PESSOA"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES ({1})"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PRODUTO VALUES ({2})"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({3})"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({4})"); - var orderHook = channel.Batch.QueryFirstHook($"SELECT TOP 1 * FROM TB_PEDIDO"); - var personHook = channel.Batch.QueryHook($"SELECT * FROM TB_PESSOA"); - var productHook = channel.Batch.QueryFirstOrDefaultHook($"SELECT TOP 1 * FROM TB_PRODUTO"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 50001"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PRODUTO WHERE CD_PRODUTO = 50002"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PESSOA WHERE CD_PESSOA IN (50003, 50004)"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES ({50001})"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PRODUTO VALUES ({50002})"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({50003})"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({50004})"); + var orderHook = channel.Batch.QueryFirstHook($"SELECT TOP 1 * FROM TB_PEDIDO WHERE CD_PEDIDO = 50001"); + var personHook = channel.Batch.QueryHook($"SELECT * FROM TB_PESSOA WHERE CD_PESSOA IN (50003, 50004)"); + var productHook = channel.Batch.QueryFirstOrDefaultHook($"SELECT TOP 1 * FROM TB_PRODUTO WHERE CD_PRODUTO = 50002"); channel.Batch.AddNoResultScript($"ROLLBACK"); await channel.Batch.RunQueries(); @@ -109,12 +113,12 @@ public async Task Should_Run_Query_Batch() orderHook.Result.Should().BeOfType(); personHook.Result.ToArray().Should().BeOfType(); productHook.Result.Should().BeOfType(); - orderHook.Result.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 1 }); + orderHook.Result.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 50001 }); personHook.Result.Should().BeEquivalentTo([ - new TB_PESSOA { CD_PESSOA = 3 }, - new TB_PESSOA { CD_PESSOA = 4 } + new TB_PESSOA { CD_PESSOA = 50003 }, + new TB_PESSOA { CD_PESSOA = 50004 } ]); - productHook.Result.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 2 }); + productHook.Result.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 50002 }); } [Fact] @@ -135,16 +139,16 @@ public async Task Should_Run_Query_Batch_Using_CustomOptions() // Act using var channel = client.CreateChannel(); channel.Batch.AddNoResultScript($"BEGIN TRANSACTION"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PRODUTO"); - channel.Batch.AddNoResultScript($"DELETE FROM TB_PESSOA"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES ({1})"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PRODUTO VALUES ({2})"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({3})"); - channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({4})"); - var orderHook = channel.Batch.QueryFirstHook($"SELECT TOP 1 * FROM TB_PEDIDO"); - var personHook = channel.Batch.QueryHook($"SELECT * FROM TB_PESSOA"); - var productHook = channel.Batch.QueryFirstOrDefaultHook($"SELECT TOP 1 * FROM TB_PRODUTO"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 40001"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PRODUTO WHERE CD_PRODUTO = 40002"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PESSOA WHERE CD_PESSOA IN (40003, 40004)"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PEDIDO VALUES ({40001})"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PRODUTO VALUES ({40002})"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({40003})"); + channel.Batch.AddNoResultScript($"INSERT INTO TB_PESSOA VALUES ({40004})"); + var orderHook = channel.Batch.QueryFirstHook($"SELECT TOP 1 * FROM TB_PEDIDO WHERE CD_PEDIDO = 40001"); + var personHook = channel.Batch.QueryHook($"SELECT * FROM TB_PESSOA WHERE CD_PESSOA IN (40003, 40004)"); + var productHook = channel.Batch.QueryFirstOrDefaultHook($"SELECT TOP 1 * FROM TB_PRODUTO WHERE CD_PRODUTO = 40002"); channel.Batch.AddNoResultScript($"ROLLBACK"); await channel.Batch.RunQueries(new() { @@ -156,17 +160,12 @@ await channel.Batch.RunQueries(new() orderHook.Result.Should().BeOfType(); personHook.Result.ToArray().Should().BeOfType(); productHook.Result.Should().BeOfType(); - orderHook.Result.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 1 }); + orderHook.Result.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 40001 }); personHook.Result.Should().BeEquivalentTo([ - new TB_PESSOA { CD_PESSOA = 3 }, - new TB_PESSOA { CD_PESSOA = 4 } + new TB_PESSOA { CD_PESSOA = 40003 }, + new TB_PESSOA { CD_PESSOA = 40004 } ]); - productHook.Result.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 2 }); - } - - private IEnumerable GetList() - { - for (var i = 0; i < 3000; i++) yield return i; + productHook.Result.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 40002 }); } [Fact] @@ -187,10 +186,10 @@ public async Task Should_Deal_With_Parameter_Limitation() // Act using var channel = client.CreateChannel(); List<(int, TB_PEDIDO)> list = []; - var pars = GetList().ToArray(); + var pars = GetList().Select(x => x * 10000).ToArray(); await channel.Batch.RunInTransaction(async () => { - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO >= {pars.Min()} AND CD_PEDIDO <= {pars.Max()}"); foreach (var i in pars) { await channel.Batch.AddTransactionScript($"INSERT INTO TB_PEDIDO VALUES ({i})"); @@ -234,10 +233,10 @@ public async Task Should_Run_PrepareBatchQuery_WithAsyncCallback() // Act using var channel = client.CreateChannel(); List<(int, TB_PEDIDO)> list = []; - var pars = GetList().ToArray(); + var pars = GetList().Select(x => x * 1000).ToArray(); await channel.Batch.RunInTransaction(async () => { - channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO"); + channel.Batch.AddNoResultScript($"DELETE FROM TB_PEDIDO WHERE CD_PEDIDO >= {pars.Min()} AND CD_PEDIDO <= {pars.Max()}"); foreach (var i in pars) { await channel.Batch.AddTransactionScript($"INSERT INTO TB_PEDIDO VALUES ({i})"); diff --git a/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientTest.cs b/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientTest.cs index 76fef82..47af7e1 100644 --- a/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientTest.cs +++ b/test/Codibre.GrpcSqlProxy.Test/GrpcSqlProxyClientTest.cs @@ -8,7 +8,6 @@ namespace Codibre.GrpcSqlProxy.Test; -[Collection("Sequential")] public class GrpcSqlProxyClientTest { [Fact] @@ -28,17 +27,17 @@ public async Task Should_Keep_Transaction_Opened() // Act using var channel = client.CreateChannel(); - await channel.Execute("DELETE FROM TB_PEDIDO"); + await channel.Execute("DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 600001"); await channel.BeginTransaction(); - await channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (1)"); - var result1 = await channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO"); + await channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (600001)"); + var result1 = await channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 600001"); await channel.Rollback(); - var result2 = await channel.Query("SELECT * FROM TB_PEDIDO").ToArrayAsync(); + var result2 = await channel.Query("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 600001").ToArrayAsync(); // Assert result1.Should().BeOfType(); result2.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 1 }); + result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 600001 }); result2.Should().BeEquivalentTo(Array.Empty()); } @@ -57,17 +56,17 @@ public async Task Should_Inject_SqlProxy_Properly() // Act using var channel = client.CreateChannel(); - await channel.Execute("DELETE FROM TB_PEDIDO"); + await channel.Execute("DELETE FROM TB_PEDIDO WHERE CD_PEDIDO = 700001"); await channel.BeginTransaction(); - await channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (1)"); - var result1 = await channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO"); + await channel.Execute("INSERT INTO TB_PEDIDO (CD_PEDIDO) VALUES (700001)"); + var result1 = await channel.QueryFirstOrDefault("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 700001"); await channel.Rollback(); - var result2 = await channel.Query("SELECT * FROM TB_PEDIDO").ToArrayAsync(); + var result2 = await channel.Query("SELECT * FROM TB_PEDIDO WHERE CD_PEDIDO = 700001").ToArrayAsync(); // Assert result1.Should().BeOfType(); result2.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 1 }); + result1.Should().BeEquivalentTo(new TB_PEDIDO { CD_PEDIDO = 700001 }); result2.Should().BeEquivalentTo(Array.Empty()); } @@ -89,16 +88,16 @@ public async Task Should_Use_Compression() // Act using var channel = client.CreateChannel(); await channel.BeginTransaction(); - await channel.Execute("DELETE FROM TB_PRODUTO"); - await channel.Execute("INSERT INTO TB_PRODUTO (CD_PRODUTO) VALUES (1)"); - var result1 = await channel.QueryFirstOrDefault("SELECT * FROM TB_PRODUTO"); + await channel.Execute("DELETE FROM TB_PRODUTO WHERE CD_PRODUTO = 800001"); + await channel.Execute("INSERT INTO TB_PRODUTO (CD_PRODUTO) VALUES (800001)"); + var result1 = await channel.QueryFirstOrDefault("SELECT * FROM TB_PRODUTO WHERE CD_PRODUTO = 800001"); await channel.Rollback(); - var result2 = await channel.Query("SELECT * FROM TB_PRODUTO").ToArrayAsync(); + var result2 = await channel.Query("SELECT * FROM TB_PRODUTO WHERE CD_PRODUTO = 800001").ToArrayAsync(); // Assert result1.Should().BeOfType(); result2.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 1 }); + result1.Should().BeEquivalentTo(new TB_PRODUTO { CD_PRODUTO = 800001 }); } [Fact] @@ -150,30 +149,30 @@ public async Task Should_Keep_Parallel_Transaction_Opened() // Act using var channel1 = client.CreateChannel(); using var channel2 = client.CreateChannel(); - await channel1.Execute("DELETE FROM TB_PESSOA"); - await channel1.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (1)"); - await channel1.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (2)"); + await channel1.Execute("DELETE FROM TB_PESSOA WHERE CD_PESSOA IN (100, 200, 300, 500)"); + await channel1.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (100)"); + await channel1.Execute("INSERT INTO TB_PESSOA (CD_PESSOA) VALUES (200)"); await channel1.BeginTransaction(); await channel2.BeginTransaction(); - await channel1.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 3 WHERE CD_PESSOA = @Id", new() + await channel1.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 300 WHERE CD_PESSOA = @Id", new() { Params = new { - Id = 1 + Id = 100 } }); var result1 = await channel1.QueryFirst("SELECT * FROM TB_PESSOA WHERE CD_PESSOA = @Id", new() { Params = new { - Id = 3 + Id = 300 } }); await channel1.Rollback(); - await channel2.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 5 WHERE CD_PESSOA = 2"); - var result2 = await channel2.Query("SELECT * FROM TB_PESSOA").ToArrayAsync(); + await channel2.Execute("UPDATE TB_PESSOA SET CD_PESSOA = 500 WHERE CD_PESSOA = 200"); + var result2 = await channel2.Query("SELECT * FROM TB_PESSOA WHERE CD_PESSOA IN (100, 200, 300, 500)").ToArrayAsync(); await channel2.Rollback(); - var result3 = await channel1.Query("SELECT * FROM TB_PESSOA", new() + var result3 = await channel1.Query("SELECT * FROM TB_PESSOA WHERE CD_PESSOA IN (100, 200, 300, 500)", new() { PacketSize = 1 }).ToArrayAsync(); @@ -182,14 +181,14 @@ public async Task Should_Keep_Parallel_Transaction_Opened() result1.Should().BeOfType(); result2.Should().BeOfType(); result3.Should().BeOfType(); - result1.Should().BeEquivalentTo(new TB_PESSOA { CD_PESSOA = 3 }); + result1.Should().BeEquivalentTo(new TB_PESSOA { CD_PESSOA = 300 }); result2.OrderBy(x => x.CD_PESSOA).ToArray().Should().BeEquivalentTo(new TB_PESSOA[] { - new () { CD_PESSOA = 1 }, - new () { CD_PESSOA = 5 } + new () { CD_PESSOA = 100 }, + new () { CD_PESSOA = 500 } }); result3.OrderBy(x => x.CD_PESSOA).ToArray().Should().BeEquivalentTo(new TB_PESSOA[] { - new () { CD_PESSOA = 1 }, - new () { CD_PESSOA = 2 } + new () { CD_PESSOA = 100 }, + new () { CD_PESSOA = 200 } }); } } \ No newline at end of file diff --git a/test/Codibre.GrpcSqlProxy.Test/TestServer.cs b/test/Codibre.GrpcSqlProxy.Test/TestServer.cs index 47ea525..98d9b87 100644 --- a/test/Codibre.GrpcSqlProxy.Test/TestServer.cs +++ b/test/Codibre.GrpcSqlProxy.Test/TestServer.cs @@ -1,4 +1,5 @@ -using Codibre.GrpcSqlProxy.Api; +using System.Security.Cryptography; +using Codibre.GrpcSqlProxy.Api; using Codibre.GrpcSqlProxy.Client; using Codibre.GrpcSqlProxy.Client.Impl; using Microsoft.AspNetCore.Builder; @@ -21,13 +22,14 @@ public class TB_PESSOA public int CD_PESSOA { get; set; } } -public class TestServer : IDisposable +public class TestServer { private readonly WebApplication _app; - private static Task _run; + private static Task? _run = null; private static TestServer? _instance = null; - public string Url { get; } = "http://localhost:3000"; + private static readonly string _port = RandomNumberGenerator.GetInt32(3000, 4000).ToString(); + public string Url { get; } = $"http://localhost:{_port}"; public IConfigurationRoot Config { get; } = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", true) @@ -36,25 +38,30 @@ public class TestServer : IDisposable private TestServer() { - _app = Program.GetApp([]); + _app = Program.GetApp([_port]); _run ??= StartApp(_app); } private async Task StartApp(WebApplication app) { - _ = Task.Run(() => app.RunAsync()); + _ = Task.Run(async () => + { + try + { + await app.RunAsync(); + } + catch + { + // Ignore + } + }); await Task.Delay(1000); } public static async Task Get() { _instance ??= new TestServer(); - await _run; + if (_run is not null) await _run; return _instance; } - - public void Dispose() - { - _app.StopAsync().GetAwaiter().GetResult(); - } } \ No newline at end of file diff --git a/test/Codibre.GrpcSqlProxy.Test/xunit.runner.json b/test/Codibre.GrpcSqlProxy.Test/xunit.runner.json new file mode 100644 index 0000000..f8fe5b5 --- /dev/null +++ b/test/Codibre.GrpcSqlProxy.Test/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "parallelizeAssembly": true, + "parallelizeTestCollections": true, + "parallelAlgorithm": "aggressive" +} \ No newline at end of file