Files
weaseldb/src/commit_request.hpp

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_;
}
}
}
};