public void CommandIncludesRequestAndTransactionTag()
        {
            var requestTag     = "request-tag-1";
            var transactionTag = "transaction-tag-1";
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteBatchDmlAsync()
            .SetupCommitAsync();
            SpannerConnection  connection  = SpannerCommandTests.BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            transaction.Tag = transactionTag;

            var command = transaction.CreateBatchDmlCommand();

            command.Add("UPDATE FOO SET BAR=1 WHERE TRUE");
            command.Tag = requestTag;
            command.ExecuteNonQuery();
            transaction.Commit();

            spannerClientMock.Verify(client => client.ExecuteBatchDmlAsync(
                                         It.Is <ExecuteBatchDmlRequest>(request => request.RequestOptions.RequestTag == requestTag && request.RequestOptions.TransactionTag == transactionTag),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.CommitAsync(
                                         It.Is <CommitRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == transactionTag),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
        public void CommitIncludesPriority()
        {
            var commitPriority  = Priority.Medium;
            var commandPriority = Priority.High;
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteStreamingSql()
            .SetupCommitAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            transaction.CommitPriority = commitPriority;

            var command = connection.CreateSelectCommand("SELECT * FROM FOO");

            command.Transaction = transaction;
            command.Priority    = commandPriority;
            using (var reader = command.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }
            transaction.Commit();

            spannerClientMock.Verify(client => client.ExecuteStreamingSql(
                                         It.Is <ExecuteSqlRequest>(request => request.RequestOptions.Priority == PriorityConverter.ToProto(commandPriority)),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.CommitAsync(
                                         It.Is <CommitRequest>(request => request.RequestOptions.Priority == PriorityConverter.ToProto(commitPriority)),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
Ejemplo n.º 3
0
 /// <summary>
 /// Creates a wrapper around an existing Spanner read-only transaction that can be used with EFCore.
 /// Read-only transactions are never aborted by Cloud Spanner and no additional retry handling is
 /// needed for this type of transaction.
 /// </summary>
 /// <param name="connection">The connection associated with the transaction</param>
 /// <param name="spannerTransaction">The underlying Spanner transaction. This transaction must be marked as read-only</param>
 public SpannerReadOnlyTransaction([NotNull] SpannerRetriableConnection connection, [NotNull] SpannerTransaction spannerTransaction)
 {
     GaxPreconditions.CheckNotNull(spannerTransaction, nameof(spannerTransaction));
     GaxPreconditions.CheckArgument(spannerTransaction.Mode == TransactionMode.ReadOnly, nameof(spannerTransaction), "Must be a read-only transaction");
     DbConnection       = GaxPreconditions.CheckNotNull(connection, nameof(connection));
     SpannerTransaction = spannerTransaction;
 }
        public void CommandIncludesPriority()
        {
            var priority = Priority.High;
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteBatchDmlAsync()
            .SetupCommitAsync();
            SpannerConnection  connection  = SpannerCommandTests.BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            var command = transaction.CreateBatchDmlCommand();

            command.Add("UPDATE FOO SET BAR=1 WHERE TRUE");
            command.Priority = priority;
            command.ExecuteNonQuery();
            transaction.Commit();

            spannerClientMock.Verify(client => client.ExecuteBatchDmlAsync(
                                         It.Is <ExecuteBatchDmlRequest>(request => request.RequestOptions.Priority == PriorityConverter.ToProto(priority)),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
        public void CommitPriorityCanBeSetAfterCommandExecution()
        {
            var priority = Priority.Medium;
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteStreamingSql()
            .SetupCommitAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            // Execute a command on the transaction.
            var command = connection.CreateSelectCommand("SELECT * FROM FOO");

            command.Transaction = transaction;
            using (var reader = command.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }
            // Verify that we can set the commit priority after a command has been executed.
            transaction.CommitPriority = priority;
            transaction.Commit();

            spannerClientMock.Verify(client => client.CommitAsync(
                                         It.Is <CommitRequest>(request => request.RequestOptions.Priority == PriorityConverter.ToProto(priority)),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
        public void TransactionTagCannotBeSetAfterCommandExecution()
        {
            var transactionTag = "transaction-tag-1";
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteStreamingSql()
            .SetupCommitAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            // Execute a command on the transaction without a transaction tag.
            var command1 = connection.CreateSelectCommand("SELECT * FROM FOO");

            command1.Transaction = transaction;
            using (var reader = command1.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }
            Assert.Throws <InvalidOperationException>(() => transaction.Tag = transactionTag);

            transaction.Commit();

            spannerClientMock.Verify(client => client.ExecuteStreamingSql(
                                         It.Is <ExecuteSqlRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == ""),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.CommitAsync(
                                         It.Is <CommitRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == ""),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
        public void TagsCanBeSetToNull()
        {
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteStreamingSql()
            .SetupCommitAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            transaction.Tag = null;

            var command = connection.CreateSelectCommand("SELECT * FROM FOO");

            command.Transaction = transaction;
            command.Tag         = null;
            using (var reader = command.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }
            transaction.Commit();

            spannerClientMock.Verify(client => client.ExecuteStreamingSql(
                                         It.Is <ExecuteSqlRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == ""),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.CommitAsync(
                                         It.Is <CommitRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == ""),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
        public void CommandIncludesRequestAndTransactionTag()
        {
            var requestTag1    = "request-tag-1";
            var requestTag2    = "request-tag-2";
            var transactionTag = "transaction-tag-1";
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync()
            .SetupExecuteStreamingSql()
            .SetupCommitAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            transaction.Tag = transactionTag;

            var command1 = connection.CreateSelectCommand("SELECT * FROM FOO");

            command1.Transaction = transaction;
            command1.Tag         = requestTag1;
            using (var reader = command1.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }

            var command2 = connection.CreateSelectCommand("SELECT * FROM FOO");

            command2.Transaction = transaction;
            command2.Tag         = requestTag2;
            using (var reader = command2.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }

            // Execute a statement without a request tag on the same transaction.
            var command3 = connection.CreateSelectCommand("SELECT * FROM FOO");

            command3.Transaction = transaction;
            using (var reader = command3.ExecuteReader())
            {
                Assert.True(reader.HasRows);
            }
            transaction.Commit();

            spannerClientMock.Verify(client => client.ExecuteStreamingSql(
                                         It.Is <ExecuteSqlRequest>(request => request.RequestOptions.RequestTag == requestTag1 && request.RequestOptions.TransactionTag == transactionTag),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.ExecuteStreamingSql(
                                         It.Is <ExecuteSqlRequest>(request => request.RequestOptions.RequestTag == requestTag2 && request.RequestOptions.TransactionTag == transactionTag),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.ExecuteStreamingSql(
                                         It.Is <ExecuteSqlRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == transactionTag),
                                         It.IsAny <CallSettings>()), Times.Once());
            spannerClientMock.Verify(client => client.CommitAsync(
                                         It.Is <CommitRequest>(request => request.RequestOptions.RequestTag == "" && request.RequestOptions.TransactionTag == transactionTag),
                                         It.IsAny <CallSettings>()), Times.Once());
        }
        internal async Task RetryAsync(SpannerException abortedException, CancellationToken cancellationToken, int timeoutSeconds = MAX_TIMEOUT_SECONDS)
        {
            if (!EnableInternalRetries)
            {
                throw abortedException;
            }
            DateTime?overallDeadline = _options.CalculateDeadline(_clock);
            TimeSpan retryDelay      = _options.InitialDelay;

            // If there's a recommended retry delay specified on the exception
            // we should respect it.
            retryDelay = abortedException.RecommendedRetryDelay ?? _options.Jitter(retryDelay);
            while (true)
            {
                if (RetryCount >= MaxInternalRetryCount)
                {
                    throw new SpannerException(ErrorCode.Aborted, "Transaction was aborted because it aborted and retried too many times");
                }

                DateTime expectedRetryTime = _clock.GetCurrentDateTimeUtc() + retryDelay;
                if (expectedRetryTime > overallDeadline)
                {
                    throw new SpannerException(ErrorCode.Aborted, "Transaction was aborted because it timed out while retrying");
                }
                await _scheduler.Delay(retryDelay, cancellationToken).ConfigureAwait(false);

                // TODO: Preferably the Spanner client library should have some 'reset' option on an existing
                // transaction instead of having to begin a new transaction. This will potentially use a transaction
                // on a different session, while the recommended behavior for a retry is to use the same session as
                // the original attempt.
                SpannerTransaction.Dispose();
                SpannerTransaction = await Connection.SpannerConnection.BeginTransactionAsync(cancellationToken);

                RetryCount++;
                try
                {
                    foreach (IRetriableStatement statement in _retriableStatements)
                    {
                        await statement.RetryAsync(this, cancellationToken, timeoutSeconds);
                    }
                    break;
                }
                catch (SpannerAbortedDueToConcurrentModificationException)
                {
                    // Retry failed because of a concurrent modification.
                    throw;
                }
                catch (SpannerException e) when(e.ErrorCode == ErrorCode.Aborted)
                {
                    // Ignore and retry.
                    retryDelay = e.RecommendedRetryDelay ?? _options.Jitter(_options.NextDelay(retryDelay));
                }
            }
        }
 internal SpannerRetriableTransaction(
     SpannerRetriableConnection connection,
     SpannerTransaction spannerTransaction,
     IClock clock,
     IScheduler scheduler,
     RetriableTransactionOptions options = null)
 {
     Connection         = connection;
     SpannerTransaction = spannerTransaction;
     _clock             = GaxPreconditions.CheckNotNull(clock, nameof(clock));
     _scheduler         = GaxPreconditions.CheckNotNull(scheduler, nameof(scheduler));
     _options           = options ?? RetriableTransactionOptions.CreateDefault();
 }
        public void CommitPriorityDefaultsToUnspecified()
        {
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginTransaction();

            Assert.Equal(Priority.Unspecified, transaction.CommitPriority);
        }
        public void TransactionTagCannotBeSetForReadOnlyTransaction()
        {
            Mock <SpannerClient> spannerClientMock = SpannerClientHelpers
                                                     .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict);

            spannerClientMock
            .SetupBatchCreateSessionsAsync()
            .SetupBeginTransactionAsync();
            SpannerConnection  connection  = BuildSpannerConnection(spannerClientMock);
            SpannerTransaction transaction = connection.BeginReadOnlyTransaction();

            Assert.Throws <InvalidOperationException>(() => transaction.Tag = "transaction-tag-1");
        }
Ejemplo n.º 13
0
 protected override void Dispose(bool disposing)
 {
     if (Disposed)
     {
         return;
     }
     if (disposing)
     {
         SpannerTransaction.Dispose();
     }
     Disposed = true;
     base.Dispose(disposing);
 }
Ejemplo n.º 14
0
        public void TransactionConstructor()
        {
            var connection = new SpannerConnection();
            var pool       = new FakeSessionPool();
            var session    = PooledSession.FromSessionName(pool, new SessionName("project", "instance", "database", "session"));

            var transaction = new SpannerTransaction(connection, TransactionMode.ReadWrite, session: session, timestampBound: null);
            var command     = new SpannerBatchCommand(transaction);

            Assert.Empty(command.Commands);
            Assert.Same(connection, command.Connection);
            Assert.Same(transaction, command.Transaction);
            Assert.Equal(SpannerBatchCommandType.None, command.CommandType);
        }
        /// <summary>
        /// Commits the database transaction asynchronously.
        /// </summary>
        /// <param name="cancellationToken">A cancellation token used for this task.</param>
        /// <returns>Returns the UTC timestamp when the data was written to the database.</returns>
        public async Task <DateTime> CommitAsync(CancellationToken cancellationToken = default)
        {
            var tracer = TracerProviderExtension.GetTracer();

            using var span = tracer.StartActiveSpan(TracerProviderExtension.SPAN_NAME_COMMIT);
            while (true)
            {
                try
                {
                    _commitTimestamp = await SpannerTransaction.CommitAsync(cancellationToken).ConfigureAwait(false);

                    // Propagate the commit timestamp to all columns that were automatically updated during this transaction.
                    // Note that this transaction could both be a manual transaction started by the application, as well as
                    // an implicit transaction started by Entity Framework.
                    foreach (var modificationCommand in _commitTimestampModificationCommands)
                    {
                        foreach (var columnModification in modificationCommand.ColumnModifications)
                        {
                            if (columnModification is SpannerPendingCommitTimestampColumnModification pendingCommitTimestampColumnModification)
                            {
                                var property = pendingCommitTimestampColumnModification.Property.PropertyInfo;
                                if (property != null)
                                {
                                    var entry         = pendingCommitTimestampColumnModification.Entry;
                                    var originalState = entry.EntityState;
                                    var entity        = entry.ToEntityEntry().Entity;
                                    property.SetValue(entity, _commitTimestamp);
                                    entry.EntityState = originalState;
                                }
                            }
                        }
                    }
                    span.SetStatus(OpenTelemetry.Trace.Status.Ok);
                    span.End();
                    return((DateTime)_commitTimestamp);
                }
                catch (SpannerException e) when(e.ErrorCode == ErrorCode.Aborted)
                {
                    span.SetAttribute(TracerProviderExtension.ATTRIBUTE_NAME_RETRYING, e.Message);
                    await RetryAsync(e, cancellationToken).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    span.SetStatus(OpenTelemetry.Trace.Status.Error.WithDescription(e.Message));
                    span.End();
                    throw;
                }
            }
        }
        private SpannerCommand CreateSpannerCommand(
            SpannerConnection spannerConnection,
            SpannerTransaction transaction,
            ModificationCommand modificationCommand)
        {
            SpannerCommand cmd;

            switch (modificationCommand.EntityState)
            {
            case EntityState.Deleted:
                cmd = spannerConnection.CreateDeleteCommand(modificationCommand.TableName);
                break;

            case EntityState.Modified:
                cmd = spannerConnection.CreateUpdateCommand(modificationCommand.TableName);
                break;

            case EntityState.Added:
                cmd = spannerConnection.CreateInsertCommand(modificationCommand.TableName);
                break;

            default:
                throw new NotSupportedException(
                          $"Modification type {modificationCommand.EntityState} is not supported.");
            }
            cmd.Logger      = new SpannerLogBridge <DbLoggerCategory.Database.Command>(_logger);
            cmd.Transaction = transaction;
            foreach (var columnModification in modificationCommand.ColumnModifications)
            {
                cmd.Parameters.Add(
                    _typeMapper.GetMapping(columnModification.Property).CreateParameter(cmd,
                                                                                        columnModification.ColumnName,
                                                                                        columnModification.UseOriginalValueParameter
                            ? columnModification.OriginalValue
                            : columnModification.Value, columnModification.Property.IsNullable));
            }
            return(cmd);
        }
 public Dictionary <int, int> FetchValues(string key, SpannerTransaction transaction = null)
 {
     using (var connection = GetConnection())
     {
         using (var command = connection.CreateSelectCommand($"SELECT OriginalValue, Value FROM {TableName} WHERE Key=@key"))
         {
             if (transaction != null)
             {
                 command.Transaction = transaction;
             }
             command.Parameters.Add("key", SpannerDbType.String, key);
             using (var reader = command.ExecuteReader())
             {
                 var dictionary = new Dictionary <int, int>();
                 while (reader.Read())
                 {
                     dictionary.Add(reader.GetInt32(0), reader.GetInt32(1));
                 }
                 return(dictionary);
             }
         }
     }
 }
 private async Task <int> DmlUpdateFixtureData(string key, SpannerConnection connection, SpannerTransaction transaction)
 {
     using (var command = connection.CreateDmlCommand(_updateSql))
     {
         command.Transaction = transaction;
         command.Parameters.Add("Key", SpannerDbType.String, key);
         return(await command.ExecuteNonQueryAsync());
     }
 }
        private async Task <IReadOnlyList <long> > BatchDmlInsertUpdateFixtureData(string key, SpannerConnection connection, SpannerTransaction transaction)
        {
            var command = transaction.CreateBatchDmlCommand();

            command.Add(_insertSql, new SpannerParameterCollection
            {
                { "Key", SpannerDbType.String, key }
            });
            command.Add(_updateSql, new SpannerParameterCollection
            {
                { "Key", SpannerDbType.String, key }
            });
            return(await command.ExecuteNonQueryAsync());
        }
                public async Task <int> DatabaseWorkAsync(SpannerConnection connection, SpannerTransaction transaction)
                {
                    _callTimes.Add(_scheduler.Clock.GetCurrentDateTimeUtc());

                    var insert = connection.CreateInsertCommand("table_1",
                                                                new SpannerParameterCollection
                    {
                        new SpannerParameter("column_1", SpannerDbType.Int64, 10)
                    });

                    insert.Transaction = transaction;

                    var dml = transaction.CreateBatchDmlCommand();

                    dml.Add("UPDATE table_1 SET column_1 = column1 + 5");

                    await insert.ExecuteNonQueryAsync();

                    await dml.ExecuteNonQueryAsync();

                    return(_callTimes.Count);
                }
 public Task <int> Fails(SpannerTransaction transaction)
 {
     _callTimes.Add(_scheduler.Clock.GetCurrentDateTimeUtc());
     throw new InvalidOperationException("Bang!");
 }
        private async Task <int> DeleteFixtureData(string key, SpannerConnection connection, SpannerTransaction transaction)
        {
            var parameters = new SpannerParameterCollection
            {
                { "Key", SpannerDbType.String, key },
                { "OriginalValue", SpannerDbType.Int64, 4 }
            };

            using (var command = connection.CreateDeleteCommand(_fixture.TableName, parameters))
            {
                command.Transaction = transaction;
                return(await command.ExecuteNonQueryAsync());
            }
        }
        private async Task SelectAndReadFixtureData(string key, SpannerConnection connection, SpannerTransaction transaction)
        {
            using (var command = connection.CreateSelectCommand(_selectSql))
            {
                command.Transaction = transaction;
                command.Parameters.Add("Key", SpannerDbType.String, key);
                var reader = await command.ExecuteReaderAsync();

                while (await reader.ReadAsync())
                {
                    ;
                }
            }
        }
 /// <see cref="SpannerTransaction.Rollback"/>
 public override void Rollback() => SpannerTransaction.Rollback();