frontend/entities/transaction.h (130 lines of code) (raw):
//
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef THIRD_PARTY_CLOUD_SPANNER_EMULATOR_FRONTEND_ENTITIES_TRANSACTIONS_H_
#define THIRD_PARTY_CLOUD_SPANNER_EMULATOR_FRONTEND_ENTITIES_TRANSACTIONS_H_
#include <memory>
#include <optional>
#include <variant>
#include "google/protobuf/empty.pb.h"
#include "google/spanner/v1/result_set.pb.h"
#include "google/spanner/v1/spanner.pb.h"
#include "google/spanner/v1/transaction.pb.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "backend/common/ids.h"
#include "backend/query/query_engine.h"
#include "backend/schema/catalog/schema.h"
#include "backend/transaction/read_only_transaction.h"
#include "backend/transaction/read_write_transaction.h"
#include "common/clock.h"
#include "frontend/entities/database.h"
#include "absl/status/status.h"
namespace google {
namespace spanner {
namespace emulator {
namespace frontend {
namespace spanner_api = ::google::spanner::v1;
// Transaction represents a database transaction within the frontend.
//
// This class provides frontend-level functionality (e.g. URI, proto conversion,
// replay protection, invalidation) and wraps the backend transaction classes
// which actually implement the core transaction functionality. We do this to
// keep the backend transaction a completely separate module from the frontend
// and isolate it from gRPC API details.
class Transaction {
public:
enum Type {
kReadWrite,
kReadOnly,
kPartitionedDml,
};
enum Usage {
kSingleUse,
kMultiUse,
};
// These operation types correspond to the handler that was invoked. For
// ExecuteSql handlers, the operation can be either kSql(read-only) or
// kDml(read-write).
enum class OpType {
kRead,
kCommit,
kSql,
kDml,
kRollback,
};
// Indicates the DML error handling mode. This is used to differentiate
// between requests, replays, and registration errors.
enum class DMLErrorHandlingMode {
kDmlRequest,
kDmlReplay,
kDmlRegistrationError,
};
// RequestReplayState tracks the state for DML operations. DML requests can be
// replayed. If a replayed request is detected, the previously saved state and
// outcome will be used.
struct RequestReplayState {
// Indicates the final status of the request. Used for replaying the status.
// For BatchDml requests, this is not used since the status will be
// saved/returned in the response.
absl::Status status;
// The hash of the request that generated this state. This is used to detect
// request replays with a reused sequence number but with a different
// request.
int64_t request_hash;
// "outcome" can be one of two types:
// a) ResultSet - Used by ExecuteSql and ExecuteStreamingSql handler.
// b) ExecuteBatchDmlResponse - Used by ExecuteBatchDml handler.
std::variant<spanner_api::ResultSet, spanner_api::ExecuteBatchDmlResponse>
outcome;
};
Transaction(std::variant<std::unique_ptr<backend::ReadWriteTransaction>,
std::unique_ptr<backend::ReadOnlyTransaction>>
backend_transaction,
const backend::QueryEngine* query_engine,
const spanner_api::TransactionOptions& options,
const Usage& usage);
// Mark the transaction as closed. This indicates that the transaction is no
// longer valid in the context of its owning session. For example, prior
// transactions are closed once a new transaction is started.
void Close() ABSL_LOCKS_EXCLUDED(mu_);
// Converts this transaction to its proto representation.
absl::StatusOr<google::spanner::v1::Transaction> ToProto();
// Returns the schema from the backend transaction.
const backend::Schema* schema() const;
// Returns the engine used to execute queries against the database.
const backend::QueryEngine* query_engine() { return query_engine_; }
// Returns the TransactionID from the backend transaction.
backend::TransactionID id() const;
// Calls Read using the backend transaction.
absl::Status Read(const backend::ReadArg& read_arg,
std::unique_ptr<backend::RowCursor>* cursor);
// Calls ExecuteSql using the backend transaction and query engine in normal
// query mode.
absl::StatusOr<backend::QueryResult> ExecuteSql(const backend::Query& query);
// Calls ExecuteSql using the backend transaction and query engine with the
// given query mode.
absl::StatusOr<backend::QueryResult> ExecuteSql(
const backend::Query& query, v1::ExecuteSqlRequest_QueryMode query_mode);
// Calls Write using the backend transaction.
absl::Status Write(const backend::Mutation& mutation);
// Calls Commit using the backend transaction.
absl::Status Commit();
// Calls Rollback using the backend transaction.
absl::Status Rollback();
// Returns the read timestamp from the backend transaction.
absl::StatusOr<absl::Time> GetReadTimestamp() const;
// Returns the commit timestamp from the backend transaction.
absl::StatusOr<absl::Time> GetCommitTimestamp() const;
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mu_);
// Returns true if the current transaction has already been committed.
// For ReadOnlyTransaction, always returns false.
bool IsCommitted() const;
// Returns true if the current transaction has already been rolledback.
// For ReadOnlyTransaction, always returns false.
bool IsRolledback() const;
// Returns true if the current transaction has been aborted due to
// non-recoverable errors. For ReadOnlyTransaction, always returns false.
bool IsInvalid() const;
// Returns true if the current transaction has been aborted.
// For ReadOnlyTransaction & PartitionedDmlTransaction, always returns false.
bool IsAborted() const;
// Returns true if the current transaction is a ReadOnlyTransaction.
bool IsReadOnly() const { return type_ == kReadOnly; }
// Returns true if the current transaction is a ReadWriteTransaction.
bool IsReadWrite() const { return type_ == kReadWrite; }
// Returns true if the current transaction is a PartitionedDmlTransaction.
bool IsPartitionedDml() const { return type_ == kPartitionedDml; }
// All transaction methods should be called inside GuardedCall.
absl::Status GuardedCall(OpType op, const std::function<absl::Status()>& fn)
ABSL_LOCKS_EXCLUDED(mu_);
backend::ReadWriteTransaction* read_write() const {
return std::get<0>(transaction_).get();
}
backend::ReadOnlyTransaction* read_only() const {
return std::get<1>(transaction_).get();
}
// Returns the current transaction status.
absl::Status Status() const;
// If status is a constraint error, it invalidates the transaction and sets
// the transaction status.
void MaybeInvalidate(const absl::Status& status);
// Registers a new RequestReplayState using the given sequence number. If a
// state already exists for a given sequence number with a different request
// hash, an error will be returned in the Status field of RequestReplayState.
// If a state already exists for a given sequence number with a matching
// request hash, the replay state will be returned. If this is a new sequence
// number, a new replay state will be registered and nullopt will be returned.
std::optional<RequestReplayState> LookupOrRegisterDmlRequest(
int64_t seqno, int64_t request_hash, const std::string& sql_statement);
// Sets the replay outcome for the current DML request if it completed
// successfully.
void SetDmlReplayOutcome(
std::variant<spanner_api::ResultSet, spanner_api::ExecuteBatchDmlResponse>
outcome);
// Returns the DML request type.
DMLErrorHandlingMode DMLErrorType() const;
// Disallow copy and assignment.
Transaction(const Transaction&) = delete;
Transaction& operator=(const Transaction&) = delete;
private:
// Sets the status within the DML RequestReplayState for the currently
// executing DML. If the current request failed due to a registration error
// (sequence out of order or request hash mismatch), this will be a no-op.
// When a DML replay request is received this saved status will be replayed
// instead of repeating the execution of that operation.
void SetDmlRequestReplayStatus(const absl::Status& status);
// Invalidates the backend transaction. This should only ever be invoked due
// to non-recoverable errors.
absl::Status Invalidate();
// Returns true if the transaction is in the given state.
bool HasState(const backend::ReadWriteTransaction::State& state) const;
// The underlying backend transaction.
std::variant<std::unique_ptr<backend::ReadWriteTransaction>,
std::unique_ptr<backend::ReadOnlyTransaction>>
transaction_;
// The query engine for executing queries.
const backend::QueryEngine* query_engine_;
// True if this transaction should not be reused. In such a case, proto
// representation of this transaction will not return transaction id.
bool is_single_use_;
// True if proto representation of this transaction should return the
// timestamp at which reads are be performed by the backend transaction.
bool return_read_timestamp_;
// Usage type (single-use or multi-use) for this transaction.
const Usage usage_type_;
// Transaction type that determines read, write and partition use.
const Type type_;
// Options for the transaction from the original rpc request.
const spanner_api::TransactionOptions options_;
// Mutex to guard state below.
mutable absl::Mutex mu_;
// True if this transaction has been invalidated.
bool closed_ ABSL_GUARDED_BY(mu_) = false;
// Previous outcome of the transaction.
absl::Status status_ ABSL_GUARDED_BY(mu_);
// DML sequence number of the current request.
int64_t current_dml_seqno_ ABSL_GUARDED_BY(mu_);
// The type of DML request.
DMLErrorHandlingMode dml_error_mode_;
// DML sequence request map.
std::map<int64_t, RequestReplayState> dml_requests_ ABSL_GUARDED_BY(mu_);
};
// Return true if the given transaction selector requires the transaction to be
// returned in the response.
bool ShouldReturnTransaction(
const google::spanner::v1::TransactionSelector& selector);
} // namespace frontend
} // namespace emulator
} // namespace spanner
} // namespace google
#endif // THIRD_PARTY_CLOUD_SPANNER_EMULATOR_FRONTEND_ENTITIES_TRANSACTIONS_H_