269 lines
8.4 KiB
C++
269 lines
8.4 KiB
C++
#pragma once
|
|
|
|
#include "arena_allocator.hpp"
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
/**
|
|
* @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<std::string_view> request_id_;
|
|
std::string_view leader_id_;
|
|
uint64_t read_version_ = 0;
|
|
std::vector<Precondition, ArenaStlAllocator<Precondition>> preconditions_;
|
|
std::vector<Operation, ArenaStlAllocator<Operation>> operations_;
|
|
|
|
public:
|
|
/**
|
|
* @brief Construct a new CommitRequest with default arena size.
|
|
*/
|
|
explicit CommitRequest()
|
|
: arena_(), preconditions_(ArenaStlAllocator<Precondition>(&arena_)),
|
|
operations_(ArenaStlAllocator<Operation>(&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<std::string_view> &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<const Precondition> preconditions() const { return preconditions_; }
|
|
|
|
/**
|
|
* @brief Get the operations.
|
|
* @return span of operations
|
|
*/
|
|
std::span<const Operation> 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<char>(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_;
|
|
}
|
|
}
|
|
}
|
|
};
|