#pragma once #include "arena_allocator.hpp" #include #include #include #include /** * @brief Represents a precondition for optimistic concurrency control. * * Preconditions allow transactions to verify that the data they read * during transaction preparation is still valid at commit time. This * enables optimistic concurrency control by detecting conflicting * modifications from other transactions. */ struct Precondition { /** * @brief Type of precondition check to perform. */ enum class Type { PointRead, ///< Check existence/content of a single key RangeRead ///< Check consistency of a key range }; Type type; ///< Type of precondition check uint64_t version; ///< Expected version number (0 uses read_version from request) std::string_view begin; ///< Begin key (or single key for PointRead) std::string_view end; ///< End key for RangeRead (unused for PointRead) }; /** * @brief Represents a mutation operation in a commit request. * * Operations define the actual changes to be applied to the database * if all preconditions pass. Operations are applied in the order they * appear in the commit request. */ struct Operation { /** * @brief Type of mutation operation to perform. */ enum class Type { Write, ///< Set a key-value pair Delete, ///< Remove a single key RangeDelete ///< Remove all keys in a range }; Type type; ///< Type of operation std::string_view param1; ///< Key for Write/Delete, begin key for RangeDelete std::string_view param2; ///< Value for Write, end key for RangeDelete (unused for Delete) }; /** * @brief Format-agnostic commit request data structure. * * All string data is stored in the arena allocator to ensure efficient * memory management and ownership. This class has no knowledge of any * specific serialization formats or encoding schemes. */ class CommitRequest { private: ArenaAllocator arena_; std::optional request_id_; std::string_view leader_id_; uint64_t read_version_ = 0; std::vector> preconditions_; std::vector> operations_; public: /** * @brief Construct a new CommitRequest with default arena size. */ explicit CommitRequest() : arena_(), preconditions_(ArenaStlAllocator(&arena_)), operations_(ArenaStlAllocator(&arena_)) {} // Move constructor CommitRequest(CommitRequest &&other) noexcept : arena_(std::move(other.arena_)), request_id_(other.request_id_), leader_id_(other.leader_id_), read_version_(other.read_version_), preconditions_(std::move(other.preconditions_)), operations_(std::move(other.operations_)) {} // Move assignment operator CommitRequest &operator=(CommitRequest &&other) noexcept { if (this != &other) { arena_ = std::move(other.arena_); request_id_ = other.request_id_; leader_id_ = other.leader_id_; read_version_ = other.read_version_; preconditions_ = std::move(other.preconditions_); operations_ = std::move(other.operations_); } return *this; } // Copy constructor and assignment are deleted CommitRequest(const CommitRequest &) = delete; CommitRequest &operator=(const CommitRequest &) = delete; /** * @brief Get the request ID if present. * @return Optional request ID */ const std::optional &request_id() const { return request_id_; } /** * @brief Get the leader ID. * @return Leader ID string view */ std::string_view leader_id() const { return leader_id_; } /** * @brief Get the read version. * @return Read version number */ uint64_t read_version() const { return read_version_; } /** * @brief Get the preconditions. * @return span of preconditions */ std::span preconditions() const { return preconditions_; } /** * @brief Get the operations. * @return span of operations */ std::span operations() const { return operations_; } /** * @brief Get the total allocated bytes in the arena. * @return Total allocated bytes */ size_t total_allocated() const { return arena_.total_allocated(); } /** * @brief Get the used bytes in the arena. * @return Used bytes */ size_t used_bytes() const { return arena_.used_bytes(); } /** * @brief Get access to the underlying arena allocator for debugging. * * @note This function is primarily used for testing and debugging. * Production code should prefer the specific accessor methods like * total_allocated() and used_bytes() instead of direct arena access. * * @return Reference to the arena allocator */ const ArenaAllocator &arena() const { return arena_; } /** * @brief Get access to the underlying arena allocator for allocation. * * @note This function exposes the internal arena for direct manipulation. * It should be used carefully and primarily for internal parser * implementation or testing purposes. * * @return Reference to the arena allocator */ ArenaAllocator &arena() { return arena_; } /** * @brief Reset the commit request for reuse. */ void reset() { arena_.reset(); request_id_.reset(); leader_id_ = {}; read_version_ = 0; preconditions_.clear(); operations_.clear(); } // Builder methods for setting data // Note: All string_view parameters must point to arena-allocated memory /** * @brief Set the optional request ID for this commit. * @param arena_allocated_request_id String view pointing to arena-allocated * memory */ void set_request_id(std::string_view arena_allocated_request_id) { request_id_ = arena_allocated_request_id; } /** * @brief Set the leader ID for consistency checks. * @param arena_allocated_leader_id String view pointing to arena-allocated * memory */ void set_leader_id(std::string_view arena_allocated_leader_id) { leader_id_ = arena_allocated_leader_id; } /** * @brief Set the read version for precondition validation. * @param read_version The snapshot version number */ void set_read_version(uint64_t read_version) { read_version_ = read_version; } /** * @brief Add a precondition to the commit request. * @param type Type of precondition (PointRead or RangeRead) * @param version Version number for the precondition check * @param arena_allocated_begin Begin key (or single key for PointRead) * @param arena_allocated_end End key for RangeRead (optional, empty for * PointRead) */ void add_precondition(Precondition::Type type, uint64_t version, std::string_view arena_allocated_begin, std::string_view arena_allocated_end = {}) { preconditions_.push_back(Precondition{type, version, arena_allocated_begin, arena_allocated_end}); } /** * @brief Add an operation to the commit request. * @param type Type of operation (Write, Delete, or RangeDelete) * @param arena_allocated_param1 Key for Write/Delete, begin key for * RangeDelete * @param arena_allocated_param2 Value for Write, end key for RangeDelete * (optional for Delete) */ void add_operation(Operation::Type type, std::string_view arena_allocated_param1, std::string_view arena_allocated_param2 = {}) { operations_.push_back( Operation{type, arena_allocated_param1, arena_allocated_param2}); } /** * @brief Copy a string into the arena and return a string_view. * Helper utility for external code that needs to copy data into arena memory. * @param str The string to copy * @return String view pointing to arena-allocated memory */ std::string_view copy_to_arena(std::string_view str) { if (str.empty()) { return {}; } char *arena_str = arena_.allocate(str.size()); std::memcpy(arena_str, str.data(), str.size()); return std::string_view(arena_str, str.size()); } /** * @brief Apply any post-processing logic after data has been populated. * This should be called after all data has been added to the request. */ void finalize() { // Fill in default read version for preconditions that don't specify one for (auto &precondition : preconditions_) { if (precondition.version == 0) { precondition.version = read_version_; } } } };