public async Task ReadWriteTransaction_Retry_GivesUp() { string sql = $"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}"; _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateUpdateCount(1)); using var connection = CreateConnection(); await connection.OpenAsync(); using var transaction = await connection.BeginTransactionAsync(); transaction.MaxInternalRetryCount = 3; for (var i = 0; i < transaction.MaxInternalRetryCount; i++) { _fixture.SpannerMock.AbortTransaction(transaction.TransactionId); var cmd = connection.CreateDmlCommand(sql); cmd.Transaction = transaction; var updateCount = await cmd.ExecuteNonQueryAsync(); Assert.Equal(1, updateCount); Assert.Equal(i + 1, transaction.RetryCount); } // The next statement that aborts will cause the transaction to fail. _fixture.SpannerMock.AbortTransaction(transaction.TransactionId); var cmd2 = connection.CreateDmlCommand(sql); cmd2.Transaction = transaction; var e = await Assert.ThrowsAsync <SpannerException>(() => cmd2.ExecuteNonQueryAsync()); Assert.Equal(ErrorCode.Aborted, e.ErrorCode); Assert.Contains("Transaction was aborted because it aborted and retried too many times", e.Message); }
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_AbortedDml_IsAutomaticallyRetried(bool enableInternalRetries) { string sql = $"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}"; _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateUpdateCount(1)); using var connection = CreateConnection(); await connection.OpenAsync(); using var transaction = await connection.BeginTransactionAsync(); transaction.EnableInternalRetries = enableInternalRetries; // Abort the transaction on the mock server. The transaction should be able to internally retry. _fixture.SpannerMock.AbortTransaction(transaction.TransactionId); var cmd = connection.CreateDmlCommand(sql); cmd.Transaction = transaction; if (enableInternalRetries) { var updateCount = await cmd.ExecuteNonQueryAsync(); Assert.Equal(1, updateCount); Assert.Equal(1, transaction.RetryCount); } else { var e = await Assert.ThrowsAsync <SpannerException>(() => cmd.ExecuteNonQueryAsync()); Assert.Equal(ErrorCode.Aborted, e.ErrorCode); } }
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_ModifiedBatchDmlUpdateCount_FailsRetry() { string sql1 = $"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}"; string sql2 = $"UPDATE Foo SET Bar='baz' WHERE Id IN ({_fixture.RandomLong()},{_fixture.RandomLong()})"; _fixture.SpannerMock.AddOrUpdateStatementResult(sql1, StatementResult.CreateUpdateCount(1)); _fixture.SpannerMock.AddOrUpdateStatementResult(sql2, StatementResult.CreateUpdateCount(2)); 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); Assert.Equal(new List <long> { 1, 2 }, await cmd.ExecuteNonQueryAsync()); // Change the update count returned by one of the statements and abort the transaction. _fixture.SpannerMock.AddOrUpdateStatementResult(sql2, StatementResult.CreateUpdateCount(1)); _fixture.SpannerMock.AbortTransaction(transaction.TransactionId); await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => transaction.CommitAsync()); Assert.Equal(1, transaction.RetryCount); }
public async Task ReadWriteTransaction_ModifiedDmlUpdateCount_FailsRetry() { // This statement returns an update count of 1 the first time. string sql = $"UPDATE Foo SET Bar='baz' WHERE Id IN ({_fixture.RandomLong()},{_fixture.RandomLong()})"; _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateUpdateCount(1)); using var connection = CreateConnection(); await connection.OpenAsync(); using var transaction = await connection.BeginTransactionAsync(); // Execute an update and then change the return value for the statement before the retry is executed. var cmd = connection.CreateDmlCommand(sql); cmd.Transaction = transaction; Assert.Equal(1, await cmd.ExecuteNonQueryAsync()); // The update statement will return 2 the next time it is executed. _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateUpdateCount(2)); // Now abort the transaction and try to execute another DML statement. The retry will fail because it sees // a different update count during the retry. _fixture.SpannerMock.AbortTransaction(transaction.TransactionId); cmd = connection.CreateDmlCommand($"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}"); cmd.Transaction = transaction; var e = await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => cmd.ExecuteNonQueryAsync()); Assert.Equal(1, transaction.RetryCount); }
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_WithoutAbort_DoesNotRetry() { string sql = $"UPDATE Foo SET Bar='bar' WHERE Id={_fixture.RandomLong()}"; _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateUpdateCount(1)); using var connection = CreateConnection(); await connection.OpenAsync(); using var transaction = await connection.BeginTransactionAsync(); var cmd = connection.CreateDmlCommand(sql); cmd.Transaction = transaction; var updateCount = await cmd.ExecuteNonQueryAsync(); await transaction.CommitAsync(); Assert.Equal(1, updateCount); Assert.Equal(0, transaction.RetryCount); }