Skip to content

Instantly share code, notes, and snippets.

@GavinRay97
Created February 8, 2023 15:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GavinRay97/a95a6495484c1f0fd3cfb55ee913c964 to your computer and use it in GitHub Desktop.
Save GavinRay97/a95a6495484c1f0fd3cfb55ee913c964 to your computer and use it in GitHub Desktop.
C++ Database Write-Ahead-Log simple proof of concept
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <span>
#include <string>
// A write-ahead log is a log of all the operations that have been performed on the database.
struct LogEntryHeader
{
enum class Type
{
INVALID = 0,
INSERT,
UPDATE,
DELETE,
BEGIN,
COMMIT,
ABORT,
NEW_PAGE
};
uint64_t entry_size;
Type type;
int64_t txn_id;
int64_t lsn;
int64_t prev_lsn;
union
{
struct
{
int64_t page_id;
int64_t slot_id;
int64_t tuple_size;
} insert;
struct
{
int64_t page_id;
int64_t slot_id;
int64_t tuple_size;
int64_t new_tuple_size;
} update;
struct
{
int64_t page_id;
int64_t slot_id;
int64_t tuple_size;
} delete_;
struct
{
int64_t prev_page_id;
} new_page;
};
};
struct LogEntry
{
LogEntryHeader header;
std::optional<std::span<const char>> tuple;
std::optional<std::span<const char>> new_tuple;
};
// Read a log entry from the given pointer
LogEntry
read_log_entry(const char* ptr)
{
auto* header = (LogEntryHeader*) ptr;
switch (header->type)
{
case LogEntryHeader::Type::INSERT: {
auto tuple_size = header->insert.tuple_size;
const auto* tuple_ptr = ptr + sizeof(LogEntryHeader) + sizeof(header->insert);
auto tuple = std::span<const char>(tuple_ptr, tuple_size);
return LogEntry { .header = *header, .tuple = tuple };
}
case LogEntryHeader::Type::UPDATE: {
auto tuple_size = header->update.tuple_size;
const auto* tuple_ptr = ptr + sizeof(LogEntryHeader) + sizeof(header->update);
auto tuple = std::span<const char>(tuple_ptr, tuple_size);
auto new_tuple_size = header->update.new_tuple_size;
const auto* new_tuple_ptr = tuple_ptr + tuple_size;
auto new_tuple = std::span<const char>(new_tuple_ptr, new_tuple_size);
return LogEntry { .header = *header, .tuple = tuple, .new_tuple = new_tuple };
}
case LogEntryHeader::Type::DELETE: {
auto tuple_size = header->delete_.tuple_size;
const auto* tuple_ptr = ptr + sizeof(LogEntryHeader) + sizeof(header->delete_);
auto tuple = std::span<const char>(tuple_ptr, tuple_size);
return LogEntry { .header = *header, .tuple = tuple };
}
case LogEntryHeader::Type::NEW_PAGE:
case LogEntryHeader::Type::BEGIN:
case LogEntryHeader::Type::COMMIT:
case LogEntryHeader::Type::ABORT: {
return LogEntry { .header = *header };
}
default: {
assert(false);
return LogEntry { .header = *header };
}
}
};
// Write a log entry to the given pointer
void
write_log_entry(char* ptr, const LogEntry& entry)
{
auto* header = (LogEntryHeader*) ptr;
*header = entry.header;
switch (entry.header.type)
{
case LogEntryHeader::Type::INSERT: {
auto tuple_size = entry.header.insert.tuple_size;
auto* tuple_ptr = ptr + sizeof(LogEntryHeader) + sizeof(entry.header.insert);
std::memcpy(tuple_ptr, entry.tuple->data(), tuple_size);
break;
}
case LogEntryHeader::Type::UPDATE: {
auto tuple_size = entry.header.update.tuple_size;
auto* tuple_ptr = ptr + sizeof(LogEntryHeader) + sizeof(entry.header.update);
std::memcpy(tuple_ptr, entry.tuple->data(), tuple_size);
auto new_tuple_size = entry.header.update.new_tuple_size;
auto* new_tuple_ptr = tuple_ptr + tuple_size;
std::memcpy(new_tuple_ptr, entry.new_tuple->data(), new_tuple_size);
break;
}
case LogEntryHeader::Type::DELETE: {
auto tuple_size = entry.header.delete_.tuple_size;
auto* tuple_ptr = ptr + sizeof(LogEntryHeader) + sizeof(entry.header.delete_);
std::memcpy(tuple_ptr, entry.tuple->data(), tuple_size);
break;
}
case LogEntryHeader::Type::NEW_PAGE:
case LogEntryHeader::Type::BEGIN:
case LogEntryHeader::Type::COMMIT:
case LogEntryHeader::Type::ABORT: {
break;
}
default: {
assert(false);
break;
}
}
};
int
main()
{
// Create a log entry
LogEntry entry;
entry.header.type = LogEntryHeader::Type::INSERT;
entry.header.entry_size = sizeof(LogEntryHeader) + sizeof(entry.header.insert) + 10;
entry.header.txn_id = 1;
entry.header.lsn = 1;
entry.header.prev_lsn = 0;
entry.header.insert.page_id = 1;
entry.header.insert.slot_id = 1;
entry.header.insert.tuple_size = 10;
entry.tuple = std::span<const char>("0123456789", 10);
// Write the log entry to a buffer
char buffer[1024];
write_log_entry(buffer, entry);
// Read the log entry from the buffer
auto read_entry = read_log_entry(buffer);
// Check that the log entry was read correctly
assert(read_entry.header.type == LogEntryHeader::Type::INSERT);
assert(read_entry.header.entry_size == sizeof(LogEntryHeader) + sizeof(entry.header.insert) + 10);
assert(read_entry.header.txn_id == 1);
assert(read_entry.header.lsn == 1);
assert(read_entry.header.prev_lsn == 0);
assert(read_entry.header.insert.page_id == 1);
assert(read_entry.header.insert.slot_id == 1);
assert(read_entry.header.insert.tuple_size == 10);
assert(read_entry.tuple.has_value());
assert(read_entry.tuple->size() == 10);
assert(std::memcmp(read_entry.tuple->data(), "0123456789", 10) == 0);
assert(!read_entry.new_tuple.has_value());
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment