diff options
| author | Estevan Castilho <tevo@riajuu.net> | 2026-07-02 00:31:57 +0000 |
|---|---|---|
| committer | Estevan Castilho <tevo@riajuu.net> | 2026-07-02 00:31:57 +0000 |
| commit | ab70e5461b98ddf2b56268c53c171cab89157027 (patch) | |
| tree | 3e57218edacc8e1366e0d1a86bdb922af7e51dca /src | |
| parent | a4f6637601548a7158869ad155f29db50b151823 (diff) | |
| download | RNT-ab70e5461b98ddf2b56268c53c171cab89157027.tar.gz RNT-ab70e5461b98ddf2b56268c53c171cab89157027.zip | |
Nuke the old code
Diffstat (limited to 'src')
| -rw-r--r-- | src/CursorManager.cpp | 131 | ||||
| -rw-r--r-- | src/HandlerManager.cpp | 59 | ||||
| -rw-r--r-- | src/IdentityManager.cpp | 11 | ||||
| -rw-r--r-- | src/InMemoryBackend.cpp | 18 | ||||
| -rw-r--r-- | src/LifecycleManager.cpp | 143 | ||||
| -rw-r--r-- | src/Merkle.cpp | 552 | ||||
| -rw-r--r-- | src/MultigroupCodec.cpp | 71 | ||||
| -rw-r--r-- | src/NamespaceReferenceManager.cpp | 110 | ||||
| -rw-r--r-- | src/ObjectManager.cpp | 40 | ||||
| -rw-r--r-- | src/PermissionsManager.cpp | 17 | ||||
| -rw-r--r-- | src/RNT_C_API.cpp | 1057 | ||||
| -rw-r--r-- | src/RelationalNT.cpp | 113 | ||||
| -rw-r--r-- | src/SqliteBackend.cpp | 55 | ||||
| -rw-r--r-- | src/TupleCodec.cpp | 67 | ||||
| -rw-r--r-- | src/VM.cpp | 99 |
15 files changed, 0 insertions, 2543 deletions
diff --git a/src/CursorManager.cpp b/src/CursorManager.cpp deleted file mode 100644 index 0d71b1c..0000000 --- a/src/CursorManager.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "CursorManager.h" -#include "Merkle.h" -#include "ObjectManager.h" -#include "TupleCodec.h" - -namespace nt { - CursorManager::CursorManager(IStorageBackend& backend, LifecycleManager* lifecycles, - ObjectManager* objects) - : backend_(backend), lifecycles_(lifecycles), objects_(objects) { - } - - // Finds the /system/snapshots/<H> entry that owns a handle's relation, or - // nullptr when the relation lives outside the snapshot namespace (tests use - // synthetic paths like {"relations", "villager"}). - static ObjectManager::registry* find_parent_snapshot(ObjectManager& objects, - HandlerManager::handle* handle) { - if (handle == nullptr || handle->object == nullptr || handle->object->head == nullptr) - return nullptr; - - const auto& p = handle->object->head->path; - if (p.size() != 5) - return nullptr; - if (p[0] != "system" || p[1] != "snapshots" || p[3] != "relations") - return nullptr; - - return objects.Find({p[0], p[1], p[2]}); - } - - void CursorManager::LoadPage(cursor* c) { - auto entries = Merkle<Hash32>::Page(backend_, c->merkle_root, c->fetch_offset, PAGE_SIZE); - c->page.clear(); - c->page_position = 0; - - for (const auto& entry : entries) { - auto bytes = backend_.Get(bin_to_hex(entry.payload)); - if (!bytes) - continue; - c->page.emplace_back(TupleCodec::Deserialize(*bytes)); - } - - c->fetch_offset += entries.size(); - if (entries.size() < PAGE_SIZE) - c->exhausted = true; - } - - CursorManager::cursor* CursorManager::Open(HandlerManager::handle* handle, - const std::string& merkle_root) { - if (handle == nullptr || handle->object == nullptr) - return nullptr; - - const auto label = handle->object->head->type->label; - - auto pin_snapshot = [&](cursor* c) { - if (lifecycles_ == nullptr || objects_ == nullptr) - return; - auto* snap = find_parent_snapshot(*objects_, handle); - if (snap == nullptr) - return; - lifecycles_->Pin(snap); - c->pinned_snapshot = snap; - }; - - if (label == EPHEMERAL_RELATION) { - // Cursor is created exhausted — the JOIN operator writes args and - // resets the state before the first probe. The generator is copied - // off the registry-owned ephemeral_object_type so Next() never has - // to dereference the handle again. - auto* etype = static_cast<ObjectManager::ephemeral_object_type*>( - handle->object->head->type.get()); - - auto* c = new cursor(); - c->is_ephemeral = true; - c->generator = etype->generator; - c->exhausted = true; - pin_snapshot(c); - return c; - } - - if (label != RELATION) - return nullptr; - - auto* c = new cursor(); - c->merkle_root = merkle_root; - pin_snapshot(c); - - if (merkle_root.empty()) { - // Empty relation: valid but immediately exhausted. - c->exhausted = true; - return c; - } - - LoadPage(c); - return c; - } - - Tuple* CursorManager::Next(cursor* c) { - if (c == nullptr) - return nullptr; - - if (c->page_position < c->page.size()) - return &c->page[c->page_position++]; - - if (c->exhausted) - return nullptr; - - if (c->is_ephemeral) { - c->page = c->generator(c->args, c->fetch_offset, PAGE_SIZE); - c->page_position = 0; - c->fetch_offset += c->page.size(); - if (c->page.size() < PAGE_SIZE) - c->exhausted = true; - if (c->page.empty()) - return nullptr; - return &c->page[c->page_position++]; - } - - LoadPage(c); - if (c->page.empty()) - return nullptr; - return &c->page[c->page_position++]; - } - - void CursorManager::Close(cursor* c) { - if (c == nullptr) { - return; - } - if (lifecycles_ != nullptr && c->pinned_snapshot != nullptr) - lifecycles_->Unpin(c->pinned_snapshot); - delete c; - } -} // namespace nt diff --git a/src/HandlerManager.cpp b/src/HandlerManager.cpp deleted file mode 100644 index 505b393..0000000 --- a/src/HandlerManager.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "HandlerManager.h" - -#include "IdentityManager.h" -#include "LifecycleManager.h" -#include "NamespaceReferenceManager.h" -#include "PermissionsManager.h" - -namespace nt { - void HandlerManager::DeallocateHandle(struct handle* handle) { - delete handle; - } - - HandlerManager::HandlerManager(ObjectManager& objects, PermissionsManager& permissions, - IdentityManager& identities, LifecycleManager& lifecycles, - NamespaceReferenceManager& references) - : objects_(objects), permissions_(permissions), identities_(identities), - lifecycles_(lifecycles), references_(references) { - } - - struct HandlerManager::handle* HandlerManager::Open(std::vector<std::string> object_path, - void* connection_context) { - const auto resolved = references_.Resolve(std::move(object_path)); - ObjectManager::registry* retrieved_object = objects_.Find(resolved); - if (permissions_.Access(retrieved_object, (const void*)connection_context) && - identities_.CanOpen(retrieved_object) && lifecycles_.Contention(retrieved_object)) { - // Starts monitoring this guy - lifecycles_.Monitor(retrieved_object); - return new handle{retrieved_object, connection_context}; // Return a successful handle - } else { - // Return something more algebraic instead of null or an int - return nullptr; - }; - } - - bool HandlerManager::Close(struct handle* handle) { - if (handle == nullptr) { - return false; - } - // Somehow grab the object pointer and connection (maybe drop it...) from the handle - ObjectManager::registry* object = handle->object; - void* connection_context = handle->connection_context; - // Checking for permissions here may be relevant. - // What if we have a handle but no permission? Spooky - if (permissions_.Access(object, connection_context) && identities_.CanClose(object) - // TODO: I think that in this case we do not need to worry about contention. - // But be careful to evaluate this scenario later. - //&& lifecycles_.Contention(object) - ) { - // Stops monitoring this guy - lifecycles_.Unmonitor(object); - DeallocateHandle(handle); - return true; // Return a successful handle - } else { - // Return something more algebraic instead of null or an int. - // It's kinda bad to not even report what went wrong. - return false; - }; - } -} // namespace nt diff --git a/src/IdentityManager.cpp b/src/IdentityManager.cpp deleted file mode 100644 index 9b131a9..0000000 --- a/src/IdentityManager.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "IdentityManager.h" - -namespace nt { - const bool IdentityManager::CanOpen(ObjectManager::registry* object) { - return object != nullptr; - } - - const bool IdentityManager::CanClose(ObjectManager::registry* object) { - return object != nullptr; - } -} // namespace nt diff --git a/src/InMemoryBackend.cpp b/src/InMemoryBackend.cpp deleted file mode 100644 index 8aebc65..0000000 --- a/src/InMemoryBackend.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "InMemoryBackend.h" - -#include <picosha2.h> - -namespace nt { - 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; - } -} // namespace nt diff --git a/src/LifecycleManager.cpp b/src/LifecycleManager.cpp deleted file mode 100644 index 4a12e09..0000000 --- a/src/LifecycleManager.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "LifecycleManager.h" - -#include "Merkle.h" -#include "Types.h" - -#include <vector> - -namespace nt { - LifecycleManager::LifecycleManager(ObjectManager& objects, IStorageBackend& storage) - : objects_(objects), storage_(storage) { - } - - void LifecycleManager::Monitor(ObjectManager::registry* object) { - if (object != nullptr) - ++object->head->handle_count; - } - - void LifecycleManager::Unmonitor(ObjectManager::registry* object) { - if (object == nullptr || object->head->handle_count == 0) - return; - --object->head->handle_count; - TryCollect(object); - } - - void LifecycleManager::Pin(ObjectManager::registry* object) { - if (object != nullptr) - ++object->head->reference_count; - } - - void LifecycleManager::Unpin(ObjectManager::registry* object) { - if (object == nullptr || object->head->reference_count == 0) - return; - --object->head->reference_count; - TryCollect(object); - } - - bool LifecycleManager::IsEligibleForGC(ObjectManager::registry* object) const { - if (object == nullptr || object->head == nullptr) - return false; - return object->head->handle_count == 0 && object->head->reference_count == 0; - } - - const bool LifecycleManager::Contention(ObjectManager::registry* object) { - return object != nullptr; - } - - void LifecycleManager::TryCollect(ObjectManager::registry* object) { - if (!IsEligibleForGC(object)) - return; - if (object->head == nullptr || object->head->type == nullptr) - return; - - // Named-lifetime types are managed explicitly and must not auto-collect: - // BRANCH — named branches persist until a future explicit-delete API - // tears them down. Their counters going to zero just means - // nobody currently holds them open. - // SESSION — rnt_session_close is the sole removal path. - // Everything else (MULTIGROUP, BRANCH_TREE, RELATION, - // EPHEMERAL_RELATION, TRANSACTION) is purely structurally referenced - // and is GC-eligible by counters alone. - const auto label = object->head->type->label; - if (label == BRANCH || label == SESSION) - return; - - if (label == MULTIGROUP) - CascadeMultigroup(object); - if (label == BRANCH_TREE) - CascadeBranchTree(object); - - objects_.Unregister(object->head->path); - } - - void LifecycleManager::CascadeMultigroup(ObjectManager::registry* multigroup) { - if (multigroup == nullptr || multigroup->head == nullptr) - return; - const auto& mg_path = multigroup->head->path; - if (mg_path.size() < 3) - return; - - // /system/snapshots/<hash>/relations/<n> entries are the relation - // children pinned by this multigroup. Collect first, mutate after — - // Unpin can Unregister the entry we're standing on if its counters - // were already zero, and iteration must outlive that mutation. - std::vector<ObjectManager::registry*> children; - for (auto* cur = objects_.entries.get(); cur != nullptr; cur = cur->next.get()) { - const auto& p = cur->head->path; - if (p.size() != mg_path.size() + 2) - continue; - - bool prefix_match = true; - for (size_t i = 0; i < mg_path.size(); ++i) - if (p[i] != mg_path[i]) { - prefix_match = false; - break; - } - if (!prefix_match) - continue; - if (p[mg_path.size()] != "relations") - continue; - - children.push_back(cur); - } - - for (auto* child : children) - Unpin(child); - } - - void LifecycleManager::CascadeBranchTree(ObjectManager::registry* branch_tree) { - if (branch_tree == nullptr || branch_tree->head == nullptr) - return; - - auto* bt = dynamic_cast<ObjectManager::BranchTree*>(branch_tree->object.get()); - if (bt == nullptr || bt->merkle_root.empty()) - return; - - // Page through the (mg_name, mg_hash) tree and collect each referenced - // multigroup before unpinning. Decoupling iteration from mutation - // keeps us safe against Unpin cascading further GC under our feet. - std::vector<ObjectManager::registry*> mgs; - constexpr size_t kPageSize = 1024; - size_t offset = 0; - while (true) { - auto page = Merkle<std::string>::Page(storage_, bt->merkle_root, offset, kPageSize); - if (page.empty()) - break; - for (const auto& entry : page) { - static const Hash32 zero{}; - if (entry.payload == zero) - continue; - const std::string mg_hash = bin_to_hex(entry.payload); - auto* mg_entry = objects_.Find({"system", "snapshots", mg_hash}); - if (mg_entry) - mgs.push_back(mg_entry); - } - if (page.size() < kPageSize) - break; - offset += page.size(); - } - - for (auto* mg : mgs) - Unpin(mg); - } -} // namespace nt diff --git a/src/Merkle.cpp b/src/Merkle.cpp deleted file mode 100644 index 09329f9..0000000 --- a/src/Merkle.cpp +++ /dev/null @@ -1,552 +0,0 @@ -#include "Merkle.h" - -#include <algorithm> -#include <cassert> -#include <cstring> -#include <stdexcept> - -namespace nt { - - // ── Wire format constants ──────────────────────────────────────────────────── - - static constexpr uint8_t TAG_LEAF = 0x4C; // 'L' - static constexpr uint8_t TAG_INTERNAL = 0x49; // 'I' - - // ── Byte-order helpers ─────────────────────────────────────────────────────── - - static void write_u32(std::vector<uint8_t>& buf, uint32_t v) { - buf.push_back(static_cast<uint8_t>(v >> 24)); - buf.push_back(static_cast<uint8_t>(v >> 16)); - buf.push_back(static_cast<uint8_t>(v >> 8)); - buf.push_back(static_cast<uint8_t>(v)); - } - - static void write_u64(std::vector<uint8_t>& buf, uint64_t v) { - buf.push_back(static_cast<uint8_t>(v >> 56)); - buf.push_back(static_cast<uint8_t>(v >> 48)); - buf.push_back(static_cast<uint8_t>(v >> 40)); - buf.push_back(static_cast<uint8_t>(v >> 32)); - buf.push_back(static_cast<uint8_t>(v >> 24)); - buf.push_back(static_cast<uint8_t>(v >> 16)); - buf.push_back(static_cast<uint8_t>(v >> 8)); - buf.push_back(static_cast<uint8_t>(v)); - } - - static uint32_t read_u32(const uint8_t* p) { - return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) | (uint32_t(p[2]) << 8) | - uint32_t(p[3]); - } - - static uint64_t read_u64(const uint8_t* p) { - return (uint64_t(p[0]) << 56) | (uint64_t(p[1]) << 48) | (uint64_t(p[2]) << 40) | - (uint64_t(p[3]) << 32) | (uint64_t(p[4]) << 24) | (uint64_t(p[5]) << 16) | - (uint64_t(p[6]) << 8) | uint64_t(p[7]); - } - - // ── Hex / binary conversion (free, key-agnostic) ───────────────────────────── - - Hash32 hex_to_bin(const std::string& hex) { - if (hex.size() != 64) - throw std::invalid_argument("Merkle: hash must be exactly 64 hex characters, got " + - std::to_string(hex.size())); - - Hash32 out{}; - auto nibble = [](char c) -> uint8_t { - if (c >= '0' && c <= '9') - return static_cast<uint8_t>(c - '0'); - if (c >= 'a' && c <= 'f') - return static_cast<uint8_t>(c - 'a' + 10); - if (c >= 'A' && c <= 'F') - return static_cast<uint8_t>(c - 'A' + 10); - throw std::invalid_argument(std::string("Merkle: invalid hex character '") + c + "'"); - }; - for (size_t i = 0; i < 32; ++i) - out[i] = static_cast<uint8_t>((nibble(hex[2 * i]) << 4) | nibble(hex[2 * i + 1])); - return out; - } - - std::string bin_to_hex(const Hash32& bin) { - static const char HEX[] = "0123456789abcdef"; - std::string out(64, '0'); - for (size_t i = 0; i < 32; ++i) { - out[2 * i] = HEX[(bin[i] >> 4) & 0xF]; - out[2 * i + 1] = HEX[bin[i] & 0xF]; - } - return out; - } - - // ── Key codec: per-Key serialisation primitives ────────────────────────────── - - namespace { - - template <typename Key> struct KeyCodec; // primary template intentionally undefined - - template <> struct KeyCodec<Hash32> { - static void write(std::vector<uint8_t>& buf, const Hash32& k) { - buf.insert(buf.end(), k.begin(), k.end()); - } - // Returns key and advances *p_inout by the number of consumed bytes. - static Hash32 read(const uint8_t*& p) { - Hash32 k; - std::memcpy(k.data(), p, 32); - p += 32; - return k; - } - static size_t encoded_size(const Hash32&) { - return 32; - } - }; - - template <> struct KeyCodec<std::string> { - static void write(std::vector<uint8_t>& buf, const std::string& k) { - write_u32(buf, static_cast<uint32_t>(k.size())); - buf.insert(buf.end(), k.begin(), k.end()); - } - static std::string read(const uint8_t*& p) { - uint32_t len = read_u32(p); - p += 4; - std::string k(reinterpret_cast<const char*>(p), len); - p += len; - return k; - } - static size_t encoded_size(const std::string& k) { - return 4 + k.size(); - } - }; - - } // anonymous namespace - - // ── Node serialisation ─────────────────────────────────────────────────────── - - template <typename Key> std::vector<uint8_t> Merkle<Key>::encode_leaf(const LeafNode& n) { - std::vector<uint8_t> buf; - size_t payload_size = 0; - for (const auto& e : n.entries) - payload_size += KeyCodec<Key>::encoded_size(e.key) + 32; - buf.reserve(1 + 4 + payload_size); - - buf.push_back(TAG_LEAF); - write_u32(buf, static_cast<uint32_t>(n.entries.size())); - for (const auto& e : n.entries) { - KeyCodec<Key>::write(buf, e.key); - buf.insert(buf.end(), e.payload.begin(), e.payload.end()); - } - return buf; - } - - template <typename Key> - std::vector<uint8_t> Merkle<Key>::encode_internal(const InternalNode& n) { - std::vector<uint8_t> buf; - size_t payload_size = 0; - for (const auto& e : n.entries) - payload_size += 8 + KeyCodec<Key>::encoded_size(e.min_key) + 32; - buf.reserve(1 + 4 + payload_size); - - buf.push_back(TAG_INTERNAL); - write_u32(buf, static_cast<uint32_t>(n.entries.size())); - for (const auto& e : n.entries) { - write_u64(buf, e.leaf_count); - KeyCodec<Key>::write(buf, e.min_key); - buf.insert(buf.end(), e.child_hash.begin(), e.child_hash.end()); - } - return buf; - } - - template <typename Key> - typename Merkle<Key>::LeafNode Merkle<Key>::decode_leaf(const std::vector<uint8_t>& bytes) { - uint32_t count = read_u32(bytes.data() + 1); - LeafNode n; - n.entries.reserve(count); - const uint8_t* p = bytes.data() + 5; - for (uint32_t i = 0; i < count; ++i) { - LeafEntry e; - e.key = KeyCodec<Key>::read(p); - std::memcpy(e.payload.data(), p, 32); - p += 32; - n.entries.push_back(std::move(e)); - } - return n; - } - - template <typename Key> - typename Merkle<Key>::InternalNode - Merkle<Key>::decode_internal(const std::vector<uint8_t>& bytes) { - uint32_t count = read_u32(bytes.data() + 1); - InternalNode n; - n.entries.reserve(count); - const uint8_t* p = bytes.data() + 5; - for (uint32_t i = 0; i < count; ++i) { - ChildEntry e; - e.leaf_count = read_u64(p); - p += 8; - e.min_key = KeyCodec<Key>::read(p); - std::memcpy(e.child_hash.data(), p, 32); - p += 32; - n.entries.push_back(std::move(e)); - } - return n; - } - - template <typename Key> bool Merkle<Key>::is_leaf_bytes(const std::vector<uint8_t>& bytes) { - return !bytes.empty() && bytes[0] == TAG_LEAF; - } - - // ── Storage helpers ────────────────────────────────────────────────────────── - - template <typename Key> - std::vector<uint8_t> Merkle<Key>::load_node(IStorageBackend& store, - const std::string& hash_hex) { - auto opt = store.Get(hash_hex); - if (!opt) - throw std::runtime_error("Merkle: node not found: " + hash_hex); - return std::move(*opt); - } - - template <typename Key> - std::string Merkle<Key>::store_leaf(IStorageBackend& store, const LeafNode& n) { - return store.Put(encode_leaf(n)); - } - - template <typename Key> - std::string Merkle<Key>::store_internal(IStorageBackend& store, const InternalNode& n) { - return store.Put(encode_internal(n)); - } - - template <typename Key> - Key Merkle<Key>::subtree_min_key(IStorageBackend& store, const std::string& node_hex) { - auto bytes = load_node(store, node_hex); - if (is_leaf_bytes(bytes)) { - auto leaf = decode_leaf(bytes); - assert(!leaf.entries.empty() && "Merkle invariant: leaf node must never be empty"); - return leaf.entries.front().key; - } - auto node = decode_internal(bytes); - assert(!node.entries.empty() && "Merkle invariant: internal node must never be empty"); - return node.entries.front().min_key; - } - - // ── Routing helper ─────────────────────────────────────────────────────────── - - template <typename Key> size_t Merkle<Key>::route(const InternalNode& node, const Key& target) { - assert(!node.entries.empty() && "Merkle invariant: internal node must never be empty"); - assert(std::is_sorted(node.entries.begin(), node.entries.end(), - [](const ChildEntry& a, const ChildEntry& b) { - return a.min_key < b.min_key; - }) && - "Merkle invariant: internal node entries must be sorted by min_key"); - - // Rightmost child whose min_key ≤ target. - // Defaults to 0 (leftmost) when target < all min_keys. - size_t idx = 0; - for (size_t i = 1; i < node.entries.size(); ++i) { - if (!(target < node.entries[i].min_key)) - idx = i; - else - break; - } - return idx; - } - - // ── Insert ─────────────────────────────────────────────────────────────────── - - template <typename Key> - std::string Merkle<Key>::Insert(IStorageBackend& store, const std::string& root_hex, - const Key& key, const Hash32& payload) { - if (root_hex.empty()) { - LeafNode leaf; - leaf.entries.push_back({key, payload}); - return store_leaf(store, leaf); - } - - auto result = insert_into(store, root_hex, key, payload); - if (!result.did_split) - return result.new_hash; - - // Root split: wrap both halves in a new internal root. - Key left_min = subtree_min_key(store, result.new_hash); - - InternalNode new_root; - - ChildEntry left; - left.leaf_count = result.leaf_count; - left.min_key = left_min; - left.child_hash = hex_to_bin(result.new_hash); - new_root.entries.push_back(std::move(left)); - - ChildEntry right; - right.leaf_count = result.split_leaf_count; - right.min_key = result.split_min_key; - right.child_hash = hex_to_bin(result.split_hash); - new_root.entries.push_back(std::move(right)); - - return store_internal(store, new_root); - } - - template <typename Key> - typename Merkle<Key>::InsertResult - Merkle<Key>::insert_into(IStorageBackend& store, const std::string& node_hex, const Key& key, - const Hash32& payload) { - auto bytes = load_node(store, node_hex); - - // ── Leaf node ─────────────────────────────────────────────────────────── - if (is_leaf_bytes(bytes)) { - auto leaf = decode_leaf(bytes); - - auto pos = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key, - [](const LeafEntry& e, const Key& k) { return e.key < k; }); - if (pos != leaf.entries.end() && !(key < pos->key)) { - // Key already present — update payload if it changed, else no-op. - if (pos->payload == payload) { - InsertResult r; - r.new_hash = node_hex; - r.leaf_count = leaf.entries.size(); - return r; - } - pos->payload = payload; - InsertResult r; - r.new_hash = store_leaf(store, leaf); - r.leaf_count = leaf.entries.size(); - return r; - } - - leaf.entries.insert(pos, LeafEntry{key, payload}); - - if (leaf.entries.size() <= B) { - InsertResult r; - r.new_hash = store_leaf(store, leaf); - r.leaf_count = leaf.entries.size(); - return r; - } - - // Split leaf at midpoint. - size_t mid = leaf.entries.size() / 2; - LeafNode left_leaf, right_leaf; - left_leaf.entries = {leaf.entries.begin(), - leaf.entries.begin() + static_cast<ptrdiff_t>(mid)}; - right_leaf.entries = {leaf.entries.begin() + static_cast<ptrdiff_t>(mid), - leaf.entries.end()}; - - InsertResult r; - r.new_hash = store_leaf(store, left_leaf); - r.leaf_count = left_leaf.entries.size(); - r.did_split = true; - r.split_leaf_count = right_leaf.entries.size(); - r.split_min_key = right_leaf.entries.front().key; - r.split_hash = store_leaf(store, right_leaf); - return r; - } - - // ── Internal node ─────────────────────────────────────────────────────── - auto node = decode_internal(bytes); - - size_t child_idx = route(node, key); - auto child_result = - insert_into(store, bin_to_hex(node.entries[child_idx].child_hash), key, payload); - - node.entries[child_idx].leaf_count = child_result.leaf_count; - node.entries[child_idx].child_hash = hex_to_bin(child_result.new_hash); - - // Update min_key for the modified child — only child 0 can have its min - // decrease (routing guarantees min_key[i>0] ≤ inserted key, so inserting - // below min_key[i>0] is impossible). - if (child_idx == 0) - node.entries[0].min_key = subtree_min_key(store, child_result.new_hash); - - uint64_t total = 0; - for (const auto& e : node.entries) - total += e.leaf_count; - - if (!child_result.did_split) { - InsertResult r; - r.new_hash = store_internal(store, node); - r.leaf_count = total; - return r; - } - - // Insert the right sibling immediately after child_idx. - ChildEntry sibling; - sibling.leaf_count = child_result.split_leaf_count; - sibling.min_key = child_result.split_min_key; - sibling.child_hash = hex_to_bin(child_result.split_hash); - node.entries.insert(node.entries.begin() + static_cast<ptrdiff_t>(child_idx) + 1, - std::move(sibling)); - total += child_result.split_leaf_count; - - if (node.entries.size() <= B) { - InsertResult r; - r.new_hash = store_internal(store, node); - r.leaf_count = total; - return r; - } - - // Split this internal node. - size_t mid = node.entries.size() / 2; - InternalNode left_node, right_node; - left_node.entries = {node.entries.begin(), - node.entries.begin() + static_cast<ptrdiff_t>(mid)}; - right_node.entries = {node.entries.begin() + static_cast<ptrdiff_t>(mid), - node.entries.end()}; - - uint64_t left_count = 0, right_count = 0; - for (const auto& e : left_node.entries) - left_count += e.leaf_count; - for (const auto& e : right_node.entries) - right_count += e.leaf_count; - - InsertResult r; - r.new_hash = store_internal(store, left_node); - r.leaf_count = left_count; - r.did_split = true; - r.split_leaf_count = right_count; - r.split_min_key = right_node.entries.front().min_key; - r.split_hash = store_internal(store, right_node); - return r; - } - - // ── Remove ─────────────────────────────────────────────────────────────────── - - template <typename Key> - std::string Merkle<Key>::Remove(IStorageBackend& store, const std::string& root_hex, - const Key& key) { - if (root_hex.empty()) - return ""; - auto result = remove_from(store, root_hex, key); - if (result.new_hash.empty()) - return ""; - - // Collapse: if the root is an internal node with a single child, skip it. - auto bytes = load_node(store, result.new_hash); - if (!is_leaf_bytes(bytes)) { - auto node = decode_internal(bytes); - if (node.entries.size() == 1) - return bin_to_hex(node.entries[0].child_hash); - } - return result.new_hash; - } - - template <typename Key> - typename Merkle<Key>::RemoveResult - Merkle<Key>::remove_from(IStorageBackend& store, const std::string& node_hex, const Key& key) { - auto bytes = load_node(store, node_hex); - - // ── Leaf node ─────────────────────────────────────────────────────────── - if (is_leaf_bytes(bytes)) { - auto leaf = decode_leaf(bytes); - auto it = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key, - [](const LeafEntry& e, const Key& k) { return e.key < k; }); - if (it == leaf.entries.end() || key < it->key) - return {node_hex, leaf.entries.size()}; // not found — no change - - leaf.entries.erase(it); - if (leaf.entries.empty()) - return {"", 0}; - return {store_leaf(store, leaf), leaf.entries.size()}; - } - - // ── Internal node ─────────────────────────────────────────────────────── - auto node = decode_internal(bytes); - - size_t child_idx = route(node, key); - auto child_result = remove_from(store, bin_to_hex(node.entries[child_idx].child_hash), key); - - if (child_result.new_hash.empty()) { - node.entries.erase(node.entries.begin() + static_cast<ptrdiff_t>(child_idx)); - if (node.entries.empty()) - return {"", 0}; - } else { - node.entries[child_idx].leaf_count = child_result.leaf_count; - node.entries[child_idx].child_hash = hex_to_bin(child_result.new_hash); - // The min of any child might have changed if we removed its minimum entry. - node.entries[child_idx].min_key = subtree_min_key(store, child_result.new_hash); - } - - uint64_t total = 0; - for (const auto& e : node.entries) - total += e.leaf_count; - return {store_internal(store, node), total}; - } - - // ── Get ────────────────────────────────────────────────────────────────────── - - template <typename Key> - std::optional<Hash32> Merkle<Key>::Get(IStorageBackend& store, const std::string& root_hex, - const Key& key) { - if (root_hex.empty()) - return std::nullopt; - return get_from(store, root_hex, key); - } - - template <typename Key> - std::optional<Hash32> Merkle<Key>::get_from(IStorageBackend& store, const std::string& node_hex, - const Key& key) { - auto bytes = load_node(store, node_hex); - if (is_leaf_bytes(bytes)) { - auto leaf = decode_leaf(bytes); - auto it = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key, - [](const LeafEntry& e, const Key& k) { return e.key < k; }); - if (it == leaf.entries.end() || key < it->key) - return std::nullopt; - return it->payload; - } - auto node = decode_internal(bytes); - size_t child_idx = route(node, key); - return get_from(store, bin_to_hex(node.entries[child_idx].child_hash), key); - } - - // ── Page ───────────────────────────────────────────────────────────────────── - - template <typename Key> - std::vector<typename Merkle<Key>::Entry> Merkle<Key>::Page(IStorageBackend& store, - const std::string& root_hex, - size_t offset, size_t limit) { - if (root_hex.empty() || limit == 0) - return {}; - std::vector<Entry> out; - out.reserve(limit); - size_t skip = offset; - page_from(store, root_hex, skip, limit, out); - return out; - } - - template <typename Key> - void Merkle<Key>::page_from(IStorageBackend& store, const std::string& node_hex, size_t& offset, - size_t limit, std::vector<Entry>& out) { - auto bytes = load_node(store, node_hex); - - if (is_leaf_bytes(bytes)) { - auto leaf = decode_leaf(bytes); - const size_t available = leaf.entries.size(); - - if (offset >= available) { - offset -= available; - return; - } - - for (size_t i = offset; i < available && out.size() < limit; ++i) - out.push_back(Entry{leaf.entries[i].key, leaf.entries[i].payload}); - - offset = 0; - return; - } - - auto node = decode_internal(bytes); - - for (const auto& entry : node.entries) { - if (out.size() >= limit) - break; - - if (offset >= entry.leaf_count) { - offset -= entry.leaf_count; - continue; - } - - page_from(store, bin_to_hex(entry.child_hash), offset, limit, out); - } - } - - // ── Explicit instantiations ────────────────────────────────────────────────── - - template class Merkle<Hash32>; - template class Merkle<std::string>; - -} // namespace nt diff --git a/src/MultigroupCodec.cpp b/src/MultigroupCodec.cpp deleted file mode 100644 index 0d0cf14..0000000 --- a/src/MultigroupCodec.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "MultigroupCodec.h" - -#include "Merkle.h" - -#include <map> - -namespace nt::MultigroupCodec { - // A relation with zero tuples reports an empty merkle_root externally. - // The merkle tree's payload type is fixed-width Hash32, so the codec - // maps "" ↔ the all-zero hash. The collision probability with a real - // SHA256 hitting all-zero (2^-256) is treated as unreachable. - static nt::Hash32 encode_payload(const std::string& hex) { - if (hex.empty()) - return nt::Hash32{}; - return nt::hex_to_bin(hex); - } - - static std::string decode_payload(const nt::Hash32& bin) { - static const nt::Hash32 zero{}; - if (bin == zero) - return ""; - return nt::bin_to_hex(bin); - } - - std::string Build(IStorageBackend& store, const std::vector<RelationEntry>& relations) { - std::map<std::string, std::string> canonical; - for (const auto& [name, root_hex] : relations) - canonical[name] = root_hex; - - std::string root; - for (const auto& [name, root_hex] : canonical) - root = nt::Merkle<std::string>::Insert(store, root, name, encode_payload(root_hex)); - return root; - } - - std::vector<RelationEntry> List(IStorageBackend& store, const std::string& root_hex) { - std::vector<RelationEntry> out; - constexpr size_t kPageSize = 1024; - size_t offset = 0; - while (true) { - auto page = nt::Merkle<std::string>::Page(store, root_hex, offset, kPageSize); - if (page.empty()) - break; - for (auto& entry : page) - out.emplace_back(std::move(entry.key), decode_payload(entry.payload)); - if (page.size() < kPageSize) - break; - offset += page.size(); - } - return out; - } - - std::string InsertOne(IStorageBackend& store, const std::string& root_hex, - const std::string& relation_name, const std::string& relation_root_hex) { - return nt::Merkle<std::string>::Insert(store, root_hex, relation_name, - encode_payload(relation_root_hex)); - } - - std::string RemoveOne(IStorageBackend& store, const std::string& root_hex, - const std::string& relation_name) { - return nt::Merkle<std::string>::Remove(store, root_hex, relation_name); - } - - std::string Lookup(IStorageBackend& store, const std::string& root_hex, - const std::string& relation_name) { - auto found = nt::Merkle<std::string>::Get(store, root_hex, relation_name); - if (!found) - return ""; - return decode_payload(*found); - } -} // namespace nt::MultigroupCodec diff --git a/src/NamespaceReferenceManager.cpp b/src/NamespaceReferenceManager.cpp deleted file mode 100644 index 0d99b73..0000000 --- a/src/NamespaceReferenceManager.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "NamespaceReferenceManager.h" - -#include "Merkle.h" - -namespace nt { - NamespaceReferenceManager::NamespaceReferenceManager(ObjectManager& objects, - IStorageBackend& storage) - : objects_(objects), storage_(storage) { - } - - // Resolves the multigroup hash that `mg_name` currently maps to inside the - // branch-tree rooted at `branch_tree_root`. Returns the empty string when - // the branch is unborn (empty root) or the multigroup is absent. - static std::string lookup_mg_hash(IStorageBackend& storage, const std::string& branch_tree_root, - const std::string& mg_name) { - if (branch_tree_root.empty()) - return ""; - auto found = Merkle<std::string>::Get(storage, branch_tree_root, mg_name); - if (!found) - return ""; - // All-zero payload = "no snapshot bound" sentinel matching MultigroupCodec. - static const Hash32 zero{}; - if (*found == zero) - return ""; - return bin_to_hex(*found); - } - - std::vector<std::string> - NamespaceReferenceManager::Resolve(std::vector<std::string> path) const { - constexpr int kMaxReparseDepth = 30; - - for (int depth = 0; depth < kMaxReparseDepth; ++depth) { - // /system/sessions/<X>/branches/<name>/multigroups/<mg>/<sub>... - // — per-session override takes precedence; absent override falls - // back to the global branch. Requires at minimum the literal - // "multigroups" qualifier and an mg name. - if (path.size() > 6 && path[0] == "system" && path[1] == "sessions" && - path[3] == "branches" && path[5] == "multigroups") { - const std::vector<std::string> session_path{"system", "sessions", path[2]}; - auto* session_entry = objects_.Find(session_path); - - std::string branch_tree_root; - if (session_entry && session_entry->object) { - auto* session = - dynamic_cast<ObjectManager::Session*>(session_entry->object.get()); - if (session) { - auto it = session->branch_overrides.find(path[4]); - if (it != session->branch_overrides.end()) - branch_tree_root = it->second; - } - } - - // No session-level override — defer to the global branch's HEAD. - if (branch_tree_root.empty()) { - const std::vector<std::string> branch_path{"system", "branches", path[4]}; - auto* bentry = objects_.Find(branch_path); - if (bentry && bentry->object) { - auto* branch = dynamic_cast<ObjectManager::Branch*>(bentry->object.get()); - if (branch) - branch_tree_root = branch->target_hash; - } - } - - const std::string mg_hash = lookup_mg_hash(storage_, branch_tree_root, path[6]); - if (mg_hash.empty()) - return path; - - std::vector<std::string> rewritten{"system", "snapshots", mg_hash}; - for (size_t i = 7; i < path.size(); ++i) - rewritten.push_back(path[i]); - - path = std::move(rewritten); - continue; - } - - // /system/branches/<name>/multigroups/<mg>/<sub>... → - // /system/snapshots/<mg_hash>/<sub>... - // Requires the literal "multigroups" qualifier and an mg name. A - // bare /system/branches/<name> path opens the branch object itself - // and is returned unchanged. - if (path.size() > 4 && path[0] == "system" && path[1] == "branches" && - path[3] == "multigroups") { - const std::vector<std::string> branch_path{"system", "branches", path[2]}; - auto* entry = objects_.Find(branch_path); - if (!entry || !entry->object) - return path; - - auto* branch = dynamic_cast<ObjectManager::Branch*>(entry->object.get()); - if (!branch) - return path; - - const std::string mg_hash = lookup_mg_hash(storage_, branch->target_hash, path[4]); - if (mg_hash.empty()) - return path; - - std::vector<std::string> rewritten{"system", "snapshots", mg_hash}; - for (size_t i = 5; i < path.size(); ++i) - rewritten.push_back(path[i]); - - path = std::move(rewritten); - continue; - } - - return path; - } - - // Cycle guard tripped — surface as a lookup miss. - return {}; - } -} // namespace nt diff --git a/src/ObjectManager.cpp b/src/ObjectManager.cpp deleted file mode 100644 index 70e622b..0000000 --- a/src/ObjectManager.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "ObjectManager.h" - -namespace nt { - void ObjectManager::Register(std::vector<std::string> path, std::unique_ptr<IObject> object, - std::unique_ptr<object_type> type) { - auto head = std::make_unique<registry_head>(); - head->reference_count = 0; - head->handle_count = 0; - head->path = std::move(path); - head->type = std::move(type); - - auto entry = std::make_unique<registry>(); - entry->head = std::move(head); - entry->object = std::move(object); - entry->next = std::move(entries); - entries = std::move(entry); - } - - ObjectManager::registry* ObjectManager::Find(const std::vector<std::string> object_path) { - registry* current = entries.get(); - while (current != nullptr) { - if (current->head->path == object_path) - return current; - current = current->next.get(); - } - return nullptr; - } - - bool ObjectManager::Unregister(const std::vector<std::string>& object_path) { - std::unique_ptr<registry>* slot = &entries; - while (slot->get() != nullptr) { - if ((*slot)->head->path == object_path) { - *slot = std::move((*slot)->next); - return true; - } - slot = &(*slot)->next; - } - return false; - } -} // namespace nt diff --git a/src/PermissionsManager.cpp b/src/PermissionsManager.cpp deleted file mode 100644 index bcdfdd7..0000000 --- a/src/PermissionsManager.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "PermissionsManager.h" - -namespace nt { - std::set<AUTH_CLAIM> PermissionsManager::Firewall(AUTH_METHOD method) { - return {}; - } - - const bool PermissionsManager::Access(const ObjectManager::registry* object, - const void* connection_context) { - // Checks the security descriptor on the head of the object we find in the - // ObjectManager::registry Then additionally check other metadata yet to be defined in the - // connection_context, like user group, policy, etc. Yet to decide if this should be - // summoned at every attempt to get ahold of an object, that might produce significant - // overhead. How can we solve this? - return object != nullptr; - } -} // namespace nt diff --git a/src/RNT_C_API.cpp b/src/RNT_C_API.cpp deleted file mode 100644 index abd683c..0000000 --- a/src/RNT_C_API.cpp +++ /dev/null @@ -1,1057 +0,0 @@ -#include "RNT_C_API.h" - -#include "CursorManager.h" -#include "HandlerManager.h" -#include "IdentityManager.h" -#include "InMemoryBackend.h" -#include "LifecycleManager.h" -#include "Merkle.h" -#include "MultigroupCodec.h" -#include "NamespaceReferenceManager.h" -#include "ObjectManager.h" -#include "PermissionsManager.h" -#include "SqliteBackend.h" -#include "TupleCodec.h" -#include "VM.h" - -#include <algorithm> -#include <cstring> -#include <memory> -#include <mutex> -#include <random> -#include <sstream> -#include <stdexcept> -#include <string> -#include <vector> - -// --------------------------------------------------------------------------- -// Global runtime state -// -// A single Runtime instance is created per process on the first successful -// rnt_init call. All managers share this instance; they do not hold their -// own state. Initialisation is protected by g_init_mutex so that concurrent -// first-callers are safe; once g_init_done is true the mutex is never -// contended again. -// --------------------------------------------------------------------------- - -namespace { - struct Runtime { - std::unique_ptr<nt::IStorageBackend> storage; - nt::ObjectManager objects; - nt::PermissionsManager permissions; - nt::IdentityManager identities; - std::unique_ptr<nt::LifecycleManager> lifecycles; - std::unique_ptr<nt::NamespaceReferenceManager> references; - std::unique_ptr<nt::HandlerManager> handler; - std::unique_ptr<nt::CursorManager> cursors; - }; - - static std::unique_ptr<Runtime> g_rt; - - static std::vector<std::string> split_path(const char* path) { - std::vector<std::string> parts; - if (!path) - return parts; - std::string s(path); - if (!s.empty() && s[0] == '/') - s.erase(0, 1); - std::stringstream ss(s); - std::string part; - while (std::getline(ss, part, '/')) - if (!part.empty()) - parts.push_back(part); - return parts; - } - - static std::vector<nt::Attribute> parse_kv(const char* kv) { - std::vector<nt::Attribute> attrs; - if (!kv) - return attrs; - std::stringstream ss(kv); - std::string line; - while (std::getline(ss, line)) { - if (line.empty()) - continue; - const auto pos = line.find('='); - if (pos == std::string::npos) - continue; - attrs.push_back({line.substr(0, pos), line.substr(pos + 1)}); - } - return attrs; - } - - static std::string tuple_to_kv(nt::Tuple* t) { - std::string out; - t->Reset(); - const nt::Attribute* a = t->Next(); - while (a) { - out += a->name + "=" + a->value + "\n"; - a = t->Next(); - } - return out; - } - - static char* heap_str(const std::string& s) { - char* p = new char[s.size() + 1]; - std::memcpy(p, s.data(), s.size()); - p[s.size()] = '\0'; - return p; - } - - static std::unique_ptr<nt::ObjectManager::object_type> make_relation_type() { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = RELATION; - t->disposable = false; - t->methods = {OPEN, CLOSE}; - t->exclusive = false; - return t; - } - - static std::unique_ptr<nt::ObjectManager::object_type> make_branch_type() { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = BRANCH; - t->disposable = false; - t->methods = {OPEN, CLOSE}; - // Branches are mutable reference heads: serialize writers through - // LifecycleManager::Contention. - t->exclusive = true; - return t; - } - - static std::unique_ptr<nt::ObjectManager::object_type> make_multigroup_type() { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = MULTIGROUP; - t->disposable = false; - t->methods = {OPEN, CLOSE}; - // Multigroup snapshots are immutable and content-addressed; concurrent - // readers never contend. GC is gated by reference_count only. - t->exclusive = false; - return t; - } - - static std::unique_ptr<nt::ObjectManager::object_type> make_branch_tree_type() { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = BRANCH_TREE; - t->disposable = false; - t->methods = {OPEN, CLOSE}; - // Branch-tree blobs are immutable content-addressed merkle roots; - // GC is gated by reference_count only. - t->exclusive = false; - return t; - } - - static std::unique_ptr<nt::ObjectManager::object_type> make_session_type() { - auto t = std::make_unique<nt::ObjectManager::object_type>(); - t->label = SESSION; - // Sessions live only as long as their connection; rnt_session_close - // removes them via ObjectManager::Unregister. - t->disposable = true; - t->methods = {OPEN, CLOSE}; - // Multiple readers/writers may consult a session concurrently to read - // overrides; contention is only enforced when an exclusive mutator - // (set_branch) is wired up. Default to non-exclusive for now. - t->exclusive = false; - return t; - } - - // Generates a random 256-bit hex hash for naming a session. Hashes are - // drawn from std::random_device-seeded mt19937_64; we do not need - // cryptographic strength here, just enough entropy that two concurrent - // rnt_session_open calls cannot collide. - static std::string random_session_hash() { - static thread_local std::mt19937_64 rng{std::random_device{}()}; - static const char hex[] = "0123456789abcdef"; - - std::string out; - out.reserve(64); - for (int i = 0; i < 4; ++i) { - uint64_t v = rng(); - for (int b = 0; b < 8; ++b) { - uint8_t byte = static_cast<uint8_t>(v & 0xFF); - out.push_back(hex[(byte >> 4) & 0xF]); - out.push_back(hex[byte & 0xF]); - v >>= 8; - } - } - return out; - } - - // Serializes the (name, merkle_root) pairs into the storage backend and - // registers the resulting multigroup snapshot under /system/snapshots/<hash>, - // with one Relation entry per child at /system/snapshots/<hash>/<name>. - // Idempotent: if the snapshot already exists in the registry the existing - // entries are reused. Returns the multigroup hash, or empty string on error. - // - // Each child Relation is Pinned by its parent Multigroup at creation time. - // LifecycleManager::CascadeMultigroup releases these pins when the - // multigroup itself becomes GC-eligible. - static std::string - register_snapshot(const std::vector<nt::MultigroupCodec::RelationEntry>& relations) { - if (!g_rt) - return {}; - - const std::string hash = nt::MultigroupCodec::Build(*g_rt->storage, relations); - if (hash.empty()) - return {}; - const auto snap_path = split_path(("/system/snapshots/" + hash).c_str()); - - if (g_rt->objects.Find(snap_path) != nullptr) - return hash; - - auto mg = std::make_unique<nt::ObjectManager::Multigroup>(); - mg->merkle_root = hash; - g_rt->objects.Register(snap_path, std::move(mg), make_multigroup_type()); - - for (const auto& [name, root] : relations) { - const auto rel_path = - split_path(("/system/snapshots/" + hash + "/relations/" + name).c_str()); - if (g_rt->objects.Find(rel_path) != nullptr) - continue; - - auto rel = std::make_unique<nt::ObjectManager::Relation>(); - rel->merkle_root = root; - g_rt->objects.Register(rel_path, std::move(rel), make_relation_type()); - - // Structural pin from the parent Multigroup; released by - // LifecycleManager::CascadeMultigroup at GC time. - g_rt->lifecycles->Pin(g_rt->objects.Find(rel_path)); - } - return hash; - } - - // Enumerates (mg_name, mg_hash) pairs in a branch-tree. - static std::vector<std::pair<std::string, std::string>> - list_branch_tree(const std::string& branch_tree_root) { - std::vector<std::pair<std::string, std::string>> out; - if (branch_tree_root.empty()) - return out; - constexpr size_t kPageSize = 1024; - size_t offset = 0; - while (true) { - auto page = - nt::Merkle<std::string>::Page(*g_rt->storage, branch_tree_root, offset, kPageSize); - if (page.empty()) - break; - for (auto& entry : page) - out.emplace_back(std::move(entry.key), nt::bin_to_hex(entry.payload)); - if (page.size() < kPageSize) - break; - offset += page.size(); - } - return out; - } - - // Registers /system/branch_trees/<hash> as a BRANCH_TREE if missing, and - // takes one structural pin on each multigroup the tree references. The - // pins are mirrored by LifecycleManager::CascadeBranchTree at GC time. - // Idempotent: a second call with the same hash returns immediately. - static void register_branch_tree(const std::string& tree_root) { - if (tree_root.empty()) - return; - const auto bt_path = split_path(("/system/branch_trees/" + tree_root).c_str()); - if (g_rt->objects.Find(bt_path) != nullptr) - return; - - auto bt = std::make_unique<nt::ObjectManager::BranchTree>(); - bt->merkle_root = tree_root; - g_rt->objects.Register(bt_path, std::move(bt), make_branch_tree_type()); - - for (const auto& [_, mg_hash] : list_branch_tree(tree_root)) { - if (mg_hash.empty()) - continue; - const auto p = split_path(("/system/snapshots/" + mg_hash).c_str()); - g_rt->lifecycles->Pin(g_rt->objects.Find(p)); - } - } - - // Adjusts pins when a branch (or session override) moves its bound - // branch-tree root. The new tree is pinned before the old is unpinned so - // a no-op advance never sees a pinned BranchTree's ref_count briefly hit - // zero and trigger cascade collection of every mg it references. - static void rebind_branch_tree_pin(const std::string& old_tree_root, - const std::string& new_tree_root) { - if (old_tree_root == new_tree_root) - return; - if (!new_tree_root.empty()) { - register_branch_tree(new_tree_root); - const auto p = split_path(("/system/branch_trees/" + new_tree_root).c_str()); - g_rt->lifecycles->Pin(g_rt->objects.Find(p)); - } - if (!old_tree_root.empty()) { - const auto p = split_path(("/system/branch_trees/" + old_tree_root).c_str()); - g_rt->lifecycles->Unpin(g_rt->objects.Find(p)); - } - } - - // Looks up the mg_hash currently bound to `mg_name` in the branch's tree. - // Returns empty string when the branch is unborn or the mg is absent. - static std::string lookup_branch_mg(const nt::ObjectManager::Branch& branch, - const std::string& mg_name) { - if (branch.target_hash.empty()) - return ""; - auto found = nt::Merkle<std::string>::Get(*g_rt->storage, branch.target_hash, mg_name); - if (!found) - return ""; - static const nt::Hash32 zero{}; - if (*found == zero) - return ""; - return nt::bin_to_hex(*found); - } - - // Reads the (relation_name, root) list of a specific multigroup under a - // branch. Returns empty for unborn branches or absent multigroups. - static std::vector<nt::MultigroupCodec::RelationEntry> - read_mg_relations(const nt::ObjectManager::Branch& branch, const std::string& mg_name) { - const std::string mg_hash = lookup_branch_mg(branch, mg_name); - if (mg_hash.empty()) - return {}; - return nt::MultigroupCodec::List(*g_rt->storage, mg_hash); - } - - // Looks up a branch object by path. Returns nullptr when the path does not - // resolve to a BRANCH. - static nt::ObjectManager::Branch* find_branch(const std::vector<std::string>& branch_path) { - auto* entry = g_rt->objects.Find(branch_path); - if (!entry || !entry->object || !entry->head) - return nullptr; - if (entry->head->type->label != BRANCH) - return nullptr; - return dynamic_cast<nt::ObjectManager::Branch*>(entry->object.get()); - } - - // Parses a path of the form - // /system/branches/<name>/multigroups/<mg>/relations/<rel>. - // Returns true on success; out-parameters are populated with the branch - // path, the multigroup name, and the relation name. Returns false for - // any other path shape. - static bool split_branch_mg_relation(const std::vector<std::string>& parts, - std::vector<std::string>& branch_path_out, - std::string& mg_name_out, std::string& relation_name_out) { - if (parts.size() != 7) - return false; - if (parts[0] != "system" || parts[1] != "branches" || parts[3] != "multigroups" || - parts[5] != "relations") - return false; - branch_path_out = {"system", "branches", parts[2]}; - mg_name_out = parts[4]; - relation_name_out = parts[6]; - return true; - } - - // Applies a per-relation root update with the full two-level cascade: - // 1. Read the (rel, root) list of `mg_name` from the branch's current - // tree (or empty when the mg is absent / branch is unborn). - // 2. Insert/replace (relation_name, new_root) in that list. - // 3. register_snapshot → new_mg_hash. - // 4. Insert (mg_name → new_mg_hash) into the branch tree → new branch - // tree root. - // 5. Advance branch.target_hash to the new root and rebind pins. - // Returns the new branch tree root on success; empty on error. - // - // Pin/unpin transitions are applied via rebind_branch_tree_pin: new mgs - // are pinned before old mgs are unpinned, so an unchanged advance never - // briefly drops a multigroup's ref_count to zero. - static std::string commit_relation_update(nt::ObjectManager::Branch& branch, - const std::string& mg_name, - const std::string& relation_name, - const std::string& new_root) { - auto relations = read_mg_relations(branch, mg_name); - auto it = std::find_if(relations.begin(), relations.end(), - [&](const auto& e) { return e.first == relation_name; }); - if (it != relations.end()) - it->second = new_root; - else - relations.emplace_back(relation_name, new_root); - - const std::string new_mg_hash = register_snapshot(relations); - if (new_mg_hash.empty()) - return {}; - - const nt::Hash32 mg_payload = nt::hex_to_bin(new_mg_hash); - const std::string new_tree_root = nt::Merkle<std::string>::Insert( - *g_rt->storage, branch.target_hash, mg_name, mg_payload); - - const std::string old_tree_root = branch.target_hash; - branch.target_hash = new_tree_root; - rebind_branch_tree_pin(old_tree_root, new_tree_root); - return new_tree_root; - } - - // Returns the merkle_root of a named relation in the specified mg under - // the branch's current tree. Empty when the branch is unborn, the mg - // doesn't exist, or the relation is absent. - static std::string read_relation_root(const nt::ObjectManager::Branch& branch, - const std::string& mg_name, - const std::string& relation_name) { - const auto relations = read_mg_relations(branch, mg_name); - auto it = std::find_if(relations.begin(), relations.end(), - [&](const auto& e) { return e.first == relation_name; }); - return (it == relations.end()) ? std::string{} : it->second; - } - - // ----------------------------------------------------------------------- - // Plan builder internals - // ----------------------------------------------------------------------- - - /** - * Wraps an entire plan subtree. All PlanNode allocations and all - * CursorManager::cursor* / HandlerManager::handle* opened by SCAN nodes - * are collected here so they can be released atomically when the plan is - * freed or transferred to a VmCursor. - */ - struct PlanWrapper { - nt::PlanNode* root = nullptr; - std::vector<std::unique_ptr<nt::PlanNode>> nodes; // owns every node in subtree - std::vector<nt::CursorManager::cursor*> cursors; // one per SCAN leaf - std::vector<nt::HandlerManager::handle*> handles; // one per SCAN leaf - }; - - static void free_plan_wrapper(PlanWrapper* pw) { - if (!pw) - return; - for (auto* c : pw->cursors) - g_rt->cursors->Close(c); - for (auto* h : pw->handles) - g_rt->handler->Close(h); - delete pw; - } - - /** - * A VM cursor owns the materialised plan tree and all resources opened - * during plan construction. After rnt_vm_execute_plan the caller drives - * it with rnt_vm_cursor_next / rnt_vm_cursor_close. - */ - struct VmCursor { - nt::VM vm; - nt::PlanNode* root; - std::vector<std::unique_ptr<nt::PlanNode>> nodes; - std::vector<nt::CursorManager::cursor*> cursors; - std::vector<nt::HandlerManager::handle*> handles; - - explicit VmCursor(nt::CursorManager& cm, PlanWrapper* pw) : vm(cm), root(pw->root) { - nodes = std::move(pw->nodes); - cursors = std::move(pw->cursors); - handles = std::move(pw->handles); - delete pw; // struct itself; resources transferred above - } - - ~VmCursor() { - for (auto* c : cursors) - g_rt->cursors->Close(c); - for (auto* h : handles) - g_rt->handler->Close(h); - } - }; - - // ---- Plan builders -------------------------------------------------- - // Pure construction: each builder receives only its operator's context and - // assumes the runtime is already initialised. rnt_plan_assemble is the sole - // caller and performs that check once before dispatching here. - - PlanWrapper* build_scan(const PlanArgsScan& a) { - if (!a.relation_path) - return nullptr; - - // Open a handle to the stored relation through the full manager pipeline. - // Open() runs NamespaceReferenceManager::Resolve internally, so the handle - // lands on the resolved /system/snapshots/<hash>/relations/<n> entry when - // the caller passed a branch-relative path. - const auto parts = split_path(a.relation_path); - auto* h = g_rt->handler->Open(parts, nullptr); - if (!h) - return nullptr; - - // Read the Merkle root straight from the handle's object — Resolve has - // already pointed it at the snapshot-bound Relation entry. - std::string merkle_root; - if (h->object && h->object->object) { - auto* rel = dynamic_cast<nt::ObjectManager::Relation*>(h->object->object.get()); - if (rel) - merkle_root = rel->merkle_root; - } - - auto* c = g_rt->cursors->Open(h, merkle_root); - if (!c) { - g_rt->handler->Close(h); - return nullptr; - } - - auto node = std::make_unique<nt::PlanNode>(); - node->op = nt::FOL_OPERATION_SCAN; - node->scan_cursor = c; - - auto* pw = new PlanWrapper(); - pw->root = node.get(); - pw->nodes.push_back(std::move(node)); - pw->cursors.push_back(c); - pw->handles.push_back(h); - return pw; - } - - PlanWrapper* build_join(const PlanArgsJoin& a) { - auto* l = static_cast<PlanWrapper*>(a.left); - auto* r = static_cast<PlanWrapper*>(a.right); - - if (!l || !r) { - free_plan_wrapper(l); - free_plan_wrapper(r); - return nullptr; - } - - auto node = std::make_unique<nt::PlanNode>(); - node->op = nt::FOL_OPERATION_JOIN; - node->left = l->root; - node->right = r->root; - - auto* pw = new PlanWrapper(); - pw->root = node.get(); - pw->nodes.push_back(std::move(node)); - - // Absorb child resources into the new wrapper. - for (auto& n : l->nodes) - pw->nodes.push_back(std::move(n)); - for (auto& n : r->nodes) - pw->nodes.push_back(std::move(n)); - for (auto* c : l->cursors) - pw->cursors.push_back(c); - for (auto* c : r->cursors) - pw->cursors.push_back(c); - for (auto* h : l->handles) - pw->handles.push_back(h); - for (auto* h : r->handles) - pw->handles.push_back(h); - - delete l; - delete r; - return pw; - } - - PlanWrapper* build_take(const PlanArgsTake& a) { - auto* s = static_cast<PlanWrapper*>(a.source); - if (!s) - return nullptr; - - auto node = std::make_unique<nt::PlanNode>(); - node->op = nt::FOL_OPERATION_TAKE; - node->left = s->root; - node->take_limit = a.limit; - - auto* pw = new PlanWrapper(); - pw->root = node.get(); - pw->nodes.push_back(std::move(node)); - - for (auto& n : s->nodes) - pw->nodes.push_back(std::move(n)); - for (auto* c : s->cursors) - pw->cursors.push_back(c); - for (auto* h : s->handles) - pw->handles.push_back(h); - - delete s; - return pw; - } - - PlanWrapper* build_project(const PlanArgsProject& a) { - auto* s = static_cast<PlanWrapper*>(a.source); - if (!s) - return nullptr; - - auto node = std::make_unique<nt::PlanNode>(); - node->op = nt::FOL_OPERATION_PROJECT; - node->left = s->root; - - if (a.attrs) - for (const char** p = a.attrs; *p; ++p) - node->project_attrs.emplace(*p); - - auto* pw = new PlanWrapper(); - pw->root = node.get(); - pw->nodes.push_back(std::move(node)); - for (auto& n : s->nodes) - pw->nodes.push_back(std::move(n)); - for (auto* c : s->cursors) - pw->cursors.push_back(c); - for (auto* h : s->handles) - pw->handles.push_back(h); - - delete s; - return pw; - } -} // namespace - -// --------------------------------------------------------------------------- -// Public C API -// -// Section order mirrors the NT open-handle pipeline: -// Runtime lifecycle → Authentication → Handle lifecycle → -// Object registration → Tuple storage → Cursor / VM plan builder -// --------------------------------------------------------------------------- - -int rnt_init(const char* driver, const char* storage_path) { - try { - auto rt = std::make_unique<Runtime>(); - - const std::string drv = driver ? driver : "sqlite"; - if (drv == "memory") - rt->storage = std::make_unique<nt::InMemoryBackend>(); - else - rt->storage = - std::make_unique<nt::SqliteBackend>(storage_path ? storage_path : ":memory:"); - - rt->lifecycles = std::make_unique<nt::LifecycleManager>(rt->objects, *rt->storage); - rt->references = std::make_unique<nt::NamespaceReferenceManager>(rt->objects, *rt->storage); - rt->handler = std::make_unique<nt::HandlerManager>( - rt->objects, rt->permissions, rt->identities, *rt->lifecycles, *rt->references); - rt->cursors = - std::make_unique<nt::CursorManager>(*rt->storage, rt->lifecycles.get(), &rt->objects); - g_rt = std::move(rt); - - // Register the default branch on first boot so that - // rnt_open_handle("/system/branches/master") always succeeds. - const auto master_path = split_path("/system/branches/master"); - if (g_rt->objects.Find(master_path) == nullptr) { - auto branch = std::make_unique<nt::ObjectManager::Branch>(); - branch->name = "master"; - g_rt->objects.Register(master_path, std::move(branch), make_branch_type()); - } - - return 0; - } catch (...) { - g_rt.reset(); - return -1; - } -} - -int rnt_firewall(const char* /*auth_method*/, char** claims_out) { - // PermissionsManager::Firewall is stubbed; grant all claims for now. - *claims_out = heap_str("READ WRITE"); - return 0; -} - -int rnt_session_open(void* connection_context, char** session_hash_out) { - if (!session_hash_out) - return -1; - - // Retry on the astronomically unlikely event of a hash collision against - // an already-registered session. - std::string hash; - for (int attempts = 0; attempts < 8; ++attempts) { - hash = random_session_hash(); - const auto session_path = split_path(("/system/sessions/" + hash).c_str()); - if (g_rt->objects.Find(session_path) != nullptr) - continue; - - auto session = std::make_unique<nt::ObjectManager::Session>(); - session->connection_context = connection_context; - g_rt->objects.Register(session_path, std::move(session), make_session_type()); - *session_hash_out = heap_str(hash); - return 0; - } - return -1; -} - -int rnt_session_close(const char* session_hash) { - if (!session_hash) - return -1; - const auto session_path = split_path(("/system/sessions/" + std::string(session_hash)).c_str()); - - auto* entry = g_rt->objects.Find(session_path); - if (!entry || !entry->object) - return -1; - auto* session = dynamic_cast<nt::ObjectManager::Session*>(entry->object.get()); - if (!session) - return -1; - - // Release every pin this session was holding before the entry disappears, - // so the dependent multigroups become GC-eligible. - for (const auto& [name, hash] : session->branch_overrides) - rebind_branch_tree_pin(hash, ""); - session->branch_overrides.clear(); - - return g_rt->objects.Unregister(session_path) ? 0 : -1; -} - -int rnt_session_set_branch(const char* session_hash, const char* branch_name, - const char* target_hash) { - if (!session_hash || !branch_name || !target_hash) - return -1; - - const auto session_path = split_path(("/system/sessions/" + std::string(session_hash)).c_str()); - auto* entry = g_rt->objects.Find(session_path); - if (!entry || !entry->object || !entry->head) - return -1; - if (entry->head->type->label != SESSION) - return -1; - - auto* session = dynamic_cast<nt::ObjectManager::Session*>(entry->object.get()); - if (!session) - return -1; - - const std::string hash(target_hash); - std::string old_hash; - auto it = session->branch_overrides.find(branch_name); - if (it != session->branch_overrides.end()) - old_hash = it->second; - - if (hash.empty()) { - session->branch_overrides.erase(branch_name); - rebind_branch_tree_pin(old_hash, ""); - return 0; - } - - // target_hash is a branch-tree root — a content-addressed blob produced - // by an earlier commit. Reject if unknown. - if (!g_rt->storage->Get(hash)) - return -1; - - session->branch_overrides[branch_name] = hash; - rebind_branch_tree_pin(old_hash, hash); - return 0; -} - -rnt_handle_t rnt_open_handle(const char* path, const char* /*claims*/) { - if (!path) - return nullptr; - return g_rt->handler->Open(split_path(path), nullptr); -} - -int rnt_close_handle(rnt_handle_t handle) { - if (!handle) - return -1; - auto* h = static_cast<nt::HandlerManager::handle*>(handle); - return g_rt->handler->Close(h) ? 0 : -1; -} - -int rnt_branch_target(rnt_handle_t handle, char** target_hash_out) { - if (!handle || !target_hash_out) - return -1; - - auto* h = static_cast<nt::HandlerManager::handle*>(handle); - if (!h->object || !h->object->head) - return -1; - if (h->object->head->type->label != BRANCH) - return -1; - - auto* branch = dynamic_cast<nt::ObjectManager::Branch*>(h->object->object.get()); - if (!branch) - return -1; - - *target_hash_out = heap_str(branch->target_hash); - return 0; -} - -int rnt_branch_advance(const char* branch_path, const char* new_hash) { - if (!branch_path || !new_hash) - return -1; - - auto* entry = g_rt->objects.Find(split_path(branch_path)); - if (!entry || !entry->object || !entry->head) - return -1; - if (entry->head->type->label != BRANCH) - return -1; - - auto* branch = dynamic_cast<nt::ObjectManager::Branch*>(entry->object.get()); - if (!branch) - return -1; - - const std::string hash(new_hash); - // Non-empty branch-tree roots must already exist in the KV store. The - // branch tree is a content-addressed blob produced by an earlier mutation - // through commit_relation_update; advancing to an unknown root is treated - // as a caller bug. - if (!hash.empty() && !g_rt->storage->Get(hash)) - return -1; - - const std::string old_hash = branch->target_hash; - branch->target_hash = hash; - rebind_branch_tree_pin(old_hash, hash); - return 0; -} - -int rnt_register_relation(const char* path) { - if (!path) - return -1; - const auto parts = split_path(path); - - std::vector<std::string> branch_path; - std::string mg_name, relation_name; - if (!split_branch_mg_relation(parts, branch_path, mg_name, relation_name)) - return -1; - - auto* branch = find_branch(branch_path); - if (!branch) - return -1; - - // Idempotent: if the relation is already present in the named mg's - // current snapshot, the call succeeds without producing a new snapshot. - const auto current = read_mg_relations(*branch, mg_name); - const bool already_present = std::any_of( - current.begin(), current.end(), [&](const auto& e) { return e.first == relation_name; }); - if (already_present) - return 0; - - // Empty merkle_root signals a relation with no tuples. - return commit_relation_update(*branch, mg_name, relation_name, "").empty() ? -1 : 0; -} - -int rnt_register_branch(const char* path, const char* target_hash) { - if (!path) - return -1; - const auto parts = split_path(path); - if (g_rt->objects.Find(parts) != nullptr) - return 0; - - const std::string hash = target_hash ? target_hash : ""; - // Non-empty target_hash is a branch-tree root produced by an earlier - // commit; it must already exist as a KV blob. - if (!hash.empty() && !g_rt->storage->Get(hash)) - return -1; - - auto branch = std::make_unique<nt::ObjectManager::Branch>(); - branch->name = parts.empty() ? "" : parts.back(); - branch->target_hash = hash; - - g_rt->objects.Register(parts, std::move(branch), make_branch_type()); - rebind_branch_tree_pin("", hash); - return 0; -} - -int rnt_list_relations(const char* branch_mg_path, char** out) { - if (!branch_mg_path || !out) - return -1; - const auto parts = split_path(branch_mg_path); - if (parts.size() != 5) - return -1; - if (parts[0] != "system" || parts[1] != "branches" || parts[3] != "multigroups") - return -1; - - auto* branch = find_branch({"system", "branches", parts[2]}); - if (!branch) - return -1; - const auto relations = read_mg_relations(*branch, parts[4]); - std::string result; - for (const auto& [name, root] : relations) - result += name + "\t" + root + "\n"; - *out = heap_str(result); - return 0; -} - -int rnt_list_branch_multigroups(const char* branch_path, char** out) { - if (!branch_path || !out) - return -1; - auto* branch = find_branch(split_path(branch_path)); - if (!branch) - return -1; - const auto mgs = list_branch_tree(branch->target_hash); - std::string result; - for (const auto& [name, mg_hash] : mgs) - result += name + "\t" + mg_hash + "\n"; - *out = heap_str(result); - return 0; -} - -int rnt_list_snapshot_relations(const char* snapshot_hash, char** out) { - if (!snapshot_hash || !out) - return -1; - const std::string hash(snapshot_hash); - const auto snap_path = split_path(("/system/snapshots/" + hash).c_str()); - auto* entry = g_rt->objects.Find(snap_path); - if (!entry || !entry->object) - return -1; - auto* mg = dynamic_cast<nt::ObjectManager::Multigroup*>(entry->object.get()); - if (!mg) - return -1; - const auto relations = nt::MultigroupCodec::List(*g_rt->storage, mg->merkle_root); - std::string result; - for (const auto& [name, root] : relations) - result += name + "\t" + root + "\n"; - *out = heap_str(result); - return 0; -} - -int rnt_link_tuple(const char* relation_path, const char* kv_attrs, char** hash_out) { - if (!relation_path || !kv_attrs || !hash_out) - return -1; - const auto parts = split_path(relation_path); - - std::vector<std::string> branch_path; - std::string mg_name, relation_name; - if (!split_branch_mg_relation(parts, branch_path, mg_name, relation_name)) - return -1; - - auto* branch = find_branch(branch_path); - if (!branch) - return -1; - - const std::string old_root = read_relation_root(*branch, mg_name, relation_name); - - auto bytes = nt::TupleCodec::Serialize(parse_kv(kv_attrs)); - const auto tuple_hash = g_rt->storage->Put(std::move(bytes)); - const auto tuple_bin = nt::hex_to_bin(tuple_hash); - const std::string new_root = - nt::Merkle<nt::Hash32>::Insert(*g_rt->storage, old_root, tuple_bin, tuple_bin); - - if (commit_relation_update(*branch, mg_name, relation_name, new_root).empty()) - return -1; - - *hash_out = heap_str(tuple_hash); - return 0; -} - -int rnt_unlink_tuple(const char* relation_path, const char* tuple_hash) { - if (!relation_path || !tuple_hash) - return -1; - const auto parts = split_path(relation_path); - - std::vector<std::string> branch_path; - std::string mg_name, relation_name; - if (!split_branch_mg_relation(parts, branch_path, mg_name, relation_name)) - return -1; - - auto* branch = find_branch(branch_path); - if (!branch) - return -1; - - const std::string old_root = read_relation_root(*branch, mg_name, relation_name); - const std::string new_root = - nt::Merkle<nt::Hash32>::Remove(*g_rt->storage, old_root, nt::hex_to_bin(tuple_hash)); - - return commit_relation_update(*branch, mg_name, relation_name, new_root).empty() ? -1 : 0; -} - -int rnt_clear_relation(const char* relation_path) { - if (!relation_path) - return -1; - const auto parts = split_path(relation_path); - - std::vector<std::string> branch_path; - std::string mg_name, relation_name; - if (!split_branch_mg_relation(parts, branch_path, mg_name, relation_name)) - return -1; - - auto* branch = find_branch(branch_path); - if (!branch) - return -1; - - return commit_relation_update(*branch, mg_name, relation_name, "").empty() ? -1 : 0; -} - -int rnt_relation_root(const char* relation_path, char** root_hash_out) { - if (!relation_path || !root_hash_out) - return -1; - const auto parts = split_path(relation_path); - - std::vector<std::string> branch_path; - std::string mg_name, relation_name; - if (!split_branch_mg_relation(parts, branch_path, mg_name, relation_name)) - return -1; - - auto* branch = find_branch(branch_path); - if (!branch) - return -1; - - // Distinguish "relation registered but empty" (returns 0 with empty hash) - // from "relation not in this multigroup's snapshot" (returns -1). Without - // this check an unborn branch would silently report every relation empty. - const auto relations = read_mg_relations(*branch, mg_name); - auto it = std::find_if(relations.begin(), relations.end(), - [&](const auto& e) { return e.first == relation_name; }); - if (it == relations.end()) - return -1; - - *root_hash_out = heap_str(it->second); - return 0; -} - -rnt_cursor_t rnt_cursor_open(rnt_handle_t handle) { - if (!handle) - return nullptr; - auto* h = static_cast<nt::HandlerManager::handle*>(handle); - - std::string merkle_root; - if (h->object && h->object->head && h->object->head->type->label == RELATION) { - auto* rel = dynamic_cast<nt::ObjectManager::Relation*>(h->object->object.get()); - if (rel) - merkle_root = rel->merkle_root; - } - - return g_rt->cursors->Open(h, merkle_root); -} - -int rnt_cursor_next(rnt_cursor_t cursor, char** tuple_out) { - if (!cursor || !tuple_out) - return -1; - auto* c = static_cast<nt::CursorManager::cursor*>(cursor); - nt::Tuple* t = g_rt->cursors->Next(c); - if (!t) { - *tuple_out = nullptr; - return 0; - } - *tuple_out = heap_str(tuple_to_kv(t)); - return 1; -} - -int rnt_cursor_close(rnt_cursor_t cursor) { - if (!cursor) - return -1; - g_rt->cursors->Close(static_cast<nt::CursorManager::cursor*>(cursor)); - return 0; -} - -void rnt_free_string(char* s) { - delete[] s; -} -void rnt_free_bytes(uint8_t* p) { - delete[] p; -} - -// --------------------------------------------------------------------------- -// VM plan builder -// --------------------------------------------------------------------------- - -rnt_plan_t rnt_plan_assemble(PlanAction action) { - switch (action.operation) { - case nt::FOL_OPERATION_SCAN: - return build_scan(action.scan); - case nt::FOL_OPERATION_JOIN: - return build_join(action.join); - case nt::FOL_OPERATION_TAKE: - return build_take(action.take); - case nt::FOL_OPERATION_PROJECT: - return build_project(action.project); - } - - return nullptr; -} - -void rnt_plan_free(rnt_plan_t plan) { - free_plan_wrapper(static_cast<PlanWrapper*>(plan)); -} - -rnt_cursor_t rnt_vm_execute_plan(rnt_plan_t plan) { - if (!plan) - return nullptr; - // VmCursor constructor transfers ownership from PlanWrapper and deletes it. - return new VmCursor(*g_rt->cursors, static_cast<PlanWrapper*>(plan)); -} - -int rnt_vm_cursor_next(rnt_cursor_t vm_cursor, char** tuple_out) { - if (!vm_cursor || !tuple_out) - return -1; - auto* vc = static_cast<VmCursor*>(vm_cursor); - nt::Tuple* t = vc->vm.Next(vc->root); - if (!t) { - *tuple_out = nullptr; - return 0; - } - *tuple_out = heap_str(tuple_to_kv(t)); - return 1; -} - -int rnt_vm_cursor_close(rnt_cursor_t vm_cursor) { - if (!vm_cursor) - return -1; - // VmCursor destructor closes all cursors and handles. - delete static_cast<VmCursor*>(vm_cursor); - return 0; -} diff --git a/src/RelationalNT.cpp b/src/RelationalNT.cpp deleted file mode 100644 index f27179f..0000000 --- a/src/RelationalNT.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "Runtime.h" - -#ifdef NT_CONSOLE_APP -#include "CursorManager.h" -#include "HandlerManager.h" -#include "IdentityManager.h" -#include "LifecycleManager.h" -#include "Merkle.h" -#include "NamespaceReferenceManager.h" -#include "ObjectManager.h" -#include "PermissionsManager.h" -#include "SqliteBackend.h" -#include "TupleCodec.h" -#include "VM.h" - -#include <iostream> -#endif - -namespace nt { - bool NT::IsRunning() const { - return true; - } - - void NT::SimulateEntryCall() { - } -} // namespace nt - -#ifdef NT_CONSOLE_APP -int main() { - const nt::NT runtime; - std::cout << "RNT running: " << (runtime.IsRunning() ? "yes" : "no") << '\n'; - - // --- 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>(); - type->label = RELATION; - type->disposable = false; - type->methods = {OPEN, CLOSE}; - - const std::vector<std::string> path = {"multigroups", "sakura", "relations", "villager"}; - objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), std::move(type)); - auto* villager_rel = - dynamic_cast<nt::ObjectManager::Relation*>(objects.Find(path)->object.get()); - - // --- Populate backend with three tuples via the Merkle tree --- - nt::SqliteBackend backend; - auto store = [&](std::vector<nt::Attribute> attrs) { - auto bytes = nt::TupleCodec::Serialize(attrs); - auto hash = backend.Put(std::move(bytes)); - auto hash_bin = nt::hex_to_bin(hash); - villager_rel->merkle_root = - nt::Merkle<nt::Hash32>::Insert(backend, villager_rel->merkle_root, hash_bin, hash_bin); - }; - 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(objects, backend); - nt::NamespaceReferenceManager references(objects, backend); - nt::HandlerManager handler(objects, permissions, identities, lifecycles, references); - - int connection = 1; // dummy connection context - nt::HandlerManager::handle* handle = handler.Open(path, &connection); - if (handle == nullptr) { - std::cout << "Failed to open handle for villager\n"; - return 1; - } - - // --- Open a cursor on the relation --- - nt::CursorManager::cursor* cursor = cursors.Open(handle, villager_rel->merkle_root); - if (cursor == nullptr) { - std::cout << "Failed to open cursor for villager\n"; - handler.Close(handle); - return 1; - } - - // --- Build a SCAN plan and pull all tuples lazily through the FOL VM --- - nt::PlanNode plan; - plan.op = nt::FOL_OPERATION_SCAN; - plan.scan_cursor = cursor; - - nt::VM vm(cursors); - std::cout << "Tuples in villager:\n"; - while (nt::Tuple* t = vm.Next(&plan)) { - std::cout << " "; - const nt::Attribute* attr = t->Next(); - while (attr != nullptr) { - std::cout << attr->name << "=" << attr->value; - attr = t->Next(); - if (attr != nullptr) - std::cout << ", "; - } - std::cout << '\n'; - } - - // --- Cleanup --- - cursors.Close(cursor); - handler.Close(handle); - -#ifdef NT_WAIT_ON_EXIT - std::cout << "Press Enter to exit..."; - std::cin.get(); -#endif - - return runtime.IsRunning() ? 0 : 1; -} -#endif diff --git a/src/SqliteBackend.cpp b/src/SqliteBackend.cpp deleted file mode 100644 index 3e01058..0000000 --- a/src/SqliteBackend.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "SqliteBackend.h" - -#include <picosha2.h> -#include <stdexcept> - -namespace nt { - 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" - ");"; - - 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::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; - } -} // namespace nt diff --git a/src/TupleCodec.cpp b/src/TupleCodec.cpp deleted file mode 100644 index a78ed5b..0000000 --- a/src/TupleCodec.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#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; - } -} // namespace nt::TupleCodec diff --git a/src/VM.cpp b/src/VM.cpp deleted file mode 100644 index 5f66f83..0000000 --- a/src/VM.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "VM.h" -#include "Types.h" - -namespace nt { - VM::VM(CursorManager& cursors) : cursors_(cursors) { - } - - std::vector<std::string> VM::ResolveArgs(const std::vector<PathArg>& tmpl, Tuple* from) { - std::vector<std::string> resolved; - resolved.reserve(tmpl.size()); - for (const auto& arg : tmpl) { - if (arg.kind == PathArg::Kind::Const) { - resolved.push_back(arg.name); - continue; - } - std::string val; - for (const auto& attr : from->attrs()) - if (attr.name == arg.name) { - val = attr.value; - break; - } - resolved.push_back(std::move(val)); - } - return resolved; - } - - void VM::ResetInner(CursorManager::cursor* c, std::vector<std::string> args) { - c->args = std::move(args); - c->page.clear(); - c->page_position = 0; - c->fetch_offset = 0; - c->exhausted = false; - } - - Tuple* VM::MergeInto(PlanNode* node, Tuple* left, Tuple* right) { - std::vector<Attribute> merged; - for (const auto& a : left->attrs()) - merged.push_back(a); - for (const auto& a : right->attrs()) - merged.push_back(a); - node->join_buffer.emplace(std::move(merged)); - return &*node->join_buffer; - } - - Tuple* VM::Next(PlanNode* node) { - if (node == nullptr) - return nullptr; - - switch (node->op) { - case Operation::FOL_OPERATION_SCAN: - return cursors_.Next(node->scan_cursor); - - case Operation::FOL_OPERATION_PROJECT: { - Tuple* next = Next(node->left); - if (next == nullptr) - return nullptr; - - std::vector<Attribute> projected; - for (const auto& attr : next->attrs()) { - if (node->project_attrs.find(attr.name) != node->project_attrs.end()) - projected.push_back(attr); - } - - node->project_buffer.emplace(std::move(projected)); - return &*node->project_buffer; - } - - case Operation::FOL_OPERATION_JOIN: { - while (true) { - if (node->join_left == nullptr) { - node->join_left = Next(node->left); - if (node->join_left == nullptr) - return nullptr; - - auto args = ResolveArgs(node->right->scan_args, node->join_left); - ResetInner(node->right->scan_cursor, std::move(args)); - } - - Tuple* right = Next(node->right); - if (right != nullptr) - return MergeInto(node, node->join_left, right); - - node->join_left = nullptr; - } - } - - case Operation::FOL_OPERATION_TAKE: { - if (node->take_count >= node->take_limit) - return nullptr; - Tuple* t = Next(node->left); - if (t) - ++node->take_count; - return t; - } - } - - return nullptr; - } -} // namespace nt |
