public async Task InsertAlbum()
        {
            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            db.Albums.Add(new Albums
            {
                AlbumId     = 1L,
                Title       = "Some title",
                SingerId    = 1L,
                ReleaseDate = new SpannerDate(2000, 1, 1),
            });
            var updateCount = await db.SaveChangesAsync();

            Assert.Equal(1L, updateCount);
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                request => {
                Assert.Single(request.Mutations);
                var mutation = request.Mutations[0];
                Assert.Equal(Mutation.OperationOneofCase.Insert, mutation.OperationCase);
                Assert.Equal("Albums", mutation.Insert.Table);
                var row  = mutation.Insert.Values[0];
                var cols = mutation.Insert.Columns;
                Assert.Equal("1", row.Values[cols.IndexOf("AlbumId")].StringValue);
                Assert.Equal("Some title", row.Values[cols.IndexOf("Title")].StringValue);
                Assert.Equal("1", row.Values[cols.IndexOf("SingerId")].StringValue);
                Assert.Equal("2000-01-01", row.Values[cols.IndexOf("ReleaseDate")].StringValue);
            }
                );
        }
        public async Task CanUpdateCommitTimestamp()
        {
            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            _fixture.SpannerMock.AddOrUpdateStatementResult($"{Environment.NewLine}SELECT `ColComputed`{Environment.NewLine}FROM `TableWithAllColumnTypes`{Environment.NewLine}WHERE  TRUE  AND `ColInt64` = @p0", StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.String
            }, "FOO"));

            var row = new TableWithAllColumnTypes {
                ColInt64 = 1L
            };

            db.TableWithAllColumnTypes.Attach(row);
            row.ColBool = true;
            await db.SaveChangesAsync();

            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                request =>
            {
                Assert.Single(request.Mutations);
                var mutation = request.Mutations[0];
                Assert.Equal(Mutation.OperationOneofCase.Update, mutation.OperationCase);
                Assert.Single(mutation.Update.Values);
                var row  = mutation.Update.Values[0];
                var cols = mutation.Update.Columns;
                Assert.Equal("spanner.commit_timestamp()", row.Values[cols.IndexOf("ColCommitTS")].StringValue);
            }
                );
        }
        public async Task DeleteSinger_DoesNotSelectFullName()
        {
            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            db.Singers.Remove(new Singers {
                SingerId = 1L
            });
            var updateCount = await db.SaveChangesAsync();

            Assert.Equal(1L, updateCount);
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                request =>
            {
                Assert.Single(request.Mutations);
                var mutation = request.Mutations[0];
                Assert.Equal(Mutation.OperationOneofCase.Delete, mutation.OperationCase);
                Assert.Equal("Singers", mutation.Delete.Table);
                var keySet = mutation.Delete.KeySet;
                Assert.False(keySet.All);
                Assert.Empty(keySet.Ranges);
                Assert.Single(keySet.Keys);
                Assert.Single(keySet.Keys[0].Values);
                Assert.Equal("1", keySet.Keys[0].Values[0].StringValue);
            }
                );
            Assert.Empty(_fixture.SpannerMock.Requests.Where(request => request is ExecuteBatchDmlRequest));
            Assert.Empty(_fixture.SpannerMock.Requests.Where(request => request is ExecuteSqlRequest));
            Assert.Single(_fixture.SpannerMock.Requests.Where(request => request is CommitRequest));
        }
        public async Task CanInsertRowWithCommitTimestampAndComputedColumn()
        {
            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            var selectSql = $"{Environment.NewLine}SELECT `ColComputed`{Environment.NewLine}FROM `TableWithAllColumnTypes`{Environment.NewLine}WHERE  TRUE  AND `ColInt64` = @p0";

            _fixture.SpannerMock.AddOrUpdateStatementResult(selectSql, StatementResult.CreateSingleColumnResultSet(new V1.Type {
                Code = V1.TypeCode.String
            }, "FOO"));

            db.TableWithAllColumnTypes.Add(
                new TableWithAllColumnTypes {
                ColInt64 = 1L
            }
                );
            await db.SaveChangesAsync();

            Assert.Empty(_fixture.SpannerMock.Requests.Where(request => request is ExecuteBatchDmlRequest));
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is ExecuteSqlRequest).Select(request => (ExecuteSqlRequest)request),
                request => Assert.Equal(selectSql.Trim(), request.Sql.Trim())
                );
            Assert.Single(_fixture.SpannerMock.Requests.Where(request => request is CommitRequest));
            // Verify the order of the requests (that is, the Select statement should be outside the implicit transaction).
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest || request is ExecuteSqlRequest).Select(request => request.GetType()),
                requestType => Assert.Equal(typeof(CommitRequest), requestType),
                requestType => Assert.Equal(typeof(ExecuteSqlRequest), requestType)
                );
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                request =>
            {
                Assert.Single(request.Mutations);
                var mutation = request.Mutations[0];
                Assert.Equal(Mutation.OperationOneofCase.Insert, mutation.OperationCase);
                Assert.Single(mutation.Insert.Values);
                var row  = mutation.Insert.Values[0];
                var cols = mutation.Insert.Columns;
                Assert.Equal("spanner.commit_timestamp()", row.Values[cols.IndexOf("ColCommitTS")].StringValue);
            }
                );
        }
        public async Task InsertSingerInTransaction()
        {
            using var db          = new MockServerSampleDbContextUsingMutations(ConnectionString);
            using var transaction = await db.Database.BeginTransactionAsync();

            db.Singers.Add(new Singers
            {
                SingerId  = 1L,
                FirstName = "Alice",
                LastName  = "Morrison",
            });
            var updateCount = await db.SaveChangesAsync();

            await transaction.CommitAsync();

            Assert.Equal(1L, updateCount);
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                request => {
                Assert.Single(request.Mutations);
                var mutation = request.Mutations[0];
                Assert.Equal(Mutation.OperationOneofCase.Insert, mutation.OperationCase);
                Assert.Equal("Singers", mutation.Insert.Table);
                var row  = mutation.Insert.Values[0];
                var cols = mutation.Insert.Columns;
                Assert.Equal("1", row.Values[cols.IndexOf("SingerId")].StringValue);
                Assert.Equal("Alice", row.Values[cols.IndexOf("FirstName")].StringValue);
                Assert.Equal("Morrison", row.Values[cols.IndexOf("LastName")].StringValue);
                Assert.Equal(-1, cols.IndexOf("FullName"));
            }
                );
            // Verify that EF Core does NOT try to fetch the name of the Singer, even though it is a computed
            // column that should normally be propagated. The fetch is skipped because the update uses mutations
            // in combination with manual transactions. Trying to fetch the name of the singer is therefore not
            // possible during the transaction.
            Assert.Collection(_fixture.SpannerMock.Requests
                              .Where(request => request is CommitRequest || request is ExecuteSqlRequest)
                              .Select(request => request.GetType()),
                              request => Assert.Equal(typeof(CommitRequest), request));
        }
        public async Task InsertSinger()
        {
            var selectFullNameSql = AddSelectSingerFullNameResult("Alice Morrison", 0);

            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            db.Singers.Add(new Singers
            {
                SingerId  = 1L,
                FirstName = "Alice",
                LastName  = "Morrison",
            });
            var updateCount = await db.SaveChangesAsync();

            Assert.Equal(1L, updateCount);
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                request => {
                Assert.Single(request.Mutations);
                var mutation = request.Mutations[0];
                Assert.Equal(Mutation.OperationOneofCase.Insert, mutation.OperationCase);
                Assert.Equal("Singers", mutation.Insert.Table);
                var row  = mutation.Insert.Values[0];
                var cols = mutation.Insert.Columns;
                Assert.Equal("1", row.Values[cols.IndexOf("SingerId")].StringValue);
                Assert.Equal("Alice", row.Values[cols.IndexOf("FirstName")].StringValue);
                Assert.Equal("Morrison", row.Values[cols.IndexOf("LastName")].StringValue);
                Assert.Equal(-1, cols.IndexOf("FullName"));
            }
                );
            // Verify that the SELECT for the FullName is done after the commit.
            Assert.Collection(_fixture.SpannerMock.Requests
                              .Where(request => request is CommitRequest || request is ExecuteSqlRequest)
                              .Select(request => request.GetType()),
                              request => Assert.Equal(typeof(CommitRequest), request),
                              request => Assert.Equal(typeof(ExecuteSqlRequest), request));
            Assert.Single(_fixture.SpannerMock.Requests
                          .Where(request => request is ExecuteSqlRequest sqlRequest && sqlRequest.Sql.Trim() == selectFullNameSql.Trim()));
        }
        public async Task ExplicitAndImplicitTransactionIsRetried(bool disableInternalRetries, bool useExplicitTransaction)
        {
            // Abort the next statement that is executed on the mock server.
            _fixture.SpannerMock.AbortNextStatement();

            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            IDbContextTransaction transaction = null;

            if (useExplicitTransaction)
            {
                // Note that using explicit transactions in combination with mutations has a couple of side-effects:
                // 1. Read-your-writes does not work.
                // 2. Computed columns are not propagated to the current context.
                transaction = await db.Database.BeginTransactionAsync();

                if (disableInternalRetries)
                {
                    transaction.DisableInternalRetries();
                }
            }
            db.Venues.Add(new Venues
            {
                Code = "C1",
                Name = "Concert Hall",
            });

            // We can only disable internal retries when using explicit transactions. Otherwise internal retries
            // are always used.
            if (disableInternalRetries && useExplicitTransaction)
            {
                await db.SaveChangesAsync();

                var e = await Assert.ThrowsAsync <SpannerException>(() => transaction.CommitAsync());

                Assert.Equal(ErrorCode.Aborted, e.ErrorCode);
            }
            else
            {
                var updateCount = await db.SaveChangesAsync();

                Assert.Equal(1L, updateCount);
                if (useExplicitTransaction)
                {
                    await transaction.CommitAsync();
                }
                Assert.Empty(_fixture.SpannerMock.Requests.Where(request => request is ExecuteBatchDmlRequest));
                Assert.Collection(
                    _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request),
                    // The commit request is sent twice to the server, as the statement is aborted during the first attempt.
                    request =>
                {
                    Assert.Single(request.Mutations);
                    Assert.Equal("Venues", request.Mutations.First().Insert.Table);
                    Assert.NotNull(request.TransactionId);
                },
                    request =>
                {
                    Assert.Single(request.Mutations);
                    Assert.Equal("Venues", request.Mutations.First().Insert.Table);
                    Assert.NotNull(request.TransactionId);
                }
                    );
            }
        }
        public async Task UpdateSinger_SelectsFullName()
        {
            // Setup results.
            var selectSingerSql = AddFindSingerResult($"SELECT `s`.`SingerId`, `s`.`BirthDate`, `s`.`FirstName`, " +
                                                      $"`s`.`FullName`, `s`.`LastName`, `s`.`Picture`{Environment.NewLine}FROM `Singers` AS `s`{Environment.NewLine}" +
                                                      $"WHERE `s`.`SingerId` = @__p_0{Environment.NewLine}LIMIT 1");
            var selectFullNameSql = AddSelectSingerFullNameResult("Alice Pieterson-Morrison", 0);

            using var db = new MockServerSampleDbContextUsingMutations(ConnectionString);
            var singer = await db.Singers.FindAsync(1L);

            singer.LastName = "Pieterson-Morrison";
            var updateCount = await db.SaveChangesAsync();

            Assert.Equal(1L, updateCount);
            Assert.Collection(
                _fixture.SpannerMock.Requests
                .Where(request => !new[] { typeof(BeginTransactionRequest), typeof(BatchCreateSessionsRequest) }.Contains(request.GetType()))
                .Select(request => request.GetType()),
                request => Assert.Equal(typeof(ExecuteSqlRequest), request),
                request => Assert.Equal(typeof(CommitRequest), request),
                request => Assert.Equal(typeof(ExecuteSqlRequest), request)
                );
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is ExecuteSqlRequest).Select(request => (ExecuteSqlRequest)request),
                request =>
            {
                Assert.Equal(selectSingerSql.Trim(), request.Sql.Trim());
                Assert.Null(request.Transaction?.Id);
            },
                request =>
            {
                Assert.Equal(selectFullNameSql.Trim(), request.Sql.Trim());
                Assert.Null(request.Transaction?.Id);
            }
                );
            Assert.Collection(
                _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => request as CommitRequest),
                request =>
            {
                Assert.Collection(
                    request.Mutations,
                    mutation =>
                {
                    Assert.Equal(Mutation.OperationOneofCase.Update, mutation.OperationCase);
                    Assert.Equal("Singers", mutation.Update.Table);
                    Assert.Collection(
                        mutation.Update.Columns,
                        column => Assert.Equal("SingerId", column),
                        column => Assert.Equal("LastName", column)
                        );
                    Assert.Collection(
                        mutation.Update.Values,
                        row =>
                    {
                        Assert.Collection(
                            row.Values,
                            value => Assert.Equal("1", value.StringValue),
                            value => Assert.Equal("Pieterson-Morrison", value.StringValue)
                            );
                    }
                        );
                }
                    );
            }
                );
        }