// Copyright 2019 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
//
//     https://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 GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_TRANSACTION_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_TRANSACTION_H

#include "google/cloud/spanner/internal/transaction_impl.h"
#include "google/cloud/spanner/timestamp.h"
#include "google/cloud/spanner/version.h"
#include "absl/types/optional.h"
#include "google/spanner/v1/transaction.pb.h"
#include <chrono>
#include <memory>
#include <string>

namespace google {
namespace cloud {
namespace spanner_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
struct TransactionInternals;
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
}  // namespace spanner_internal

namespace spanner {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

/**
 * The representation of a Cloud Spanner transaction.
 *
 * A transaction is a set of reads and writes that execute atomically at a
 * single logical point in time across the columns/rows/tables in a database.
 * Those reads and writes are grouped by passing them the same `Transaction`.
 *
 * All reads/writes in the transaction must be executed within the same
 * session, and that session may have only one transaction active at a time.
 *
 * Spanner supports these transaction modes:
 *   - ReadOnly. Provides guaranteed consistency across several reads, but does
 *     not allow writes. Can be configured to read at timestamps in the past.
 *     Does not need to be committed and does not take locks.
 *   - ReadWrite. Supports reading and writing data at a single point in time.
 *     Uses pessimistic locking and, if necessary, two-phase commit. May abort,
 *     requiring the application to rerun.
 *   - SingleUse. A restricted form of a ReadOnly transaction where Spanner
 *     chooses the read timestamp.
 */
class Transaction {
 public:
  /**
   * Defines the isolation level for a transaction.
   *
   * This determines how concurrent transactions interact with each other and
   * what consistency guarantees are provided for read and write operations.
   *
   * @note This setting only applies to read-write transactions.
   *
   * See the `v1::TransactionOptions` proto for more details.
   *
   * @see https://docs.cloud.google.com/spanner/docs/isolation-levels
   */
  enum class IsolationLevel {
    /// The isolation level is not specified, using the backend default.
    kUnspecified,
    /**
     * All transactions appear as if they executed in a serial order.
     * This is the default isolation level for read-write transactions.
     */
    kSerializable,
    /**
     * All reads performed during the transaction observe a consistent snapshot
     * of the database. The transaction is only successfully committed in the
     * absence of conflicts between its updates and any concurrent updates
     * that have occurred since that snapshot. Consequently, in contrast to
     * `kSerializable` transactions, only write-write conflicts are detected in
     * repeatable read transactions.
     */
    kRepeatableRead,
  };

  /**
   * Options for ReadOnly transactions.
   */
  class ReadOnlyOptions {
   public:
    // Strong: Guarantees visibility of the effects of all transactions that
    // committed before the start of the reads.
    ReadOnlyOptions();

    // Exact Staleness: Executes all reads at `read_timestamp`.
    explicit ReadOnlyOptions(Timestamp read_timestamp);

    // Exact Staleness: Executes all reads at a timestamp `exact_staleness`
    // old. The actual timestamp is chosen soon after the reads are started.
    explicit ReadOnlyOptions(std::chrono::nanoseconds exact_staleness);

   private:
    friend Transaction;
    google::spanner::v1::TransactionOptions_ReadOnly ro_opts_;
  };

  /**
   * Read lock mode for ReadWrite transactions
   * The Spanner V1 Transaction proto classes have their own enum
   * implementations.
   * See google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode
   * This is a shorthand convenience for the developer.
   */
  enum class ReadLockMode {
    kUnspecified,
    kPessimistic,
    kOptimistic,
  };

  /**
   * Options for ReadWrite transactions.
   */
  class ReadWriteOptions {
   public:
    ReadWriteOptions();

    explicit ReadWriteOptions(ReadLockMode read_lock_mode);

    // A tag used for collecting statistics about the transaction.
    ReadWriteOptions& WithTag(absl::optional<std::string> tag);

    // Sets the isolation level for the transaction. This controls how the
    // transaction interacts with other concurrent transactions, primarily
    // regarding data consistency for reads and writes.
    // See `IsolationLevel` enum for possible values.
    ReadWriteOptions& WithIsolationLevel(IsolationLevel isolation_level);

   private:
    friend Transaction;
    google::spanner::v1::TransactionOptions_ReadWrite rw_opts_;
    absl::optional<std::string> tag_;
    absl::optional<IsolationLevel> isolation_level_;
  };

  /**
   * Options for "single-use", ReadOnly transactions, where Spanner chooses
   * the read timestamp, subject to user-provided bounds. This allows reading
   * without blocking.
   *
   * Because selection of the timestamp requires knowledge of which rows will
   * be read, a single-use transaction can only be used with one read. See
   * Client::Read() and Client::ExecuteQuery(). SingleUseOptions cannot be used
   * to construct an application-level Transaction.
   */
  class SingleUseOptions {
   public:
    // Strong or Exact Staleness: See ReadOnlyOptions.
    // NOLINTNEXTLINE(google-explicit-constructor)
    SingleUseOptions(ReadOnlyOptions opts);

    // Bounded Staleness: Executes all reads at a timestamp that is not
    // before `min_read_timestamp`.
    explicit SingleUseOptions(Timestamp min_read_timestamp);

    // Bounded Staleness: Executes all reads at a timestamp that is not
    // before `NOW - max_staleness`.
    explicit SingleUseOptions(std::chrono::nanoseconds max_staleness);

   private:
    friend Transaction;
    google::spanner::v1::TransactionOptions_ReadOnly ro_opts_;
  };

  /// @name Construction of read-only and read-write transactions.
  ///@{
  /**
   * @note This is a lazy evaluated operation. No RPCs are made as part of
   *     creating a `Transaction` object. Instead, the first request to the
   *     server (for example as part of a `ExecuteQuery()` call) will also
   *     create the transaction.
   */
  explicit Transaction(ReadOnlyOptions opts);
  /// @copydoc Transaction(ReadOnlyOptions)
  explicit Transaction(ReadWriteOptions opts);
  /// @copydoc Transaction(ReadOnlyOptions)
  Transaction(Transaction const& txn, ReadWriteOptions opts);
  ///@}

  ~Transaction();

  /// @name Regular value type, supporting copy, assign, move.
  ///@{
  Transaction(Transaction&&) = default;
  Transaction& operator=(Transaction&&) = default;
  Transaction(Transaction const&) = default;
  Transaction& operator=(Transaction const&) = default;
  ///@}

  /// @name Equality operators
  ///@{
  friend bool operator==(Transaction const& a, Transaction const& b) {
    return a.impl_ == b.impl_;
  }
  friend bool operator!=(Transaction const& a, Transaction const& b) {
    return !(a == b);
  }
  ///@}

 private:
  // Friendship for access by internal helpers.
  friend struct spanner_internal::TransactionInternals;
  struct SingleUseCommitTag {};

  // Construction of a single-use transaction.
  explicit Transaction(SingleUseOptions opts);
  // Construction of a single-use commit transaction.
  Transaction(ReadWriteOptions opts, SingleUseCommitTag);
  // Construction of a transaction with existing IDs.
  Transaction(std::string session_id, std::string transaction_id,
              bool route_to_leader, std::string transaction_tag);

  std::shared_ptr<spanner_internal::TransactionImpl> impl_;
};

/**
 * Create a read-only transaction configured with @p opts.
 *
 * @copydoc Transaction::Transaction(Transaction::ReadOnlyOptions)
 */
inline Transaction MakeReadOnlyTransaction(
    Transaction::ReadOnlyOptions opts = {}) {
  return Transaction(std::move(opts));
}

/**
 * Create a read-write transaction configured with @p opts.
 *
 * @copydoc Transaction::Transaction(Transaction::ReadOnlyOptions)
 */
inline Transaction MakeReadWriteTransaction(
    Transaction::ReadWriteOptions opts = {}) {
  return Transaction(std::move(opts));
}

/**
 * Create a read-write transaction configured with @p opts, and sharing
 * lock priority with @p txn. This should be used when rerunning an aborted
 * transaction, so that the new attempt has a slightly better chance of
 * success.
 */
inline Transaction MakeReadWriteTransaction(
    Transaction const& txn, Transaction::ReadWriteOptions opts = {}) {
  return Transaction(txn, std::move(opts));
}

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
}  // namespace spanner

namespace spanner_internal {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

struct TransactionInternals {
  template <typename T>
  static spanner::Transaction MakeSingleUseTransaction(T&& opts) {
    // Requires that `opts` is implicitly convertible to SingleUseOptions.
    spanner::Transaction::SingleUseOptions su_opts = std::forward<T>(opts);
    return spanner::Transaction(std::move(su_opts));
  }

  static spanner::Transaction MakeSingleUseCommitTransaction(
      spanner::Transaction::ReadWriteOptions opts) {
    return spanner::Transaction(std::move(opts),
                                spanner::Transaction::SingleUseCommitTag{});
  }

  template <typename Functor>
  // Pass `txn` by value, despite being used only once. This avoids the
  // possibility of `txn` being destroyed by `f` before `Visit()` can
  // return. Therefore, ...
  // NOLINTNEXTLINE(performance-unnecessary-value-param)
  static VisitInvokeResult<Functor> Visit(spanner::Transaction txn,
                                          Functor&& f) {
    return txn.impl_->Visit(std::forward<Functor>(f));
  }

  static spanner::Transaction MakeTransactionFromIds(
      std::string session_id, std::string transaction_id, bool route_to_leader,
      std::string transaction_tag);
};

template <typename T>
spanner::Transaction MakeSingleUseTransaction(T&& opts) {
  return TransactionInternals::MakeSingleUseTransaction(std::forward<T>(opts));
}

inline spanner::Transaction MakeSingleUseCommitTransaction(
    spanner::Transaction::ReadWriteOptions opts) {
  return TransactionInternals::MakeSingleUseCommitTransaction(std::move(opts));
}

template <typename Functor>
// Pass `txn` by value, despite being used only once. This avoids the
// possibility of `txn` being destroyed by `f` before `Visit()` can
// return. Therefore, ...
// NOLINTNEXTLINE(performance-unnecessary-value-param)
VisitInvokeResult<Functor> Visit(spanner::Transaction txn, Functor&& f) {
  return TransactionInternals::Visit(std::move(txn), std::forward<Functor>(f));
}

inline spanner::Transaction MakeTransactionFromIds(
    std::string session_id, std::string transaction_id, bool route_to_leader,
    std::string transaction_tag) {
  return TransactionInternals::MakeTransactionFromIds(
      std::move(session_id), std::move(transaction_id), route_to_leader,
      std::move(transaction_tag));
}

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
}  // namespace spanner_internal
}  // namespace cloud
}  // namespace google

#endif  // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_TRANSACTION_H
