Created
February 8, 2023 15:28
-
-
Save GavinRay97/a95a6495484c1f0fd3cfb55ee913c964 to your computer and use it in GitHub Desktop.
C++ Database Write-Ahead-Log simple proof of concept
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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