public async Task ReadWriteTransaction_QueryFullyConsumed_AndThenError_FailsRetry()
        {
            string sql = $"SELECT Id FROM Foo WHERE Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.Int64
            }, "Id", 1));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            var cmd = connection.CreateSelectCommand(sql);

            cmd.Transaction = transaction;
            using (var reader = await cmd.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    Assert.Equal(1, reader.GetInt64(reader.GetOrdinal("Id")));
                }
            }
            // Replace the result returned by the query on the server with an error and abort the transaction.
            // The retry should now fail.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateException(new RpcException(new Status(StatusCode.NotFound, "Table not found: Foo"))));
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => transaction.CommitAsync());

            Assert.Equal(1, transaction.RetryCount);
        }
        public async Task ReadWriteTransaction_BatchDmlWithSameExceptionHalfwayAndDifferentResults_FailsRetry()
        {
            string sql1 = $"UPDATE Foo SET Bar='valid' WHERE Id={_fixture.RandomLong()}";
            string sql2 = $"UPDATE Foo SET Bar='invalid' Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql1, StatementResult.CreateUpdateCount(1));
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql2, StatementResult.CreateException(new RpcException(new Status(StatusCode.FailedPrecondition, "UPDATE statement misses WHERE clause"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            var cmd = connection.CreateBatchDmlCommand();

            cmd.Transaction = transaction;
            cmd.Add(sql1);
            cmd.Add(sql2);
            var e = await Assert.ThrowsAsync <SpannerBatchNonQueryException>(() => cmd.ExecuteNonQueryAsync());

            Assert.Contains("UPDATE statement misses WHERE clause", e.Message);
            Assert.Equal(new List <long> {
                1
            }, e.SuccessfulCommandResults);

            // Change the result of the first statement and abort the transaction.
            // The retry should now fail, even though the error code and message is the same.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql1, StatementResult.CreateUpdateCount(2));
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => transaction.CommitAsync());

            Assert.Equal(1, transaction.RetryCount);
        }
        public async Task ReadWriteTransaction_QueryWithError_AndThenNoError_FailsRetry()
        {
            string sql = $"SELECT Id FROM Foo WHERE Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateException(new RpcException(new Status(StatusCode.NotFound, "Table not found: Foo"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            var cmd = connection.CreateSelectCommand(sql);

            cmd.Transaction = transaction;
            using (var reader = await cmd.ExecuteReaderAsync())
            {
                // Any query error is thrown by the first call to reader.ReadAsync();
                var e = await Assert.ThrowsAsync <SpannerException>(() => reader.ReadAsync());

                Assert.Contains("Table not found: Foo", e.InnerException.Message);
            }
            // Remove the error returned by the query on the server and abort the transaction.
            // The retry should now fail.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.Int64
            }, "Id", 1));
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => transaction.CommitAsync());

            Assert.Equal(1, transaction.RetryCount);
        }
        public async Task ReadWriteTransaction_BatchDmlWithDifferentException_FailsRetry()
        {
            string sql1 = $"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql1, StatementResult.CreateException(new RpcException(new Status(StatusCode.AlreadyExists, "Unique key constraint violation"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            var cmd = connection.CreateBatchDmlCommand();

            cmd.Transaction = transaction;
            cmd.Add(sql1);
            try
            {
                await cmd.ExecuteNonQueryAsync();

                Assert.True(false, "Missing expected exception");
            }
            catch (SpannerException e) when(e.ErrorCode == ErrorCode.AlreadyExists)
            {
                Assert.Contains("Unique key constraint violation", e.InnerException?.Message);
            }

            // Remove the error message for the udpate statement.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql1, StatementResult.CreateUpdateCount(1));
            // Abort the transaction and try to commit. That will trigger a retry, but the retry
            // will not receive an error for the update statement. That will fail the retry.
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);

            await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => transaction.CommitAsync());

            Assert.Equal(1, transaction.RetryCount);
        }
        public async Task ReadWriteTransaction_AbortedDmlWithDifferentException_FailsRetry()
        {
            string sql = $"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateException(new RpcException(new Status(StatusCode.AlreadyExists, "Unique key constraint violation"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            var cmd = connection.CreateDmlCommand(sql);

            cmd.Transaction = transaction;
            var e = await Assert.ThrowsAsync <SpannerException>(() => cmd.ExecuteNonQueryAsync());

            Assert.Equal(ErrorCode.AlreadyExists, e.ErrorCode);
            Assert.Contains("Unique key constraint violation", e.InnerException?.Message);

            // Change the error for the statement on the mock server and abort the transaction.
            // The retry should now fail as the error has changed.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateException(new RpcException(new Status(StatusCode.NotFound, "Table Foo not found"))));
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => transaction.CommitAsync());

            Assert.Equal(1, transaction.RetryCount);
        }
        public async Task ReadWriteTransaction_AbortedDmlWithSameException_CanBeRetried(bool enableInternalRetries)
        {
            string sql = $"UPDATE Foo SET Bar='bar' Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateException(new RpcException(new Status(StatusCode.FailedPrecondition, "UPDATE statement misses WHERE clause"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            transaction.EnableInternalRetries = enableInternalRetries;
            var cmd = connection.CreateDmlCommand(sql);

            cmd.Transaction = transaction;

            var e = await Assert.ThrowsAsync <SpannerException>(() => cmd.ExecuteNonQueryAsync());

            Assert.Equal(ErrorCode.FailedPrecondition, e.ErrorCode);
            Assert.Contains("UPDATE statement misses WHERE clause", e.InnerException?.Message);

            // Abort the transaction on the mock server. The transaction should be able to internally retry.
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            if (enableInternalRetries)
            {
                await transaction.CommitAsync();

                Assert.Equal(1, transaction.RetryCount);
            }
            else
            {
                var se = await Assert.ThrowsAsync <SpannerException>(() => transaction.CommitAsync());

                Assert.Equal(ErrorCode.Aborted, se.ErrorCode);
            }
        }
        public async Task ReadWriteTransaction_BatchDmlWithSameExceptionHalfwayAndSameResults_CanBeRetried(bool enableInternalRetries)
        {
            string sql1 = $"UPDATE Foo SET Bar='valid' WHERE Id={_fixture.RandomLong()}";
            string sql2 = $"UPDATE Foo SET Bar='invalid' Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql1, StatementResult.CreateUpdateCount(1));
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql2, StatementResult.CreateException(new RpcException(new Status(StatusCode.FailedPrecondition, "UPDATE statement misses WHERE clause"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            transaction.EnableInternalRetries = enableInternalRetries;
            var cmd = connection.CreateBatchDmlCommand();

            cmd.Transaction = transaction;
            cmd.Add(sql1);
            cmd.Add(sql2);

            var e = await Assert.ThrowsAsync <SpannerBatchNonQueryException>(() => cmd.ExecuteNonQueryAsync());

            Assert.Contains("UPDATE statement misses WHERE clause", e.Message);
            Assert.Equal(new List <long> {
                1
            }, e.SuccessfulCommandResults);

            // Abort the transaction and try to commit. That will trigger a retry, and the retry will receive
            // the same error and the same results for the BatchDML call as the original attempt.
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            if (enableInternalRetries)
            {
                await transaction.CommitAsync();

                Assert.Equal(1, transaction.RetryCount);
            }
            else
            {
                var se = await Assert.ThrowsAsync <SpannerException>(() => transaction.CommitAsync());

                Assert.Equal(ErrorCode.Aborted, se.ErrorCode);
            }
        }
        public async Task ReadWriteTransaction_QueryWithSameException_CanBeRetried(bool enableInternalRetries)
        {
            string sql = $"SELECT Id FROM Foo WHERE Id={_fixture.RandomLong()}";

            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateException(new RpcException(new Status(StatusCode.NotFound, "Table not found: Foo"))));
            using var connection = CreateConnection();
            await connection.OpenAsync();

            using var transaction = await connection.BeginTransactionAsync();

            transaction.EnableInternalRetries = enableInternalRetries;
            var cmd = connection.CreateSelectCommand(sql);

            cmd.Transaction = transaction;
            using (var reader = await cmd.ExecuteReaderAsync())
            {
                // Any query error is thrown by the first call to reader.ReadAsync();
                var e = await Assert.ThrowsAsync <SpannerException>(() => reader.ReadAsync());

                Assert.Equal(ErrorCode.NotFound, e.ErrorCode);
                Assert.Contains("Table not found: Foo", e.InnerException.Message);
            }
            // Abort the transaction on the mock server. The transaction should be able to internally retry.
            _fixture.SpannerMock.AbortTransaction(transaction.TransactionId);
            if (enableInternalRetries)
            {
                await transaction.CommitAsync();

                Assert.Equal(1, transaction.RetryCount);
            }
            else
            {
                var se = await Assert.ThrowsAsync <SpannerException>(() => transaction.CommitAsync());

                Assert.Equal(ErrorCode.Aborted, se.ErrorCode);
            }
        }