public async Task ReadWriteTransaction_QueryAbortsHalfway_WithDifferentUnseenResults_CanBeRetried(bool enableInternalRetries)
        {
            string sql = $"SELECT Id FROM Foo WHERE Id IN ({_fixture.RandomLong()}, {_fixture.RandomLong()})";

            // Create a result set with 2 rows.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.Int64
            }, "Id", 1, 2));
            // The following will cause the ExecuteStreamingSql method on the mock server to return an Aborted error on stream index 1 (i.e. before the row with value 2 is returned).
            // This simulates a transaction that is aborted while a streaming result set is still being returned to the client.
            var streamWritePermissions = new BlockingCollection <int>
            {
                1
            };
            var executionTime = ExecutionTime.StreamException(MockSpannerService.CreateAbortedException(sql), 1, streamWritePermissions);

            _fixture.SpannerMock.AddOrUpdateExecutionTime(nameof(MockSpannerService.ExecuteStreamingSql), executionTime);

            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();

            // Only the first row of the reader.
            Assert.True(await reader.ReadAsync());
            Assert.Equal(1, reader.GetInt64(reader.GetOrdinal("Id")));

            // Now change the result of the query, but only for the second row which has not yet been
            // seen by this transaction.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.Int64
            }, "Id", 1, 3));
            // Try to get the second row of the result. This should succeed, even though the transaction
            // was aborted, retried and the reader was re-initialized under the hood. The retry succeeds
            // because only data that had not yet been seen by this transaction was changed.
            executionTime.AlwaysAllowWrite();
            if (enableInternalRetries)
            {
                Assert.True(await reader.ReadAsync());
                Assert.Equal(3, reader.GetInt64(reader.GetOrdinal("Id")));
                // Ensure that there are no more rows in the results.
                Assert.False(await reader.ReadAsync());
                // Check that the transaction really retried.
                Assert.Equal(1, transaction.RetryCount);
            }
            else
            {
                var e = await Assert.ThrowsAsync <SpannerException>(() => reader.ReadAsync());

                Assert.Equal(ErrorCode.Aborted, e.ErrorCode);
            }
        }
Beispiel #2
0
 public SpannerMockServerFixture()
 {
     SpannerMock = new MockSpannerService();
     _server     = new Server
     {
         Services = { Google.Cloud.Spanner.V1.Spanner.BindService(SpannerMock) },
         Ports    = { new ServerPort("localhost", 0, ServerCredentials.Insecure) }
     };
     _server.Start();
 }
        public async Task ReadWriteTransaction_QueryAbortsHalfway_WithDifferentResults_FailsRetry()
        {
            string sql = $"SELECT Id FROM Foo WHERE Id IN ({_fixture.RandomLong()}, {_fixture.RandomLong()})";

            // Create a result set with 2 rows.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.Int64
            }, "Id", 1, 2));
            // The following will cause the ExecuteStreamingSql method on the mock server to return an Aborted error on stream index 1 (i.e. before the row with value 2 is returned).
            // This simulates a transaction that is aborted while a streaming result set is still being returned to the client.
            var streamWritePermissions = new BlockingCollection <int>
            {
                1
            };
            var executionTime = ExecutionTime.StreamException(MockSpannerService.CreateAbortedException(sql), 1, streamWritePermissions);

            _fixture.SpannerMock.AddOrUpdateExecutionTime(nameof(MockSpannerService.ExecuteStreamingSql), executionTime);

            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();

            // Only the first row of the reader.
            Assert.True(await reader.ReadAsync());
            Assert.Equal(1, reader.GetInt64(reader.GetOrdinal("Id")));

            // Now change the result of the query for the record that has already been seen.
            _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.Int64
            }, "Id", 3, 2));
            executionTime.AlwaysAllowWrite();
            // Try to get the second row of the result. This will now fail.
            await Assert.ThrowsAsync <SpannerAbortedDueToConcurrentModificationException>(() => reader.ReadAsync());

            Assert.Equal(1, transaction.RetryCount);
        }