diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs index 29a5e315..e850b0a8 100644 --- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs +++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Migrations/YdbMigrationsTest.cs @@ -392,11 +392,6 @@ await Test(_ => { }, builder => }, model => Assert.Collection( Assert.Single(model.Tables, t => t.Name == "Contacts").Columns, // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - c => - { - Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name); - Assert.True(c.IsNullable); - }, c => Assert.Equal("Id", c.Name), c => Assert.Equal("Discriminator", c.Name), c => Assert.Equal("Name", c.Name), @@ -412,6 +407,11 @@ await Test(_ => { }, builder => { Assert.Equal("MyComplex_MyNestedComplex_Bar", c.Name); Assert.True(c.IsNullable); + }, + c => + { + Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name); + Assert.True(c.IsNullable); })); AssertSql( diff --git a/src/Ydb.Sdk/src/Ado/PoolManager.cs b/src/Ydb.Sdk/src/Ado/PoolManager.cs index 7de299b8..ed2712d6 100644 --- a/src/Ydb.Sdk/src/Ado/PoolManager.cs +++ b/src/Ydb.Sdk/src/Ado/PoolManager.cs @@ -9,7 +9,7 @@ internal static class PoolManager private static readonly SemaphoreSlim SemaphoreSlim = new(1); // async mutex private static readonly ConcurrentDictionary Pools = new(); - internal static async Task GetSession( + internal static async Task GetSession( YdbConnectionStringBuilder connectionString, CancellationToken cancellationToken ) diff --git a/src/Ydb.Sdk/src/Ado/Session/ISession.cs b/src/Ydb.Sdk/src/Ado/Session/ISession.cs new file mode 100644 index 00000000..bf5522b3 --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/ISession.cs @@ -0,0 +1,23 @@ +using Ydb.Query; +using Ydb.Sdk.Value; +using TransactionControl = Ydb.Query.TransactionControl; + +namespace Ydb.Sdk.Ado.Session; + +internal interface ISession +{ + ValueTask> ExecuteQuery( + string query, + Dictionary parameters, + GrpcRequestSettings settings, + TransactionControl? txControl + ); + + Task CommitTransaction(string txId, CancellationToken cancellationToken = default); + + Task RollbackTransaction(string txId, CancellationToken cancellationToken = default); + + void OnNotSuccessStatusCode(StatusCode code); + + void Close(); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs b/src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs new file mode 100644 index 00000000..db81e3ba --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/ISessionSource.cs @@ -0,0 +1,8 @@ +namespace Ydb.Sdk.Ado.Session; + +internal interface ISessionSource where TSession : ISession +{ + ValueTask OpenSession(); + + void Return(TSession session); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs b/src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs new file mode 100644 index 00000000..c873055a --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/ImplicitSession.cs @@ -0,0 +1,56 @@ +using Ydb.Query; +using Ydb.Query.V1; +using Ydb.Sdk.Value; + +namespace Ydb.Sdk.Ado.Session; + +internal class ImplicitSession : ISession +{ + private readonly IDriver _driver; + + public ImplicitSession(IDriver driver) + { + _driver = driver; + } + + public ValueTask> ExecuteQuery( + string query, + Dictionary parameters, + GrpcRequestSettings settings, + TransactionControl? txControl + ) + { + if (txControl is not null && !txControl.CommitTx) + { + throw NotSupportedTransaction; + } + + var request = new ExecuteQueryRequest + { + ExecMode = ExecMode.Execute, + QueryContent = new QueryContent { Text = query, Syntax = Syntax.YqlV1 }, + StatsMode = StatsMode.None, + TxControl = txControl + }; + request.Parameters.Add(parameters.ToDictionary(p => p.Key, p => p.Value.GetProto())); + + return _driver.ServerStreamCall(QueryService.ExecuteQueryMethod, request, settings); + } + + public Task CommitTransaction(string txId, CancellationToken cancellationToken = default) => + throw NotSupportedTransaction; + + public Task RollbackTransaction(string txId, CancellationToken cancellationToken = default) => + throw NotSupportedTransaction; + + public void OnNotSuccessStatusCode(StatusCode code) + { + } + + public void Close() + { + } + + private static YdbException NotSupportedTransaction => + new(StatusCode.BadRequest, "Transactions are not supported in implicit sessions"); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs new file mode 100644 index 00000000..24f49e4a --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs @@ -0,0 +1,17 @@ +namespace Ydb.Sdk.Ado.Session; + +internal class PoolingSessionSource : ISessionSource +{ + public ValueTask OpenSession() => throw new NotImplementedException(); + + public void Return(IPoolingSession session) => throw new NotImplementedException(); +} + +internal interface IPoolingSession : ISession +{ + bool IsActive { get; } + + Task Open(CancellationToken cancellationToken); + + Task DeleteSession(); +} diff --git a/src/Ydb.Sdk/src/Ado/Session/Session.cs b/src/Ydb.Sdk/src/Ado/Session/Session.cs new file mode 100644 index 00000000..5a40c326 --- /dev/null +++ b/src/Ydb.Sdk/src/Ado/Session/Session.cs @@ -0,0 +1,240 @@ +using Microsoft.Extensions.Logging; +using Ydb.Query; +using Ydb.Query.V1; +using Ydb.Sdk.Ado.Internal; +using Ydb.Sdk.Value; +using CommitTransactionRequest = Ydb.Query.CommitTransactionRequest; +using TransactionControl = Ydb.Query.TransactionControl; + +namespace Ydb.Sdk.Ado.Session; + +internal class Session : IPoolingSession +{ + private const string SessionBalancer = "session-balancer"; + + private static readonly TimeSpan DeleteSessionTimeout = TimeSpan.FromSeconds(5); + private static readonly CreateSessionRequest CreateSessionRequest = new(); + + private readonly IDriver _driver; + private readonly PoolingSessionSource _poolingSessionSource; + private readonly YdbConnectionStringBuilder _settings; + private readonly ILogger _logger; + + private volatile bool _isActive; + + private string SessionId { get; set; } = string.Empty; + private long NodeId { get; set; } + + public bool IsActive => _isActive; + + internal Session( + IDriver driver, + PoolingSessionSource poolingSessionSource, + YdbConnectionStringBuilder settings, + ILogger logger + ) + { + _driver = driver; + _poolingSessionSource = poolingSessionSource; + _settings = settings; + _logger = logger; + } + + public ValueTask> ExecuteQuery( + string query, + Dictionary parameters, + GrpcRequestSettings settings, + TransactionControl? txControl + ) + { + settings.NodeId = NodeId; + + var request = new ExecuteQueryRequest + { + SessionId = SessionId, + ExecMode = ExecMode.Execute, + QueryContent = new QueryContent { Text = query, Syntax = Syntax.YqlV1 }, + StatsMode = StatsMode.None, + TxControl = txControl + }; + request.Parameters.Add(parameters.ToDictionary(p => p.Key, p => p.Value.GetProto())); + + return _driver.ServerStreamCall(QueryService.ExecuteQueryMethod, request, settings); + } + + public async Task CommitTransaction( + string txId, + CancellationToken cancellationToken = default + ) + { + var response = await _driver.UnaryCall( + QueryService.CommitTransactionMethod, + new CommitTransactionRequest { SessionId = SessionId, TxId = txId }, + new GrpcRequestSettings { CancellationToken = cancellationToken, NodeId = NodeId } + ); + + if (response.Status.IsNotSuccess()) + { + throw YdbException.FromServer(response.Status, response.Issues); + } + } + + public async Task RollbackTransaction( + string txId, + CancellationToken cancellationToken = default + ) + { + var response = await _driver.UnaryCall( + QueryService.RollbackTransactionMethod, + new RollbackTransactionRequest { SessionId = SessionId, TxId = txId }, + new GrpcRequestSettings { CancellationToken = cancellationToken, NodeId = NodeId } + ); + + if (response.Status.IsNotSuccess()) + { + throw YdbException.FromServer(response.Status, response.Issues); + } + } + + public void OnNotSuccessStatusCode(StatusCode code) + { + if (code is + StatusCode.Cancelled or + StatusCode.BadSession or + StatusCode.SessionBusy or + StatusCode.InternalError or + StatusCode.ClientTransportTimeout or + StatusCode.Unavailable or + StatusCode.ClientTransportUnavailable) + { + _logger.LogWarning("Session[{SessionId}] is deactivated. Reason StatusCode: {Code}", SessionId, code); + + _isActive = false; + } + } + + public async Task Open(CancellationToken cancellationToken) + { + var requestSettings = new GrpcRequestSettings { CancellationToken = cancellationToken }; + + if (!_settings.DisableServerBalancer) + { + requestSettings.ClientCapabilities.Add(SessionBalancer); + } + + var response = await _driver.UnaryCall(QueryService.CreateSessionMethod, CreateSessionRequest, requestSettings); + + if (response.Status.IsNotSuccess()) + { + throw YdbException.FromServer(response.Status, response.Issues); + } + + TaskCompletionSource completeTask = new(); + + SessionId = response.SessionId; + NodeId = response.NodeId; + + _ = Task.Run(async () => + { + try + { + using var stream = await _driver.ServerStreamCall( + QueryService.AttachSessionMethod, + new AttachSessionRequest { SessionId = SessionId }, + new GrpcRequestSettings { NodeId = NodeId } + ); + + if (!await stream.MoveNextAsync(cancellationToken)) + { + // Session wasn't started! + completeTask.SetException(new YdbException(StatusCode.Cancelled, "Attach stream is not started!")); + + return; + } + + var initSessionState = stream.Current; + + if (initSessionState.Status.IsNotSuccess()) + { + throw YdbException.FromServer(initSessionState.Status, initSessionState.Issues); + } + + completeTask.SetResult(); + + try + { + // ReSharper disable once MethodSupportsCancellation + while (await stream.MoveNextAsync()) + { + var sessionState = stream.Current; + + var statusCode = sessionState.Status.Code(); + + _logger.LogDebug( + "Session[{SessionId}] was received the status from the attach stream: {StatusMessage}", + SessionId, statusCode.ToMessage(sessionState.Issues)); + + OnNotSuccessStatusCode(statusCode); + + if (!IsActive) + { + return; + } + } + + _logger.LogDebug("Session[{SessionId}]: Attached stream is closed", SessionId); + + // attach stream is closed + } + catch (YdbException e) + { + if (e.Code == StatusCode.Cancelled) + { + _logger.LogDebug("AttachStream is cancelled (possible grpcChannel is closing)"); + + return; + } + + _logger.LogWarning(e, "Session[{SessionId}] is deactivated by transport error", SessionId); + } + } + catch (Exception e) + { + completeTask.SetException(e); + } + finally + { + _isActive = false; + } + }, cancellationToken); + + await completeTask.Task; + } + + public async Task DeleteSession() + { + try + { + _isActive = false; + + var deleteSessionResponse = await _driver.UnaryCall( + QueryService.DeleteSessionMethod, + new DeleteSessionRequest { SessionId = SessionId }, + new GrpcRequestSettings { TransportTimeout = DeleteSessionTimeout, NodeId = NodeId } + ); + + if (deleteSessionResponse.Status.IsNotSuccess()) + { + _logger.LogWarning("Failed to delete session[{SessionId}], {StatusMessage}", SessionId, + deleteSessionResponse.Status.Code().ToMessage(deleteSessionResponse.Issues)); + } + } + catch (Exception e) + { + _logger.LogWarning(e, "Error occurred while deleting session[{SessionId}] (NodeId = {NodeId})", + SessionId, NodeId); + } + } + + public void Close() => _poolingSessionSource.Return(this); +} diff --git a/src/Ydb.Sdk/src/Ado/YdbConnection.cs b/src/Ydb.Sdk/src/Ado/YdbConnection.cs index 63ebbbe0..8cac45c2 100644 --- a/src/Ydb.Sdk/src/Ado/YdbConnection.cs +++ b/src/Ydb.Sdk/src/Ado/YdbConnection.cs @@ -24,7 +24,7 @@ private YdbConnectionStringBuilder ConnectionStringBuilder [param: AllowNull] init => _connectionStringBuilder = value; } - internal Session Session + internal Services.Query.Session Session { get { @@ -35,7 +35,7 @@ internal Session Session private set => _session = value; } - private Session _session = null!; + private Services.Query.Session _session = null!; public YdbConnection() { diff --git a/src/Ydb.Sdk/src/Client/Response.cs b/src/Ydb.Sdk/src/Client/Response.cs index 22051085..b34a2720 100644 --- a/src/Ydb.Sdk/src/Client/Response.cs +++ b/src/Ydb.Sdk/src/Client/Response.cs @@ -61,11 +61,11 @@ public abstract class StreamResponse where TProtoResponse : class where TResponse : class { - private readonly ServerStream _iterator; + private readonly IServerStream _iterator; private TResponse? _response; private bool _transportError; - internal StreamResponse(ServerStream iterator) + internal StreamResponse(IServerStream iterator) { _iterator = iterator; } diff --git a/src/Ydb.Sdk/src/IDriver.cs b/src/Ydb.Sdk/src/IDriver.cs index 7461b704..9599284f 100644 --- a/src/Ydb.Sdk/src/IDriver.cs +++ b/src/Ydb.Sdk/src/IDriver.cs @@ -17,7 +17,7 @@ public Task UnaryCall( where TRequest : class where TResponse : class; - public ValueTask> ServerStreamCall( + public ValueTask> ServerStreamCall( Method method, TRequest request, GrpcRequestSettings settings) @@ -126,7 +126,7 @@ public async Task UnaryCall( } } - public async ValueTask> ServerStreamCall( + public async ValueTask> ServerStreamCall( Method method, TRequest request, GrpcRequestSettings settings) diff --git a/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs b/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs index 58886ee6..c972dbd4 100644 --- a/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs +++ b/src/Ydb.Sdk/src/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("EntityFrameworkCore.Ydb")] +[assembly: InternalsVisibleTo("Ydb.Sdk.Ado.Benchmarks")] [assembly: InternalsVisibleTo("Ydb.Sdk.Ado.Tests")] [assembly: InternalsVisibleTo("Ydb.Sdk.Topic.Tests")] diff --git a/src/Ydb.Sdk/src/Services/Query/SessionPool.cs b/src/Ydb.Sdk/src/Services/Query/SessionPool.cs index 15ff1060..2c496b06 100644 --- a/src/Ydb.Sdk/src/Services/Query/SessionPool.cs +++ b/src/Ydb.Sdk/src/Services/Query/SessionPool.cs @@ -54,7 +54,7 @@ protected override async Task CreateSession( Status.FromProto(response.Status, response.Issues).EnsureSuccess(); - TaskCompletionSource completeTask = new(); + TaskCompletionSource completeTask = new(); var sessionId = response.SessionId; var nodeId = response.NodeId; @@ -74,12 +74,17 @@ protected override async Task CreateSession( if (!await stream.MoveNextAsync(cancellationToken)) { // Session wasn't started! - completeTask.SetResult(new Status(StatusCode.Cancelled, "Attach stream is not started!")); + completeTask.SetException(new YdbException(StatusCode.Cancelled, "Attach stream is not started!")); return; } - completeTask.SetResult(Status.FromProto(stream.Current.Status, stream.Current.Issues)); + if (stream.Current.Status.IsNotSuccess()) + { + completeTask.SetException(YdbException.FromServer(stream.Current.Status, stream.Current.Issues)); + } + + completeTask.SetResult(); try { @@ -128,7 +133,7 @@ protected override async Task CreateSession( } }, cancellationToken); - (await completeTask.Task).EnsureSuccess(); + await completeTask.Task; return session; } @@ -151,7 +156,7 @@ ILogger logger Driver = driver; } - internal ValueTask> ExecuteQuery( + internal ValueTask> ExecuteQuery( string query, Dictionary? parameters, ExecuteQuerySettings? settings, diff --git a/src/Ydb.Sdk/src/Services/Query/Settings.cs b/src/Ydb.Sdk/src/Services/Query/Settings.cs index 4938558d..fc9cf6ae 100644 --- a/src/Ydb.Sdk/src/Services/Query/Settings.cs +++ b/src/Ydb.Sdk/src/Services/Query/Settings.cs @@ -86,10 +86,10 @@ internal ExecuteQueryPart(ExecuteQueryResponsePart part) public sealed class ExecuteQueryStream : IAsyncEnumerator, IAsyncEnumerable { - private readonly ServerStream _stream; + private readonly IServerStream _stream; private readonly Action _onTxId; - internal ExecuteQueryStream(ServerStream stream, Action? onTx = null) + internal ExecuteQueryStream(IServerStream stream, Action? onTx = null) { _stream = stream; _onTxId = onTx ?? (_ => { }); diff --git a/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs b/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs index a9ce437a..045c5b64 100644 --- a/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs +++ b/src/Ydb.Sdk/src/Services/Table/ExecuteScanQuery.cs @@ -32,7 +32,7 @@ internal static ResultData FromProto(ExecuteScanQueryPartialResult resultProto) public class ExecuteScanQueryStream : StreamResponse { - internal ExecuteScanQueryStream(ServerStream iterator) + internal ExecuteScanQueryStream(IServerStream iterator) : base(iterator) { } diff --git a/src/Ydb.Sdk/src/Services/Table/ReadTable.cs b/src/Ydb.Sdk/src/Services/Table/ReadTable.cs index 39a58f29..a704aff1 100644 --- a/src/Ydb.Sdk/src/Services/Table/ReadTable.cs +++ b/src/Ydb.Sdk/src/Services/Table/ReadTable.cs @@ -36,7 +36,7 @@ internal ResultData(Value.ResultSet resultSet) public class ReadTableStream : StreamResponse { - internal ReadTableStream(ServerStream iterator) + internal ReadTableStream(IServerStream iterator) : base(iterator) { } diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs new file mode 100644 index 00000000..52b9f833 --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Program.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using BenchmarkDotNet.Running; + +Console.WriteLine("YDB .NET SDK Session Pool Benchmarks"); +Console.WriteLine("====================================="); + +var summaries = new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); + +foreach (var summary in summaries) +{ + Console.WriteLine($"Benchmark completed. Results saved to: {summary.ResultsDirectoryPath}"); +} diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md new file mode 100644 index 00000000..558eaac2 --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/README.md @@ -0,0 +1,8 @@ +# YDB .NET SDK Session Pool Benchmarks + +| Method | Mean | Error | StdDev | Completed Work Items | Lock Contentions | Gen0 | Allocated | +|--------------------------|-------------:|------------:|------------:|---------------------:|-----------------:|-------:|----------:| +| SingleThreaded_OpenClose | 130.2 ns | 0.91 ns | 0.71 ns | 0.0000 | - | 0.0257 | 216 B | +| MultiThreaded_OpenClose | 41,667.8 ns | 1,065.07 ns | 3,140.37 ns | 20.0018 | 0.3466 | 1.0376 | 8851 B | +| HighContention_OpenClose | 130,331.1 ns | 2,569.39 ns | 6,106.44 ns | 100.0000 | 1.9094 | 5.1270 | 43421 B | +| SessionReuse_Pattern | 204,351.2 ns | 4,038.25 ns | 7,485.16 ns | 20.0000 | 3.6716 | 5.6152 | 47762 B | \ No newline at end of file diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs new file mode 100644 index 00000000..4237d43e --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/SessionPoolBenchmark.cs @@ -0,0 +1,114 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Ydb.Sdk.Pool; + +namespace Ydb.Sdk.Ado.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net80)] +[MemoryDiagnoser] +[ThreadingDiagnoser] +public class SessionPoolBenchmark +{ + private TestSessionPool _sessionPool = null!; + private const int SessionPoolSize = 50; + private const int ConcurrentTasks = 20; + + [GlobalSetup] + public void Setup() + { + var config = new SessionPoolConfig(MaxSessionPool: SessionPoolSize); + + _sessionPool = new TestSessionPool(NullLogger.Instance, config); + } + + [GlobalCleanup] + public async Task Cleanup() => await _sessionPool.DisposeAsync(); + + [Benchmark] + public async Task SingleThreaded_OpenClose() + { + var session = await _sessionPool.GetSession(); + await session.Release(); + } + + [Benchmark] + public async Task MultiThreaded_OpenClose() + { + var tasks = new Task[ConcurrentTasks]; + + for (var i = 0; i < ConcurrentTasks; i++) + { + tasks[i] = Task.Run(async () => + { + var session = await _sessionPool.GetSession(); + await session.Release(); + }); + } + + await Task.WhenAll(tasks); + } + + [Benchmark] + public async Task HighContention_OpenClose() + { + const int highContentionTasks = 100; + var tasks = new Task[highContentionTasks]; + + for (var i = 0; i < highContentionTasks; i++) + { + tasks[i] = Task.Run(async () => + { + var session = await _sessionPool.GetSession(); + await session.Release(); + }); + } + + await Task.WhenAll(tasks); + } + + [Benchmark] + public async Task SessionReuse_Pattern() + { + const int iterations = 10; + var tasks = new Task[ConcurrentTasks]; + + for (var i = 0; i < ConcurrentTasks; i++) + { + tasks[i] = Task.Run(async () => + { + for (var j = 0; j < iterations; j++) + { + var session = await _sessionPool.GetSession(); + await session.Release(); + } + }); + } + + await Task.WhenAll(tasks); + } +} + +internal class TestSessionPool(ILogger logger, SessionPoolConfig config) + : SessionPool(logger, config) +{ + private volatile int _sessionIdCounter; + + protected override Task CreateSession(CancellationToken cancellationToken = default) + { + var sessionId = $"test-session-{Interlocked.Increment(ref _sessionIdCounter)}"; + var session = new TestSession(this, sessionId, nodeId: 1); + return Task.FromResult(session); + } +} + +internal class TestSession : SessionBase +{ + internal TestSession(SessionPool sessionPool, string sessionId, long nodeId) + : base(sessionPool, sessionId, nodeId, NullLogger.Instance) + { + } + + internal override Task DeleteSession() => Task.FromResult(Status.Success); +} diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj new file mode 100644 index 00000000..bf0f3e33 --- /dev/null +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Benchmarks/Ydb.Sdk.Ado.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs index b9c5ab3e..9840b761 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Pool/EndpointPoolTests.cs @@ -8,13 +8,14 @@ namespace Ydb.Sdk.Ado.Tests.Pool; public class EndpointPoolTests { - private static readonly ImmutableArray EndpointSettingsList = ImmutableArray.Create( - new EndpointSettings(1, "n1.ydb.tech", "MAN"), - new EndpointSettings(2, "n2.ydb.tech", "VLA"), - new EndpointSettings(3, "n3.ydb.tech", "SAS"), - new EndpointSettings(4, "n4.ydb.tech", "SAS"), - new EndpointSettings(5, "n5.ydb.tech", "VLA") - ); + private static readonly ImmutableArray EndpointSettingsList = + [ + new(1, "n1.ydb.tech", "MAN"), + new(2, "n2.ydb.tech", "VLA"), + new(3, "n3.ydb.tech", "SAS"), + new(4, "n4.ydb.tech", "SAS"), + new(5, "n5.ydb.tech", "VLA") + ]; public class MockRandomUnitTests { diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs index 2b51b663..c892c896 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionStringBuilderTests.cs @@ -25,6 +25,7 @@ public void InitDefaultValues_WhenEmptyConstructorInvoke_ReturnDefaultConnection Assert.False(ydbConnectionStringBuilder.DisableDiscovery); Assert.False(ydbConnectionStringBuilder.DisableServerBalancer); Assert.Equal(5, ydbConnectionStringBuilder.CreateSessionTimeout); + Assert.False(ydbConnectionStringBuilder.UseTls); } [Fact] @@ -45,13 +46,14 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection "ConnectTimeout=30;KeepAlivePingDelay=30;KeepAlivePingTimeout=60;" + "EnableMultipleHttp2Connections=true;CreateSessionTimeout=30;" + "MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;" + - "DisableDiscovery=true;DisableServerBalancer=true" + "DisableDiscovery=true;DisableServerBalancer=true;" + + "MaxSessionPool=50" ); Assert.Equal(2135, connectionString.Port); Assert.Equal("server", connectionString.Host); Assert.Equal("/my/path", connectionString.Database); - Assert.Equal(100, connectionString.MaxSessionPool); + Assert.Equal(50, connectionString.MaxSessionPool); Assert.Equal("Kirill", connectionString.User); Assert.Equal(30, connectionString.ConnectTimeout); Assert.Equal(30, connectionString.KeepAlivePingDelay); @@ -64,10 +66,12 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection "ConnectTimeout=30;KeepAlivePingDelay=30;KeepAlivePingTimeout=60;" + "EnableMultipleHttp2Connections=True;CreateSessionTimeout=30;" + "MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;" + - "DisableDiscovery=True;DisableServerBalancer=True", connectionString.ConnectionString); + "DisableDiscovery=True;DisableServerBalancer=True;" + + "MaxSessionPool=50", connectionString.ConnectionString); Assert.True(connectionString.DisableDiscovery); Assert.True(connectionString.DisableServerBalancer); Assert.Equal(30, connectionString.CreateSessionTimeout); + Assert.Equal(50, connectionString.MaxSessionPool); } [Fact] @@ -79,8 +83,7 @@ public void Host_WhenSetInProperty_ReturnUpdatedConnectionString() connectionString.Host = "new_server"; Assert.Equal("new_server", connectionString.Host); - Assert.Equal("Host=new_server;Port=2135;Database=/my/path;User=Kirill", - connectionString.ConnectionString); + Assert.Equal("Host=new_server;Port=2135;Database=/my/path;User=Kirill", connectionString.ConnectionString); } [Fact] diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs index 49357e42..3cf9852b 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbSchemaTests.cs @@ -186,23 +186,23 @@ public async Task GetSchema_WhenAllTypesTable_ReturnAllTypes() void CheckAllColumns(DataTable pDataTable, bool isNullableTable) { - CheckColumn(pDataTable.Rows[0], "TimestampColumn", 0, isNullableTable); - CheckColumn(pDataTable.Rows[1], "Int32Column", 1, isNullableTable); - CheckColumn(pDataTable.Rows[2], "BoolColumn", 2, isNullableTable); - CheckColumn(pDataTable.Rows[3], "Int64Column", 3, isNullableTable); - CheckColumn(pDataTable.Rows[4], "Int16Column", 4, isNullableTable); - CheckColumn(pDataTable.Rows[5], "Int8Column", 5, isNullableTable); - CheckColumn(pDataTable.Rows[6], "FloatColumn", 6, isNullableTable); - CheckColumn(pDataTable.Rows[7], "DoubleColumn", 7, isNullableTable); - CheckColumn(pDataTable.Rows[8], "DefaultDecimalColumn", 8, isNullableTable, "Decimal(22, 9)"); - CheckColumn(pDataTable.Rows[9], "Uint8Column", 9, isNullableTable); - CheckColumn(pDataTable.Rows[10], "Uint16Column", 10, isNullableTable); - CheckColumn(pDataTable.Rows[11], "Uint32Column", 11, isNullableTable); - CheckColumn(pDataTable.Rows[12], "Uint64Column", 12, isNullableTable); - CheckColumn(pDataTable.Rows[13], "TextColumn", 13, isNullableTable); - CheckColumn(pDataTable.Rows[14], "BytesColumn", 14, isNullableTable); - CheckColumn(pDataTable.Rows[15], "DateColumn", 15, isNullableTable); - CheckColumn(pDataTable.Rows[16], "DatetimeColumn", 16, isNullableTable); + CheckColumn(pDataTable.Rows[0], "Int32Column", 0, isNullableTable); + CheckColumn(pDataTable.Rows[1], "BoolColumn", 1, isNullableTable); + CheckColumn(pDataTable.Rows[2], "Int64Column", 2, isNullableTable); + CheckColumn(pDataTable.Rows[3], "Int16Column", 3, isNullableTable); + CheckColumn(pDataTable.Rows[4], "Int8Column", 4, isNullableTable); + CheckColumn(pDataTable.Rows[5], "FloatColumn", 5, isNullableTable); + CheckColumn(pDataTable.Rows[6], "DoubleColumn", 6, isNullableTable); + CheckColumn(pDataTable.Rows[7], "DefaultDecimalColumn", 7, isNullableTable, "Decimal(22, 9)"); + CheckColumn(pDataTable.Rows[8], "Uint8Column", 8, isNullableTable); + CheckColumn(pDataTable.Rows[9], "Uint16Column", 9, isNullableTable); + CheckColumn(pDataTable.Rows[10], "Uint32Column", 10, isNullableTable); + CheckColumn(pDataTable.Rows[11], "Uint64Column", 11, isNullableTable); + CheckColumn(pDataTable.Rows[12], "TextColumn", 12, isNullableTable); + CheckColumn(pDataTable.Rows[13], "BytesColumn", 13, isNullableTable); + CheckColumn(pDataTable.Rows[14], "DateColumn", 14, isNullableTable); + CheckColumn(pDataTable.Rows[15], "DatetimeColumn", 15, isNullableTable); + CheckColumn(pDataTable.Rows[16], "TimestampColumn", 16, isNullableTable); } void CheckColumn(DataRow column, string columnName, int ordinal, bool isNullable, string? dataType = null) diff --git a/src/YdbSdk.sln b/src/YdbSdk.sln index 46eb69ab..9803346a 100644 --- a/src/YdbSdk.sln +++ b/src/YdbSdk.sln @@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.Topic.Tests", "Ydb. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.Ado.Dapper.Tests", "Ydb.Sdk\test\Ydb.Sdk.Ado.Dapper.Tests\Ydb.Sdk.Ado.Dapper.Tests.csproj", "{AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.Ado.Benchmarks", "Ydb.Sdk\test\Ydb.Sdk.Ado.Benchmarks\Ydb.Sdk.Ado.Benchmarks.csproj", "{3C20B44A-9649-4927-9D4A-8605CC1D1271}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +71,10 @@ Global {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8}.Release|Any CPU.Build.0 = Release|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C20B44A-9649-4927-9D4A-8605CC1D1271}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -85,6 +91,7 @@ Global {5F30D97C-E6D2-451D-B2FB-F9F7BA052A52} = {316B82EF-019D-4267-95A9-5E243086B240} {0FF9D790-72A9-46DC-96DA-7EF1BE685A08} = {316B82EF-019D-4267-95A9-5E243086B240} {AABFEDAE-1AEE-4ACF-804F-7F3C4D610CD8} = {316B82EF-019D-4267-95A9-5E243086B240} + {3C20B44A-9649-4927-9D4A-8605CC1D1271} = {316B82EF-019D-4267-95A9-5E243086B240} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0AB27123-0C66-4E43-A75F-D9EAB9ED0849}