diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a642bc7e..aa912c91 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: strategy: fail-fast: false matrix: - ydb-version: [ 'trunk' ] + ydb-version: [ 'trunk', 'latest' ] dotnet-version: [ 6.0.x, 7.0.x ] include: - dotnet-version: 6.0.x diff --git a/src/Ydb.Sdk/src/Ado/YdbCommand.cs b/src/Ydb.Sdk/src/Ado/YdbCommand.cs index d6f40598..c208e57f 100644 --- a/src/Ydb.Sdk/src/Ado/YdbCommand.cs +++ b/src/Ydb.Sdk/src/Ado/YdbCommand.cs @@ -185,13 +185,14 @@ protected override async Task ExecuteDbDataReaderAsync(CommandBeha ? new ExecuteQuerySettings { TransportTimeout = TimeSpan.FromSeconds(CommandTimeout) } : new ExecuteQuerySettings(); + var transaction = YdbConnection.CurrentTransaction; + var ydbDataReader = await YdbDataReader.CreateYdbDataReader(YdbConnection.Session.ExecuteQuery( - preparedSql.ToString(), ydbParameters, execSettings, Transaction?.TransactionControl), - YdbConnection.Session.OnStatus, Transaction); + preparedSql.ToString(), ydbParameters, execSettings, transaction?.TransactionControl), + YdbConnection.Session.OnStatus, transaction); YdbConnection.LastReader = ydbDataReader; YdbConnection.LastCommand = CommandText; - YdbConnection.LastTransaction = Transaction; return ydbDataReader; } diff --git a/src/Ydb.Sdk/src/Ado/YdbConnection.cs b/src/Ydb.Sdk/src/Ado/YdbConnection.cs index 195e9343..d24a5e44 100644 --- a/src/Ydb.Sdk/src/Ado/YdbConnection.cs +++ b/src/Ydb.Sdk/src/Ado/YdbConnection.cs @@ -1,5 +1,6 @@ using System.Data; using System.Data.Common; +using System.Diagnostics.CodeAnalysis; using Ydb.Sdk.Services.Query; using static System.Data.IsolationLevel; @@ -7,8 +8,6 @@ namespace Ydb.Sdk.Ado; public sealed class YdbConnection : DbConnection { - private static readonly YdbConnectionStringBuilder DefaultSettings = new(); - private static readonly StateChangeEventArgs ClosedToOpenEventArgs = new(ConnectionState.Closed, ConnectionState.Open); @@ -16,8 +15,14 @@ public sealed class YdbConnection : DbConnection new(ConnectionState.Open, ConnectionState.Closed); private bool _disposed; + private YdbConnectionStringBuilder? _connectionStringBuilder; - private YdbConnectionStringBuilder ConnectionStringBuilder { get; set; } + private YdbConnectionStringBuilder ConnectionStringBuilder + { + get => _connectionStringBuilder ?? + throw new InvalidOperationException("The ConnectionString property has not been initialized."); + [param: AllowNull] init => _connectionStringBuilder = value; + } internal Session Session { @@ -34,7 +39,6 @@ internal Session Session public YdbConnection() { - ConnectionStringBuilder = DefaultSettings; } public YdbConnection(string connectionString) @@ -67,7 +71,16 @@ public YdbTransaction BeginTransaction(TxMode txMode = TxMode.SerializableRw) { EnsureConnectionOpen(); - return new YdbTransaction(this, txMode); + if (CurrentTransaction is { Completed: false }) + { + throw new InvalidOperationException( + "A transaction is already in progress; nested/concurrent transactions aren't supported." + ); + } + + CurrentTransaction = new YdbTransaction(this, txMode); + + return CurrentTransaction; } public override void ChangeDatabase(string databaseName) @@ -121,9 +134,9 @@ public override async Task CloseAsync() await LastReader.CloseAsync(); } - if (LastTransaction is { Completed: false }) + if (CurrentTransaction is { Completed: false }) { - await LastTransaction.RollbackAsync(); + await CurrentTransaction.RollbackAsync(); } OnStateChange(OpenToClosedEventArgs); @@ -138,7 +151,7 @@ public override async Task CloseAsync() public override string ConnectionString { - get => ConnectionStringBuilder.ConnectionString; + get => _connectionStringBuilder?.ConnectionString ?? string.Empty; #pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). set #pragma warning restore CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). @@ -146,7 +159,7 @@ public override string ConnectionString EnsureConnectionClosed(); // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - ConnectionStringBuilder = value != null ? new YdbConnectionStringBuilder(value) : DefaultSettings; + _connectionStringBuilder = value != null ? new YdbConnectionStringBuilder(value) : null; } } @@ -160,8 +173,8 @@ public override string ConnectionString internal YdbDataReader? LastReader { get; set; } internal string LastCommand { get; set; } = string.Empty; - internal YdbTransaction? LastTransaction { get; set; } internal bool IsBusy => LastReader is { IsClosed: false }; + internal YdbTransaction? CurrentTransaction { get; private set; } public override string DataSource => string.Empty; // TODO diff --git a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs index cf76ae72..c470ca38 100644 --- a/src/Ydb.Sdk/src/Ado/YdbDataReader.cs +++ b/src/Ydb.Sdk/src/Ado/YdbDataReader.cs @@ -434,9 +434,22 @@ public override IEnumerator GetEnumerator() public override async Task CloseAsync() { + if (ReaderState == State.Closed) + { + return; + } + ReaderState = State.Closed; + _onNotSuccessStatus(new Status(StatusCode.SessionBusy)); await _stream.DisposeAsync(); + + if (_ydbTransaction != null) + { + _ydbTransaction.Failed = true; + + throw new YdbException("YdbDataReader was closed during transaction execution. Transaction is broken!"); + } } public override void Close() @@ -497,7 +510,7 @@ private async Task NextExecPart() _currentResultSet = part.ResultSet?.FromProto(); ReaderMetadata = _currentResultSet != null ? new Metadata(_currentResultSet) : EmptyMetadata.Instance; - if (_ydbTransaction != null) + if (_ydbTransaction != null && part.TxMeta != null) { _ydbTransaction.TxId ??= part.TxMeta.Id; } diff --git a/src/Ydb.Sdk/src/Ado/YdbTransaction.cs b/src/Ydb.Sdk/src/Ado/YdbTransaction.cs index 8478e255..35259104 100644 --- a/src/Ydb.Sdk/src/Ado/YdbTransaction.cs +++ b/src/Ydb.Sdk/src/Ado/YdbTransaction.cs @@ -10,6 +10,8 @@ public sealed class YdbTransaction : DbTransaction private readonly TxMode _txMode; private bool _failed; + private YdbConnection? _ydbConnection; + private bool _isDisposed; internal string? TxId { get; set; } internal bool Completed { get; private set; } @@ -32,7 +34,7 @@ internal bool Failed internal YdbTransaction(YdbConnection ydbConnection, TxMode txMode) { - DbConnection = ydbConnection; + _ydbConnection = ydbConnection; _txMode = txMode; } @@ -44,7 +46,7 @@ public override void Commit() // TODO propagate cancellation token public override async Task CommitAsync(CancellationToken cancellationToken = new()) { - await FinishTransaction(txId => DbConnection.Session.CommitTransaction(txId)); + await FinishTransaction(txId => DbConnection!.Session.CommitTransaction(txId)); } public override void Rollback() @@ -62,10 +64,17 @@ public override void Rollback() return; } - await FinishTransaction(txId => DbConnection.Session.RollbackTransaction(txId)); + await FinishTransaction(txId => DbConnection!.Session.RollbackTransaction(txId)); } - protected override YdbConnection DbConnection { get; } + protected override YdbConnection? DbConnection + { + get + { + CheckDisposed(); + return _ydbConnection; + } + } public override IsolationLevel IsolationLevel => _txMode == TxMode.SerializableRw ? IsolationLevel.Serializable @@ -73,25 +82,25 @@ public override void Rollback() private async Task FinishTransaction(Func> finishMethod) { - if (Completed || DbConnection.State == ConnectionState.Closed) + if (DbConnection?.State == ConnectionState.Closed || Completed) { throw new InvalidOperationException("This YdbTransaction has completed; it is no longer usable"); } - if (DbConnection.IsBusy) + if (DbConnection!.IsBusy) { throw new YdbOperationInProgressException(DbConnection); } - Completed = true; - - if (TxId == null) - { - return; // transaction isn't started - } - try { + Completed = true; + + if (TxId == null) + { + return; // transaction isn't started + } + var status = await finishMethod(TxId); // Commit or Rollback if (status.IsNotSuccess) @@ -111,5 +120,43 @@ private async Task FinishTransaction(Func> finishMethod) throw new YdbException(e.Status); } + finally + { + _ydbConnection = null; + } + } + + protected override void Dispose(bool disposing) + { + if (_isDisposed || !disposing) + return; + + if (!Completed) + { + Rollback(); + } + + _isDisposed = true; + } + + public override async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + if (!Completed) + { + await RollbackAsync(); + } + + _isDisposed = true; + } + + private void CheckDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(YdbTransaction)); + } } } diff --git a/src/Ydb.Sdk/tests/Ado/Specification/YdbConnectionTests.cs b/src/Ydb.Sdk/tests/Ado/Specification/YdbConnectionTests.cs index 5fe7210b..b59228cb 100644 --- a/src/Ydb.Sdk/tests/Ado/Specification/YdbConnectionTests.cs +++ b/src/Ydb.Sdk/tests/Ado/Specification/YdbConnectionTests.cs @@ -25,14 +25,6 @@ public override Task DisposeAsync_raises_Disposed() return base.DisposeAsync_raises_Disposed(); } -#pragma warning disable xUnit1004 - [Fact(Skip = "Connect to default settings 'grpc://localhost:2136/local'.")] -#pragma warning restore xUnit1004 - public override void Open_throws_when_no_connection_string() - { - base.Open_throws_when_no_connection_string(); - } - #pragma warning disable xUnit1004 [Fact(Skip = "TODO Supported this field.")] #pragma warning restore xUnit1004 diff --git a/src/Ydb.Sdk/tests/Ado/Specification/YdbFactoryFixture.cs b/src/Ydb.Sdk/tests/Ado/Specification/YdbFactoryFixture.cs index 6b586788..8edef5ab 100644 --- a/src/Ydb.Sdk/tests/Ado/Specification/YdbFactoryFixture.cs +++ b/src/Ydb.Sdk/tests/Ado/Specification/YdbFactoryFixture.cs @@ -8,5 +8,5 @@ public class YdbFactoryFixture : IDbFactoryFixture { public DbProviderFactory Factory => YdbProviderFactory.Instance; - public string ConnectionString => "Host=localhost;Port=2136;Database=local"; + public string ConnectionString => "Host=localhost;Port=2136;Database=local;MaxSessionPool=10"; } diff --git a/src/Ydb.Sdk/tests/Ado/Specification/YdbTransactionTests.cs b/src/Ydb.Sdk/tests/Ado/Specification/YdbTransactionTests.cs new file mode 100644 index 00000000..3c1d795b --- /dev/null +++ b/src/Ydb.Sdk/tests/Ado/Specification/YdbTransactionTests.cs @@ -0,0 +1,10 @@ +using AdoNet.Specification.Tests; + +namespace Ydb.Sdk.Tests.Ado.Specification; + +public class YdbTransactionTests : TransactionTestBase +{ + public YdbTransactionTests(YdbFactoryFixture fixture) : base(fixture) + { + } +} diff --git a/src/Ydb.Sdk/tests/Ado/YdbAdoUserPasswordTests.cs b/src/Ydb.Sdk/tests/Ado/YdbAdoUserPasswordTests.cs index 6231a70c..51855563 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbAdoUserPasswordTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbAdoUserPasswordTests.cs @@ -1,32 +1,48 @@ using Xunit; using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; namespace Ydb.Sdk.Tests.Ado; -public class YdbAdoUserPasswordTests +public class YdbAdoUserPasswordTests : YdbAdoNetFixture { + public YdbAdoUserPasswordTests(YdbFactoryFixture fixture) : base(fixture) + { + } + [Fact] public async Task Authentication_WhenUserAndPassword_ReturnValidConnection() { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var ydbCommand = connection.CreateCommand(); var kurdyukovkirya = "kurdyukovkirya" + Random.Shared.Next(); ydbCommand.CommandText = $"CREATE USER {kurdyukovkirya} PASSWORD 'password'"; await ydbCommand.ExecuteNonQueryAsync(); await connection.CloseAsync(); - await using var userPasswordConnection = new YdbConnection($"User={kurdyukovkirya};Password=password;"); + await using var userPasswordConnection = + new YdbConnection($"{ConnectionString};User={kurdyukovkirya};Password=password;"); await userPasswordConnection.OpenAsync(); ydbCommand = userPasswordConnection.CreateCommand(); ydbCommand.CommandText = "SELECT 1 + 2"; Assert.Equal(3, await ydbCommand.ExecuteScalarAsync()); - await using var newConnection = new YdbConnection(); - await newConnection.OpenAsync(); + await using var newConnection = await CreateOpenConnectionAsync(); ydbCommand = newConnection.CreateCommand(); ydbCommand.CommandText = $"DROP USER {kurdyukovkirya};"; await ydbCommand.ExecuteNonQueryAsync(); } + + [Fact] + public async Task ExecuteNonQueryAsync_WhenCreateUser_ReturnEmptyResultSet() + { + await using var connection = await CreateOpenConnectionAsync(); + var dbCommand = connection.CreateCommand(); + var user = "user" + Random.Shared.Next(); + dbCommand.CommandText = $"CREATE USER {user} PASSWORD '123qweqwe'"; + await dbCommand.ExecuteNonQueryAsync(); + dbCommand.CommandText = $"DROP USER {user};"; + await dbCommand.ExecuteNonQueryAsync(); + } } diff --git a/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs b/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs index 325dd714..06e2d1b0 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbCommandTests.cs @@ -2,26 +2,29 @@ using System.Text; using Xunit; using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; using Ydb.Sdk.Value; namespace Ydb.Sdk.Tests.Ado; -public class YdbCommandTests +public class YdbCommandTests : YdbAdoNetFixture { + public YdbCommandTests(YdbFactoryFixture fixture) : base(fixture) + { + } + [Theory] [ClassData(typeof(YdbParameterTests.TestDataGenerator))] public async Task ExecuteScalarAsync_WhenSetYdbParameter_ReturnThisValue(YdbParameterTests.Data data) { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var dbCommand = connection.CreateCommand(); - - dbCommand.CommandText = "SELECT $var as var;"; + dbCommand.CommandText = "SELECT @var as var;"; var dbParameter = new YdbParameter { - ParameterName = "$var", + ParameterName = "var", DbType = data.DbType, Value = data.Expected, IsNullable = data.IsNullable @@ -38,7 +41,7 @@ public async Task ExecuteScalarAsync_WhenSetYdbParameter_ReturnThisValue(YdbP Assert.Equal(typeof(T), ydbDataReader.GetFieldType(0)); } - while (ydbDataReader.Read()) + while (await ydbDataReader.NextResultAsync()) { } } @@ -48,11 +51,8 @@ public async Task ExecuteScalarAsync_WhenSetYdbParameter_ReturnThisValue(YdbP public async Task ExecuteScalarAsync_WhenSetYdbParameterThenPrepare_ReturnThisValue( YdbParameterTests.Data data) { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var dbCommand = connection.CreateCommand(); - dbCommand.CommandText = "SELECT @var;"; var dbParameter = new YdbParameter @@ -76,11 +76,8 @@ public async Task ExecuteScalarAsync_WhenDbTypeIsObject_ReturnThisValue(YdbPa return; } - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var dbCommand = connection.CreateCommand(); - dbCommand.CommandText = "SELECT @var;"; var dbParameter = new YdbParameter @@ -112,9 +109,7 @@ public async Task ExecuteScalarAsync_WhenNoDbTypeParameter_ReturnThisValue() Encoding.ASCII.GetBytes("{type=\"yson\"}")) }; - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var dbCommand = connection.CreateCommand(); dbCommand.CommandText = "SELECT @var;"; @@ -130,27 +125,10 @@ public async Task ExecuteScalarAsync_WhenNoDbTypeParameter_ReturnThisValue() } } - [Fact] - public async Task ExecuteNonQueryAsync_WhenCreateUser_ReturnEmptyResultSet() - { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - - var dbCommand = connection.CreateCommand(); - dbCommand.CommandText = "CREATE USER user PASSWORD '123qweqwe'"; - - await dbCommand.ExecuteNonQueryAsync(); - - dbCommand.CommandText = "DROP USER user;"; - await dbCommand.ExecuteNonQueryAsync(); - } - [Fact] public async Task CloseAsync_WhenDoubleInvoke_Idempotent() { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var ydbCommand = connection.CreateCommand(); ydbCommand.CommandText = "SELECT 1;"; var ydbDataReader = await ydbCommand.ExecuteReaderAsync(); @@ -165,19 +143,20 @@ public async Task CloseAsync_WhenDoubleInvoke_Idempotent() [Fact] public async Task ExecuteDbDataReader_WhenSelectManyResultSet_ReturnYdbDataReader() { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var dbCommand = connection.CreateCommand(); dbCommand.CommandText = @" +DECLARE $var1 AS Datetime; +DECLARE $var2 AS Timestamp; + SELECT 1 as a, CAST('text' AS Text) as b; $data = ListReplicate(AsStruct(true AS bool_field, 1.5 AS double_field, 23 AS int_field), 1500); SELECT bool_field, double_field, int_field FROM AS_TABLE($data); -SELECT CAST(NULL AS Int8) AS null_field; - +SELECT CAST(NULL AS Int8) AS null_field; + $new_data = AsList( AsStruct($var1 AS Key, $var2 AS Value), AsStruct($var1 AS Key, $var2 AS Value) @@ -258,11 +237,8 @@ public async Task ExecuteDbDataReader_WhenSelectManyResultSet_ReturnYdbDataReade [Fact] public void CommandTimeout_WhenCommandTimeoutLessZero_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); - + using var connection = CreateOpenConnection(); var dbCommand = connection.CreateCommand(); - Assert.Equal("CommandTimeout can't be less than zero. (Parameter 'value')\nActual value was -1.", Assert.Throws(() => dbCommand.CommandTimeout = -1).Message); } @@ -270,13 +246,9 @@ public void CommandTimeout_WhenCommandTimeoutLessZero_ThrowException() [Fact] public void ExecuteDbDataReader_WhenPreviousIsNotClosed_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); - + using var connection = CreateOpenConnection(); var dbCommand = connection.CreateCommand(); - dbCommand.CommandText = "SELECT 1; SELECT 1;"; - var ydbDataReader = dbCommand.ExecuteReader(); Assert.Equal("A command is already in progress: SELECT 1; SELECT 1;", @@ -292,8 +264,7 @@ public void ExecuteDbDataReader_WhenPreviousIsNotClosed_ThrowException() [Fact] public void GetChars_WhenSelectText_MoveCharsToBuffer() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbDataReader = new YdbCommand(connection) { CommandText = "SELECT CAST('abacaba' AS Text)" }.ExecuteReader(); Assert.True(ydbDataReader.Read()); @@ -342,8 +313,7 @@ public void GetChars_WhenSelectText_MoveCharsToBuffer() [Fact] public void GetBytes_WhenSelectBytes_MoveBytesToBuffer() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbDataReader = new YdbCommand(connection) { CommandText = "SELECT 'abacaba'" }.ExecuteReader(); Assert.True(ydbDataReader.Read()); var bufferChars = new byte[10]; @@ -389,8 +359,7 @@ public void GetBytes_WhenSelectBytes_MoveBytesToBuffer() [Fact] public async Task GetEnumerator_WhenReadMultiSelect_ReadFirstResultSet() { - await using var ydbConnection = new YdbConnection(); - ydbConnection.Open(); + await using var ydbConnection = await CreateOpenConnectionAsync(); var ydbCommand = new YdbCommand(ydbConnection) { CommandText = @" @@ -432,9 +401,7 @@ public async Task GetEnumerator_WhenReadMultiSelect_ReadFirstResultSet() [Fact] public async Task ExecuteScalar_WhenSelectNull_ReturnNull() { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); - + await using var ydbConnection = await CreateOpenConnectionAsync(); Assert.Null(await new YdbCommand(ydbConnection) { CommandText = "SELECT NULL" }.ExecuteScalarAsync()); } @@ -444,9 +411,7 @@ public async Task ExecuteScalar_WhenSelectNull_ReturnNull() [InlineData("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B")] public async Task Guid_WhenSelectUuid_ReturnThisUuid(string guid) { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); - + await using var ydbConnection = await CreateOpenConnectionAsync(); var actualGuid = await new YdbCommand(ydbConnection) { CommandText = $"SELECT CAST('{guid}' AS UUID);" } .ExecuteScalarAsync(); @@ -461,9 +426,7 @@ public async Task Guid_WhenSelectUuid_ReturnThisUuid(string guid) [InlineData("6E73B41C-4EDE-4D08-9CFB-B7462D9E498B")] public async Task Guid_WhenSetUuid_ReturnThisUtf8Uuid(string guid) { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); - + await using var ydbConnection = await CreateOpenConnectionAsync(); var ydbCommand = new YdbCommand(ydbConnection) { CommandText = "SELECT CAST(@guid AS Text);" diff --git a/src/Ydb.Sdk/tests/Ado/YdbConnectionTests.cs b/src/Ydb.Sdk/tests/Ado/YdbConnectionTests.cs index 16f6d6ec..c087ccb0 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbConnectionTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbConnectionTests.cs @@ -1,33 +1,37 @@ using System.Data; using Xunit; using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; using Ydb.Sdk.Value; namespace Ydb.Sdk.Tests.Ado; -[Collection("YdbConnectionTests")] -public class YdbConnectionTests +public sealed class YdbConnectionTests : YdbAdoNetFixture { private static readonly TemporaryTables Tables = new(); + private readonly string _connectionString; + private volatile int _counter; + public YdbConnectionTests(YdbFactoryFixture fixture) : base(fixture) + { + _connectionString = "Host=localhost;Port=2135;Database=/local;MaxSessionPool=10;RootCertificate=" + + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "ca.pem"); + } + [Fact] public async Task ClearPool_WhenHasActiveConnection_CloseActiveConnectionOnClose() { var tasks = GenerateTasks(); - - tasks.Add(YdbConnection.ClearPool(new YdbConnection("MaxSessionPool=10"))); - + tasks.Add(YdbConnection.ClearPool(new YdbConnection(ConnectionString))); tasks.AddRange(GenerateTasks()); - await Task.WhenAll(tasks); Assert.Equal(9900, _counter); tasks = GenerateTasks(); - - tasks.Add(YdbConnection.ClearPool(new YdbConnection("MaxSessionPool=10"))); - + tasks.Add(YdbConnection.ClearPool(new YdbConnection(ConnectionString))); await Task.WhenAll(tasks); Assert.Equal(14850, _counter); } @@ -36,18 +40,13 @@ public async Task ClearPool_WhenHasActiveConnection_CloseActiveConnectionOnClose public async Task ClearPoolAllPools_WhenHasActiveConnection_CloseActiveConnectionOnClose() { var tasks = GenerateTasks(); - tasks.Add(YdbConnection.ClearAllPools()); - tasks.AddRange(GenerateTasks()); - await Task.WhenAll(tasks); Assert.Equal(9900, _counter); tasks = GenerateTasks(); - tasks.Add(YdbConnection.ClearAllPools()); - await Task.WhenAll(tasks); Assert.Equal(14850, _counter); } @@ -56,19 +55,13 @@ public async Task ClearPoolAllPools_WhenHasActiveConnection_CloseActiveConnectio [Fact] public async Task TlsSettings_WhenUseGrpcs_ReturnValidConnection() { - await using var ydbConnection = new YdbConnection( - "Host=localhost;Port=2135;MaxSessionPool=10;RootCertificate=" + - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "ca.pem")); - + await using var ydbConnection = new YdbConnection(_connectionString); await ydbConnection.OpenAsync(); - var command = ydbConnection.CreateCommand(); command.CommandText = Tables.CreateTables; await command.ExecuteNonQueryAsync(); - command.CommandText = Tables.UpsertData; await command.ExecuteNonQueryAsync(); - command.CommandText = Tables.DeleteTables; await command.ExecuteNonQueryAsync(); } @@ -76,9 +69,7 @@ public async Task TlsSettings_WhenUseGrpcs_ReturnValidConnection() [Fact] public async Task Open_WhenConnectionIsOpen_ThrowException() { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); - + await using var ydbConnection = await CreateOpenConnectionAsync(); Assert.Equal("Connection already open", Assert.Throws(() => ydbConnection.Open()).Message); } @@ -86,9 +77,7 @@ public async Task Open_WhenConnectionIsOpen_ThrowException() [Fact] public async Task SetConnectionString_WhenConnectionIsOpen_ThrowException() { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); - + await using var ydbConnection = await CreateOpenConnectionAsync(); Assert.Equal("Connection already open", Assert.Throws(() => ydbConnection.ConnectionString = "UseTls=false").Message); } @@ -96,10 +85,8 @@ public async Task SetConnectionString_WhenConnectionIsOpen_ThrowException() [Fact] public async Task BeginTransaction_WhenConnectionIsClosed_ThrowException() { - var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + var ydbConnection = await CreateOpenConnectionAsync(); await ydbConnection.CloseAsync(); - Assert.Equal("Connection is closed", Assert.Throws(() => ydbConnection.BeginTransaction()).Message); } @@ -107,8 +94,7 @@ public async Task BeginTransaction_WhenConnectionIsClosed_ThrowException() [Fact] public async Task ExecuteScalar_WhenConnectionIsClosed_ThrowException() { - var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + var ydbConnection = await CreateOpenConnectionAsync(); await ydbConnection.CloseAsync(); var ydbCommand = ydbConnection.CreateCommand(); @@ -121,8 +107,7 @@ public async Task ExecuteScalar_WhenConnectionIsClosed_ThrowException() [Fact] public async Task ClosedYdbDataReader_WhenConnectionIsClosed_ThrowException() { - var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + var ydbConnection = await CreateOpenConnectionAsync(); var ydbCommand = ydbConnection.CreateCommand(); ydbCommand.CommandText = "SELECT 1; SELECT 2; SELECT 3;"; @@ -137,8 +122,7 @@ public async Task ClosedYdbDataReader_WhenConnectionIsClosed_ThrowException() [Fact] public async Task SetNulls_WhenTableAllTypes_SussesSet() { - var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + var ydbConnection = await CreateOpenConnectionAsync(); var ydbCommand = ydbConnection.CreateCommand(); var tableName = "AllTypes_" + Random.Shared.Next(); @@ -173,34 +157,34 @@ INSERT INTO {tableName} (id, bool_column, bigint_column, smallint_column, tinyint_column, float_column, double_column, decimal_column, uint8_column, uint16_column, uint32_column, uint64_column, text_column, binary_column, json_column, jsondocument_column, date_column, datetime_column, timestamp_column, interval_column) VALUES -($name1, $name2, $name3, $name4, $name5, $name6, $name7, $name8, $name9, $name10, $name11, $name12, $name13, $name14, - $name15, $name16, $name17, $name18, $name19, $name20); +(@name1, @name2, @name3, @name4, @name5, @name6, @name7, @name8, @name9, @name10, @name11, @name12, @name13, @name14, + @name15, @name16, @name17, @name18, @name19, @name20); "; - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name1", DbType = DbType.Int32, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name2", DbType = DbType.Boolean, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name3", DbType = DbType.Int64, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name4", DbType = DbType.Int16, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name5", DbType = DbType.SByte, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name6", DbType = DbType.Single, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name7", DbType = DbType.Double, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name8", DbType = DbType.Decimal, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name9", DbType = DbType.Byte, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name10", DbType = DbType.UInt16, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name11", DbType = DbType.UInt32, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name12", DbType = DbType.UInt64, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name13", DbType = DbType.String, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name14", DbType = DbType.Binary, Value = null }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name15", Value = YdbValue.MakeOptionalJson() }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name1", DbType = DbType.Int32, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name2", DbType = DbType.Boolean, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name3", DbType = DbType.Int64, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name4", DbType = DbType.Int16, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name5", DbType = DbType.SByte, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name6", DbType = DbType.Single, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name7", DbType = DbType.Double, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name8", DbType = DbType.Decimal, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name9", DbType = DbType.Byte, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name10", DbType = DbType.UInt16, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name11", DbType = DbType.UInt32, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name12", DbType = DbType.UInt64, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name13", DbType = DbType.String, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name14", DbType = DbType.Binary, Value = null }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name15", Value = YdbValue.MakeOptionalJson() }); ydbCommand.Parameters.Add(new YdbParameter - { ParameterName = "$name16", Value = YdbValue.MakeOptionalJsonDocument() }); - ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$name17", DbType = DbType.Date, Value = null }); + { ParameterName = "name16", Value = YdbValue.MakeOptionalJsonDocument() }); + ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "name17", DbType = DbType.Date, Value = null }); ydbCommand.Parameters.Add( - new YdbParameter { ParameterName = "$name18", DbType = DbType.DateTime, Value = null }); + new YdbParameter { ParameterName = "name18", DbType = DbType.DateTime, Value = null }); ydbCommand.Parameters.Add(new YdbParameter - { ParameterName = "$name19", DbType = DbType.DateTime2, Value = null }); + { ParameterName = "name19", DbType = DbType.DateTime2, Value = null }); ydbCommand.Parameters.Add(new YdbParameter - { ParameterName = "$name20", Value = YdbValue.MakeOptionalInterval() }); + { ParameterName = "name20", Value = YdbValue.MakeOptionalInterval() }); await ydbCommand.ExecuteNonQueryAsync(); ydbCommand.CommandText = $"SELECT NULL, t.* FROM {tableName} t"; @@ -221,16 +205,17 @@ private List GenerateTasks() { return Enumerable.Range(0, 100).Select(async i => { - await using var connection = new YdbConnection("MaxSessionPool=10"); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); var command = connection.CreateCommand(); command.CommandText = "SELECT " + i; - var scalar = (int)(await command.ExecuteScalarAsync())!; Assert.Equal(i, scalar); - Interlocked.Add(ref _counter, scalar); }).ToList(); } + + protected override async Task OnDisposeAsync() + { + await YdbConnection.ClearPool(new YdbConnection(_connectionString)); + } } diff --git a/src/Ydb.Sdk/tests/Ado/YdbDataSourceTests.cs b/src/Ydb.Sdk/tests/Ado/YdbDataSourceTests.cs index ac83a4f6..7183e82f 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbDataSourceTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbDataSourceTests.cs @@ -1,14 +1,21 @@ #if NET7_0_OR_GREATER using Xunit; using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; namespace Ydb.Sdk.Tests.Ado; -public class YdbDataSourceTests +public class YdbDataSourceTests : YdbAdoNetFixture { private const int SelectedCount = 100; - private readonly YdbDataSource _dataSource = new("MaxSessionPool=10"); + private readonly YdbDataSource _dataSource; + + public YdbDataSourceTests(YdbFactoryFixture fixture) : base(fixture) + { + _dataSource = new YdbDataSource(Fixture.ConnectionString); + } [Fact] public async Task OpenConnectionAsync_WhenMaxSessionPool10_ReturnOpenConnection() @@ -32,13 +39,15 @@ public void CreateCommand_FromDataSource_ReturnDbCommand() { for (var i = 0; i < SelectedCount; i++) { - Assert.Equal(1, _dataSource.CreateCommand("SELECT 1;").ExecuteScalar()); + using var command = _dataSource.CreateCommand("SELECT 1;"); + Assert.Equal(1, command.ExecuteScalar()); } _dataSource.Dispose(); for (var i = 0; i < SelectedCount; i++) { - Assert.Equal(1, _dataSource.CreateCommand("SELECT 1;").ExecuteScalar()); + using var command = _dataSource.CreateCommand("SELECT 1;"); + Assert.Equal(1, command.ExecuteScalar()); } } diff --git a/src/Ydb.Sdk/tests/Ado/YdbExceptionTests.cs b/src/Ydb.Sdk/tests/Ado/YdbExceptionTests.cs index e58a79d0..f77284bd 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbExceptionTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbExceptionTests.cs @@ -1,16 +1,21 @@ using System.Data; using Xunit; using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; namespace Ydb.Sdk.Tests.Ado; -public class YdbExceptionTests +public class YdbExceptionTests : YdbAdoNetFixture { + public YdbExceptionTests(YdbFactoryFixture fixture) : base(fixture) + { + } + [Fact] public async Task IsTransient_WhenSchemaError_ReturnFalse() { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + await using var ydbConnection = await CreateOpenConnectionAsync(); var ydbCommand = new YdbCommand(ydbConnection) { CommandText = "CREATE TABLE A(text Text)" @@ -23,8 +28,7 @@ public async Task IsTransient_WhenSchemaError_ReturnFalse() public async Task IsTransient_WhenAborted_ReturnTrueAndMakeEmptyRollback() { var bankTable = $"Bank_{Utils.Net}"; - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + await using var ydbConnection = await CreateOpenConnectionAsync(); var ydbCommand = new YdbCommand(ydbConnection) { CommandText = $"CREATE TABLE {bankTable}(id Int32, amount Int32, PRIMARY KEY (id))" @@ -38,15 +42,14 @@ public async Task IsTransient_WhenAborted_ReturnTrueAndMakeEmptyRollback() var select = (int)(await ydbCommand.ExecuteScalarAsync())!; Assert.Equal(100, select); - await using var anotherConnection = new YdbConnection(); - await anotherConnection.OpenAsync(); + await using var anotherConnection = await CreateOpenConnectionAsync(); await new YdbCommand(anotherConnection) { CommandText = $"UPDATE {bankTable} SET amount = amount + 50 WHERE id = 1" }.ExecuteNonQueryAsync(); - ydbCommand.CommandText = $"UPDATE {bankTable} SET amount = amount + $var WHERE id = 1"; - ydbCommand.Parameters.AddWithValue("$var", DbType.Int32, select); + ydbCommand.CommandText = $"UPDATE {bankTable} SET amount = amount + @var WHERE id = 1"; + ydbCommand.Parameters.AddWithValue("var", DbType.Int32, select); Assert.True(Assert.Throws(() => { ydbCommand.ExecuteNonQuery(); diff --git a/src/Ydb.Sdk/tests/Ado/YdbSchemaTests.cs b/src/Ydb.Sdk/tests/Ado/YdbSchemaTests.cs index b14316dd..90c7487f 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbSchemaTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbSchemaTests.cs @@ -1,16 +1,21 @@ using System.Data; using Xunit; using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; namespace Ydb.Sdk.Tests.Ado; -public class YdbSchemaTests +public class YdbSchemaTests : YdbAdoNetFixture { + public YdbSchemaTests(YdbFactoryFixture fixture) : base(fixture) + { + } + [Fact] public async Task GetSchema_WhenTablesCollection_ReturnAllTables() { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + await using var ydbConnection = await CreateOpenConnectionAsync(); var table1 = $"a/b/{Utils.Net}"; var table2 = $"a/{Utils.Net}"; @@ -62,8 +67,7 @@ public async Task GetSchema_WhenTablesCollection_ReturnAllTables() [Fact] public async Task GetSchema_WhenTablesWithStatsCollection_ReturnAllTables() { - await using var ydbConnection = new YdbConnection(); - await ydbConnection.OpenAsync(); + await using var ydbConnection = await CreateOpenConnectionAsync(); var table1 = $"a/b/{Utils.Net}_for_stats"; var table2 = $"a/{Utils.Net}_for_stats"; diff --git a/src/Ydb.Sdk/tests/Ado/YdbTransactionTests.cs b/src/Ydb.Sdk/tests/Ado/YdbTransactionTests.cs index 5036e3a4..aa3b9031 100644 --- a/src/Ydb.Sdk/tests/Ado/YdbTransactionTests.cs +++ b/src/Ydb.Sdk/tests/Ado/YdbTransactionTests.cs @@ -2,18 +2,23 @@ using Xunit; using Ydb.Sdk.Ado; using Ydb.Sdk.Services.Query; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; namespace Ydb.Sdk.Tests.Ado; -public class YdbTransactionTests : IAsyncLifetime +public class YdbTransactionTests : YdbAdoNetFixture { private static readonly TemporaryTables Tables = new(); + public YdbTransactionTests(YdbFactoryFixture fixture) : base(fixture) + { + } + [Fact] public void Rollback_WhenUpsertThenRollback_ReturnPrevRow() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(); var ydbCommand = connection.CreateCommand(); @@ -21,7 +26,7 @@ public void Rollback_WhenUpsertThenRollback_ReturnPrevRow() ydbCommand.CommandText = $@" UPSERT INTO {Tables.Seasons} (series_id, season_id, first_aired) - VALUES ($series_id, $season_id, $air_date); + VALUES (@series_id, @season_id, @air_date); "; ydbCommand.Parameters.Add(new YdbParameter { ParameterName = "$series_id", DbType = DbType.UInt64, Value = 1U }); @@ -40,8 +45,7 @@ public void Rollback_WhenUpsertThenRollback_ReturnPrevRow() [Fact] public void Commit_WhenMakeTwoUpsertOperation_ReturnUpdatedTables() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(IsolationLevel.Serializable); var ydbCommand = connection.CreateCommand(); @@ -81,13 +85,11 @@ public void Commit_WhenMakeTwoUpsertOperation_ReturnUpdatedTables() [Fact] public void Commit_WhenEmptyYdbCommand_DoNothing() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(); var ydbCommand = connection.CreateCommand(); ydbCommand.Transaction = ydbTransaction; - ydbTransaction.Commit(); // Do nothing // Out transaction executing @@ -98,8 +100,7 @@ public void Commit_WhenEmptyYdbCommand_DoNothing() [Fact] public void CommitAndRollback_WhenDoubleCommit_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(); @@ -120,11 +121,55 @@ public void CommitAndRollback_WhenDoubleCommit_ThrowException() Assert.Throws(() => ydbTransaction.Rollback()).Message); } + [Fact] + public void BeginTransaction_WhenYdbDataReaderIsClosed_ThrowExceptionTransactionIsBroken() + { + using var connection = CreateOpenConnection(); + + using var ydbTransaction = connection.BeginTransaction(); + var ydbCommand = connection.CreateCommand(); + ydbCommand.CommandText = "SELECT 1; SELECT 2; SELECT 3"; + var dbDataReader = ydbCommand.ExecuteReader(); + dbDataReader.Read(); + Assert.Equal("YdbDataReader was closed during transaction execution. Transaction is broken!", + Assert.Throws(() => dbDataReader.Close()).Message); + Assert.Equal("This YdbTransaction has completed; it is no longer usable", + Assert.Throws(() => ydbTransaction.Commit()).Message); + ydbTransaction.Rollback(); + Assert.Equal("This YdbTransaction has completed; it is no longer usable", + Assert.Throws(() => ydbTransaction.Commit()).Message); + Assert.Equal("This YdbTransaction has completed; it is no longer usable", + Assert.Throws(() => ydbTransaction.Rollback()).Message); + } + + [Fact] + public async Task BeginTransaction_WhenTxIdIsReceivedThenYdbDataReaderIsClosed_SuccessCommit() + { + await using var connection = await CreateOpenConnectionAsync(); + + var tx = connection.BeginTransaction(); + var ydbCommand1 = connection.CreateCommand(); + ydbCommand1.CommandText = "SELECT 1"; + Assert.Equal(1, await ydbCommand1.ExecuteScalarAsync()); + var ydbCommand2 = connection.CreateCommand(); + ydbCommand2.CommandText = "SELECT 1; SELECT 2; SELECT 3"; + var dbDataReader = await ydbCommand2.ExecuteReaderAsync(); + await dbDataReader.NextResultAsync(); + Assert.Equal("YdbDataReader was closed during transaction execution. Transaction is broken!", + (await Assert.ThrowsAsync(() => dbDataReader.CloseAsync())).Message); + Assert.Equal("This YdbTransaction has completed; it is no longer usable", + (await Assert.ThrowsAsync(() => tx.CommitAsync())).Message); + await tx.RollbackAsync(); // do nothing transaction + Assert.Equal("This YdbTransaction has completed; it is no longer usable", + (await Assert.ThrowsAsync(() => tx.CommitAsync())).Message); + Assert.Equal("This YdbTransaction has completed; it is no longer usable", + (await Assert.ThrowsAsync(() => tx.RollbackAsync())).Message); + } + [Fact] public void CommitAndRollback_WhenStreamIsOpened_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(); var ydbCommand = connection.CreateCommand(); @@ -151,8 +196,7 @@ public void CommitAndRollback_WhenStreamIsOpened_ThrowException() [Fact] public void CommitAndRollback_WhenConnectionIsClosed_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(); var ydbCommand = connection.CreateCommand(); @@ -171,8 +215,7 @@ public void CommitAndRollback_WhenConnectionIsClosed_ThrowException() [Fact] public void CommitAndRollback_WhenConnectionIsClosedAndTxDoesNotStarted_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbTransaction = connection.BeginTransaction(); connection.Close(); @@ -186,8 +229,7 @@ public void CommitAndRollback_WhenConnectionIsClosedAndTxDoesNotStarted_ThrowExc [Fact] public void CommitAndRollback_WhenTransactionIsFailed_ThrowException() { - using var connection = new YdbConnection(); - connection.Open(); + using var connection = CreateOpenConnection(); var ydbCommand = connection.CreateCommand(); ydbCommand.Transaction = connection.BeginTransaction(); @@ -204,10 +246,9 @@ public void CommitAndRollback_WhenTransactionIsFailed_ThrowException() Assert.Throws(() => ydbCommand.Transaction.Rollback()).Message); } - public async Task InitializeAsync() + protected override async Task OnInitializeAsync() { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); + await using var connection = await CreateOpenConnectionAsync(); var ydbCommand = connection.CreateCommand(); ydbCommand.CommandText = Tables.CreateTables; await ydbCommand.ExecuteNonQueryAsync(); @@ -215,10 +256,9 @@ public async Task InitializeAsync() await ydbCommand.ExecuteNonQueryAsync(); } - public async Task DisposeAsync() + protected override async Task OnDisposeAsync() { - await using var connection = new YdbConnection(); - await connection.OpenAsync(); + await using var connection = await CreateOpenConnectionAsync(); var ydbCommand = connection.CreateCommand(); ydbCommand.CommandText = Tables.DeleteTables; await ydbCommand.ExecuteNonQueryAsync(); diff --git a/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs b/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs index 42b88a43..d4ffeaf5 100644 --- a/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs +++ b/src/Ydb.Sdk/tests/Dapper/DapperIntegrationTests.cs @@ -2,14 +2,19 @@ using System.Data; using Dapper; using Xunit; -using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; +using Ydb.Sdk.Tests.Fixture; namespace Ydb.Sdk.Tests.Dapper; -public class DapperIntegrationTests +public class DapperIntegrationTests : YdbAdoNetFixture { private static readonly TemporaryTables Tables = new(); + public DapperIntegrationTests(YdbFactoryFixture fixture) : base(fixture) + { + } + [Fact] public async Task DapperYqlTutorialTests() { @@ -23,12 +28,9 @@ public async Task DapperYqlTutorialTests() .OfType() .Any(attr => attr.Name == columnName)) ?? throw new InvalidOperationException())); - await using var connection = new YdbConnection(); - await connection.OpenAsync(); - + await using var connection = await CreateOpenConnectionAsync(); await connection.ExecuteAsync(Tables.CreateTables); // create tables await connection.ExecuteAsync(Tables.UpsertData); // adding data to table - var selectedEpisodes = (await connection.QueryAsync($@" SELECT series_id, @@ -131,10 +133,8 @@ UPSERT INTO {Tables.Episodes} @air_date ); ;", parameters1, transaction); - await using (var otherConn = new YdbConnection()) + await using (var otherConn = await CreateOpenConnectionAsync()) { - await otherConn.OpenAsync(); - Assert.Null(await otherConn.QuerySingleOrDefaultAsync( $"SELECT * FROM {Tables.Episodes} WHERE series_id = @p1 AND season_id = @p2 AND episode_id = @p3", new { p1 = episode1.SeriesId, p2 = episode1.SeasonId, p3 = episode1.EpisodeId })); @@ -206,7 +206,7 @@ public async Task NullableFieldSupported() { var tableName = "DapperNullableTypes_" + Random.Shared.Next(); - await using var connection = new YdbConnection(); + await using var connection = await CreateOpenConnectionAsync(); await connection.ExecuteAsync(@$" CREATE TABLE {tableName} ( Id INT32, diff --git a/src/Ydb.Sdk/tests/Fixture/YdbAdoNetFixture.cs b/src/Ydb.Sdk/tests/Fixture/YdbAdoNetFixture.cs new file mode 100644 index 00000000..550ca74d --- /dev/null +++ b/src/Ydb.Sdk/tests/Fixture/YdbAdoNetFixture.cs @@ -0,0 +1,30 @@ +using AdoNet.Specification.Tests; +using Ydb.Sdk.Ado; +using Ydb.Sdk.Tests.Ado.Specification; + +namespace Ydb.Sdk.Tests.Fixture; + +public class YdbAdoNetFixture : DbFactoryTestBase +{ + protected YdbAdoNetFixture(YdbFactoryFixture fixture) : base(fixture) + { + } + + protected override YdbConnection CreateConnection() + { + return (YdbConnection)base.CreateConnection(); + } + + protected override YdbConnection CreateOpenConnection() + { + return (YdbConnection)base.CreateOpenConnection(); + } + + protected async Task CreateOpenConnectionAsync() + { + var connection = new YdbConnection(new YdbConnectionStringBuilder(ConnectionString) + { LoggerFactory = Utils.GetLoggerFactory() }); + await connection.OpenAsync(); + return connection; + } +} diff --git a/src/Ydb.Sdk/tests/Tests.csproj b/src/Ydb.Sdk/tests/Tests.csproj index 78a99fea..e893debd 100644 --- a/src/Ydb.Sdk/tests/Tests.csproj +++ b/src/Ydb.Sdk/tests/Tests.csproj @@ -21,8 +21,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive