aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/CursorManager.cpp64
-rw-r--r--src/InMemoryBackend.cpp55
-rw-r--r--src/RelationalNT.cpp37
-rw-r--r--src/SqliteBackend.cpp123
-rw-r--r--src/TupleCodec.cpp72
5 files changed, 313 insertions, 38 deletions
diff --git a/src/CursorManager.cpp b/src/CursorManager.cpp
index f80ac54..6a44767 100644
--- a/src/CursorManager.cpp
+++ b/src/CursorManager.cpp
@@ -1,50 +1,66 @@
#include "CursorManager.h"
-
-#include <numeric>
+#include "TupleCodec.h"
namespace nt
{
- static std::string JoinPath(const std::vector<std::string>& path)
+ CursorManager::CursorManager(IStorageBackend& backend)
+ : backend_(backend)
+ {}
+
+ void CursorManager::LoadPage(cursor* c, const std::vector<std::string>& path)
{
- return std::accumulate(
- std::next(path.begin()), path.end(), path.front(),
- [](std::string a, const std::string& b) { return std::move(a) + "/" + b; });
+ auto hashes = backend_.TupleHashes(path, c->fetch_offset, PAGE_SIZE);
+ c->page.clear();
+ c->page_position = 0;
+
+ for (const auto& hash : hashes)
+ {
+ auto bytes = backend_.Get(hash);
+ if (!bytes) continue;
+ c->page.emplace_back(TupleCodec::Deserialize(*bytes));
+ }
+
+ c->fetch_offset += hashes.size();
+ if (hashes.size() < PAGE_SIZE)
+ c->exhausted = true;
}
CursorManager::cursor* CursorManager::Open(HandlerManager::handle* handle)
{
if (handle == nullptr || handle->object == nullptr)
return nullptr;
-
if (handle->object->head->type->label != RELATION)
return nullptr;
- const std::string key = JoinPath(handle->object->head->path);
- auto it = mock_store_.find(key);
- if (it == mock_store_.end())
- return nullptr;
-
auto* c = new cursor();
c->handle = handle;
- for (const auto& row : it->second)
- c->tuples.emplace_back(row);
- return c;
- }
+ LoadPage(c, handle->object->head->path);
- Tuple* CursorManager::Next(cursor* cursor)
- {
- if (cursor == nullptr || cursor->position >= cursor->tuples.size())
+ if (c->page.empty())
+ {
+ delete c;
return nullptr;
- return &cursor->tuples[cursor->position++];
+ }
+ return c;
}
- void CursorManager::Close(cursor* cursor)
+ Tuple* CursorManager::Next(cursor* c)
{
- delete cursor;
+ if (c == nullptr) return nullptr;
+
+ if (c->page_position < c->page.size())
+ return &c->page[c->page_position++];
+
+ if (c->exhausted) return nullptr;
+
+ LoadPage(c, c->handle->object->head->path);
+ if (c->page.empty()) return nullptr;
+
+ return &c->page[c->page_position++];
}
- void CursorManager::MockInsert(std::vector<std::string> path, std::vector<Attribute> tuple)
+ void CursorManager::Close(cursor* c)
{
- mock_store_[JoinPath(path)].push_back(std::move(tuple));
+ delete c;
}
}
diff --git a/src/InMemoryBackend.cpp b/src/InMemoryBackend.cpp
new file mode 100644
index 0000000..4d637fe
--- /dev/null
+++ b/src/InMemoryBackend.cpp
@@ -0,0 +1,55 @@
+#include "InMemoryBackend.h"
+
+#include <picosha2.h>
+#include <numeric>
+
+namespace nt
+{
+ std::string InMemoryBackend::PathKey(const std::vector<std::string>& path)
+ {
+ return std::accumulate(
+ std::next(path.begin()), path.end(), path.front(),
+ [](std::string a, const std::string& b) { return std::move(a) + "/" + b; });
+ }
+
+ std::string InMemoryBackend::Put(std::vector<uint8_t> value)
+ {
+ const std::string hash = picosha2::hash256_hex_string(value.begin(), value.end());
+ kv_.emplace(hash, std::move(value));
+ return hash;
+ }
+
+ std::optional<std::vector<uint8_t>> InMemoryBackend::Get(const std::string& hash)
+ {
+ auto it = kv_.find(hash);
+ if (it == kv_.end())
+ return std::nullopt;
+ return it->second;
+ }
+
+ void InMemoryBackend::LinkTuple(const std::vector<std::string>& relation_path,
+ const std::string& tuple_hash)
+ {
+ auto& hashes = index_[PathKey(relation_path)];
+ for (const auto& h : hashes)
+ if (h == tuple_hash) return;
+ hashes.push_back(tuple_hash);
+ }
+
+ std::vector<std::string> InMemoryBackend::TupleHashes(
+ const std::vector<std::string>& relation_path,
+ std::size_t offset,
+ std::size_t limit)
+ {
+ auto it = index_.find(PathKey(relation_path));
+ if (it == index_.end() || offset >= it->second.size())
+ return {};
+
+ const auto& all = it->second;
+ auto begin = all.begin() + static_cast<ptrdiff_t>(offset);
+ auto end = (offset + limit <= all.size())
+ ? begin + static_cast<ptrdiff_t>(limit)
+ : all.end();
+ return { begin, end };
+ }
+}
diff --git a/src/RelationalNT.cpp b/src/RelationalNT.cpp
index 8661ffa..e82b8c9 100644
--- a/src/RelationalNT.cpp
+++ b/src/RelationalNT.cpp
@@ -7,6 +7,8 @@
#include "LifecycleManager.h"
#include "ObjectManager.h"
#include "PermissionsManager.h"
+#include "SqliteBackend.h"
+#include "TupleCodec.h"
#include "VM.h"
#include <iostream>
@@ -30,7 +32,7 @@ int main()
const nt::NT runtime;
std::cout << "RNT running: " << (runtime.IsRunning() ? "yes" : "no") << '\n';
- // --- Mock: register user in the object registry ---
+ // --- Mock: register villager in the object registry ---
nt::ObjectManager objects;
std::unique_ptr<nt::ObjectManager::object_type> type = std::make_unique<nt::ObjectManager::object_type>();
@@ -38,25 +40,32 @@ int main()
type->disposable = false;
type->methods = { OPEN, CLOSE };
- objects.Register({ "multigroups", "sakura", "relations", "user" }, std::make_unique<nt::ObjectManager::Relation>(), std::move(type));
-
- // --- Mock: populate the cursor store with three tuples ---
- nt::CursorManager cursors;
- cursors.MockInsert({ "multigroups", "sakura", "relations", "user" }, { { "name", "Alice" }, { "age", "30" } });
- cursors.MockInsert({ "multigroups", "sakura", "relations", "user" }, { { "name", "Bob" }, { "age", "25" } });
- cursors.MockInsert({ "multigroups", "sakura", "relations", "user"}, {{"name", "Carol"}, {"age", "35"}});
-
- // --- Open a handle on user through the full manager pipeline ---
+ objects.Register({ "multigroups", "sakura", "relations", "villager" }, std::make_unique<nt::ObjectManager::Relation>(), std::move(type));
+
+ // --- Populate SQLite backend with three tuples ---
+ nt::SqliteBackend backend;
+ const std::vector<std::string> path = { "multigroups", "sakura", "relations", "villager" };
+ auto store = [&](std::vector<nt::Attribute> attrs) {
+ auto bytes = nt::TupleCodec::Serialize(attrs);
+ auto hash = backend.Put(bytes);
+ backend.LinkTuple(path, hash);
+ };
+ store({ { "name", "Blathers" }, { "profession", "Museum Curator" } });
+ store({ { "name", "Rover" }, { "profession", "Traveller" } });
+ store({ { "name", "K.K." }, { "profession", "Artist" } });
+ nt::CursorManager cursors(backend);
+
+ // --- Open a handle on villager through the full manager pipeline ---
nt::PermissionsManager permissions;
nt::IdentityManager identities;
nt::LifecycleManager lifecycles;
nt::HandlerManager handler(objects, permissions, identities, lifecycles);
int connection = 1; // dummy connection context
- nt::HandlerManager::handle* handle = handler.Open({ "multigroups", "sakura", "relations", "user" }, &connection);
+ nt::HandlerManager::handle* handle = handler.Open({ "multigroups", "sakura", "relations", "villager" }, &connection);
if (handle == nullptr)
{
- std::cout << "Failed to open handle for user\n";
+ std::cout << "Failed to open handle for villager\n";
return 1;
}
@@ -64,7 +73,7 @@ int main()
nt::CursorManager::cursor* cursor = cursors.Open(handle);
if (cursor == nullptr)
{
- std::cout << "Failed to open cursor for user\n";
+ std::cout << "Failed to open cursor for villager\n";
handler.Close(handle);
return 1;
}
@@ -75,7 +84,7 @@ int main()
plan.scan_cursor = cursor;
nt::VM vm(cursors);
- std::cout << "Tuples in user:\n";
+ std::cout << "Tuples in villager:\n";
while (nt::Tuple* t = vm.Next(&plan))
{
std::cout << " ";
diff --git a/src/SqliteBackend.cpp b/src/SqliteBackend.cpp
new file mode 100644
index 0000000..4741033
--- /dev/null
+++ b/src/SqliteBackend.cpp
@@ -0,0 +1,123 @@
+#include "SqliteBackend.h"
+
+#include <picosha2.h>
+#include <numeric>
+#include <stdexcept>
+
+namespace nt
+{
+ static std::string JoinPath(const std::vector<std::string>& path)
+ {
+ return std::accumulate(
+ std::next(path.begin()), path.end(), path.front(),
+ [](std::string a, const std::string& b) { return std::move(a) + "/" + b; });
+ }
+
+ SqliteBackend::SqliteBackend(const std::string& path)
+ {
+ if (sqlite3_open(path.c_str(), &db_) != SQLITE_OK)
+ throw std::runtime_error(sqlite3_errmsg(db_));
+
+ const char* schema =
+ "CREATE TABLE IF NOT EXISTS kv ("
+ " hash TEXT PRIMARY KEY,"
+ " value BLOB NOT NULL"
+ ");"
+ "CREATE TABLE IF NOT EXISTS relation_index ("
+ " path TEXT NOT NULL,"
+ " tuple_hash TEXT NOT NULL,"
+ " UNIQUE(path, tuple_hash)"
+ ");";
+
+ char* err = nullptr;
+ if (sqlite3_exec(db_, schema, nullptr, nullptr, &err) != SQLITE_OK)
+ {
+ std::string msg(err);
+ sqlite3_free(err);
+ throw std::runtime_error(msg);
+ }
+ }
+
+ SqliteBackend::~SqliteBackend()
+ {
+ sqlite3_close(db_);
+ }
+
+ std::string SqliteBackend::PathKey(const std::vector<std::string>& path)
+ {
+ return JoinPath(path);
+ }
+
+ std::string SqliteBackend::Put(std::vector<uint8_t> value)
+ {
+ const std::string hash = picosha2::hash256_hex_string(value.begin(), value.end());
+
+ sqlite3_stmt* stmt = nullptr;
+ sqlite3_prepare_v2(db_,
+ "INSERT OR IGNORE INTO kv (hash, value) VALUES (?, ?)",
+ -1, &stmt, nullptr);
+ sqlite3_bind_text(stmt, 1, hash.c_str(), -1, SQLITE_TRANSIENT);
+ sqlite3_bind_blob(stmt, 2, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+
+ return hash;
+ }
+
+ std::optional<std::vector<uint8_t>> SqliteBackend::Get(const std::string& hash)
+ {
+ sqlite3_stmt* stmt = nullptr;
+ sqlite3_prepare_v2(db_,
+ "SELECT value FROM kv WHERE hash = ?",
+ -1, &stmt, nullptr);
+ sqlite3_bind_text(stmt, 1, hash.c_str(), -1, SQLITE_TRANSIENT);
+
+ std::optional<std::vector<uint8_t>> result;
+ if (sqlite3_step(stmt) == SQLITE_ROW)
+ {
+ const auto* blob = static_cast<const uint8_t*>(sqlite3_column_blob(stmt, 0));
+ int size = sqlite3_column_bytes(stmt, 0);
+ result = std::vector<uint8_t>(blob, blob + size);
+ }
+ sqlite3_finalize(stmt);
+ return result;
+ }
+
+ void SqliteBackend::LinkTuple(const std::vector<std::string>& relation_path,
+ const std::string& tuple_hash)
+ {
+ const std::string path_key = PathKey(relation_path);
+
+ sqlite3_stmt* stmt = nullptr;
+ sqlite3_prepare_v2(db_,
+ "INSERT OR IGNORE INTO relation_index (path, tuple_hash) VALUES (?, ?)",
+ -1, &stmt, nullptr);
+ sqlite3_bind_text(stmt, 1, path_key.c_str(), -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 2, tuple_hash.c_str(), -1, SQLITE_TRANSIENT);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ }
+
+ std::vector<std::string> SqliteBackend::TupleHashes(
+ const std::vector<std::string>& relation_path,
+ std::size_t offset,
+ std::size_t limit)
+ {
+ const std::string path_key = PathKey(relation_path);
+
+ sqlite3_stmt* stmt = nullptr;
+ sqlite3_prepare_v2(db_,
+ "SELECT tuple_hash FROM relation_index"
+ " WHERE path = ? ORDER BY rowid LIMIT ? OFFSET ?",
+ -1, &stmt, nullptr);
+ sqlite3_bind_text(stmt, 1, path_key.c_str(), -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int64(stmt, 2, static_cast<sqlite3_int64>(limit));
+ sqlite3_bind_int64(stmt, 3, static_cast<sqlite3_int64>(offset));
+
+ std::vector<std::string> hashes;
+ while (sqlite3_step(stmt) == SQLITE_ROW)
+ hashes.emplace_back(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)));
+ sqlite3_finalize(stmt);
+ return hashes;
+ }
+}
diff --git a/src/TupleCodec.cpp b/src/TupleCodec.cpp
new file mode 100644
index 0000000..9b76046
--- /dev/null
+++ b/src/TupleCodec.cpp
@@ -0,0 +1,72 @@
+#include "TupleCodec.h"
+
+#include <picosha2.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <numeric>
+
+namespace nt::TupleCodec
+{
+ static std::vector<Attribute> Sorted(std::vector<Attribute> attrs)
+ {
+ std::sort(attrs.begin(), attrs.end(),
+ [](const Attribute& a, const Attribute& b) { return a.name < b.name; });
+ return attrs;
+ }
+
+ std::string Hash(const std::vector<Attribute>& attrs)
+ {
+ auto bytes = Serialize(attrs);
+ return picosha2::hash256_hex_string(bytes.begin(), bytes.end());
+ }
+
+ std::vector<uint8_t> Serialize(const std::vector<Attribute>& attrs)
+ {
+ // Format: [uint32_le name_len][name][uint32_le value_len][value] ...
+ // Attributes are stored in sorted name order for determinism.
+ std::vector<uint8_t> out;
+ auto write_u32 = [&](uint32_t n) {
+ out.push_back(static_cast<uint8_t>(n));
+ out.push_back(static_cast<uint8_t>(n >> 8));
+ out.push_back(static_cast<uint8_t>(n >> 16));
+ out.push_back(static_cast<uint8_t>(n >> 24));
+ };
+
+ for (const auto& a : Sorted({ attrs.begin(), attrs.end() }))
+ {
+ write_u32(static_cast<uint32_t>(a.name.size()));
+ out.insert(out.end(), a.name.begin(), a.name.end());
+ write_u32(static_cast<uint32_t>(a.value.size()));
+ out.insert(out.end(), a.value.begin(), a.value.end());
+ }
+ return out;
+ }
+
+ std::vector<Attribute> Deserialize(const std::vector<uint8_t>& bytes)
+ {
+ auto read_u32 = [&](size_t pos) -> uint32_t {
+ return static_cast<uint32_t>(bytes[pos])
+ | (static_cast<uint32_t>(bytes[pos + 1]) << 8)
+ | (static_cast<uint32_t>(bytes[pos + 2]) << 16)
+ | (static_cast<uint32_t>(bytes[pos + 3]) << 24);
+ };
+
+ std::vector<Attribute> result;
+ size_t pos = 0;
+ while (pos + 8 <= bytes.size())
+ {
+ uint32_t name_len = read_u32(pos); pos += 4;
+ std::string name(reinterpret_cast<const char*>(bytes.data() + pos), name_len);
+ pos += name_len;
+
+ uint32_t value_len = read_u32(pos); pos += 4;
+ std::string value(reinterpret_cast<const char*>(bytes.data() + pos), value_len);
+ pos += value_len;
+
+ result.push_back({ std::move(name), std::move(value) });
+ }
+ return result;
+ }
+}