aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/VM.cpp207
1 files changed, 207 insertions, 0 deletions
diff --git a/test/VM.cpp b/test/VM.cpp
new file mode 100644
index 0000000..ed98be0
--- /dev/null
+++ b/test/VM.cpp
@@ -0,0 +1,207 @@
+#include <catch2/catch_test_macros.hpp>
+
+#include "CursorManager.h"
+#include "HandlerManager.h"
+#include "IdentityManager.h"
+#include "InMemoryBackend.h"
+#include "LifecycleManager.h"
+#include "ObjectManager.h"
+#include "PermissionsManager.h"
+#include "TupleCodec.h"
+#include "VM.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+static void store_tuple(nt::InMemoryBackend& backend,
+ const std::vector<std::string>& path,
+ std::vector<nt::Attribute> attrs)
+{
+ auto bytes = nt::TupleCodec::Serialize(attrs);
+ auto hash = backend.Put(std::move(bytes));
+ backend.LinkTuple(path, hash);
+}
+
+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 };
+ return t;
+}
+
+// Generator for eq[A, B]: returns one tuple when A == B, nothing otherwise.
+// Treats both args as integers when parseable, strings otherwise.
+static std::unique_ptr<nt::ObjectManager::ephemeral_object_type> make_eq_type()
+{
+ auto t = std::make_unique<nt::ObjectManager::ephemeral_object_type>();
+ t->label = EPHEMERAL_RELATION;
+ t->disposable = false;
+ t->methods = { OPEN, CLOSE };
+ t->cardinality = nt::ObjectManager::ephemeral_object_type::Cardinality::AlephZero;
+ t->generator = [](const std::vector<std::string>& args,
+ std::size_t offset,
+ std::size_t limit) -> std::vector<nt::Tuple>
+ {
+ if (args.size() < 2 || offset > 0 || limit == 0) return {};
+ if (args[0] != args[1]) return {};
+ return { nt::Tuple({ { "left", args[0] }, { "right", args[1] } }) };
+ };
+ return t;
+}
+
+// ---------------------------------------------------------------------------
+// Fixture: shared manager stack
+// ---------------------------------------------------------------------------
+
+struct Fixture
+{
+ nt::InMemoryBackend backend;
+ nt::ObjectManager objects;
+ nt::PermissionsManager permissions;
+ nt::IdentityManager identities;
+ nt::LifecycleManager lifecycles;
+ nt::HandlerManager handler { objects, permissions, identities, lifecycles };
+ nt::CursorManager cursors { backend };
+ int conn = 1;
+};
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+
+TEST_CASE("SCAN returns all tuples from a stored relation", "[scan]")
+{
+ Fixture f;
+ const std::vector<std::string> path = { "relations", "villager" };
+
+ f.objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type());
+ store_tuple(f.backend, path, { { "name", "John" }, { "age", "30" } });
+ store_tuple(f.backend, path, { { "name", "Peter" }, { "age", "25" } });
+
+ auto* handle = f.handler.Open(path, &f.conn);
+ REQUIRE(handle != nullptr);
+ auto* cursor = f.cursors.Open(handle);
+ REQUIRE(cursor != nullptr);
+
+ nt::PlanNode plan;
+ plan.op = nt::PlanNode::Op::SCAN;
+ plan.scan_cursor = cursor;
+
+ nt::VM vm(f.cursors);
+ int count = 0;
+ while (vm.Next(&plan)) ++count;
+
+ REQUIRE(count == 2);
+
+ f.cursors.Close(cursor);
+ f.handler.Close(handle);
+}
+
+TEST_CASE("JOIN filters a stored relation with eq ephemeral", "[join]")
+{
+ Fixture f;
+ const std::vector<std::string> people_path = { "relations", "people" };
+ const std::vector<std::string> eq_path = { "builtins", "eq" };
+
+ // Register stored relation: people(name, age)
+ f.objects.Register(people_path,
+ std::make_unique<nt::ObjectManager::Relation>(),
+ make_relation_type());
+
+ store_tuple(f.backend, people_path, { { "name", "John" }, { "age", "30" } });
+ store_tuple(f.backend, people_path, { { "name", "Peter" }, { "age", "25" } });
+ store_tuple(f.backend, people_path, { { "name", "Jude" }, { "age", "25" } });
+
+ // Register ephemeral eq relation
+ f.objects.Register(eq_path,
+ std::make_unique<nt::ObjectManager::Relation>(),
+ make_eq_type());
+
+ // Open handles and cursors
+ auto* people_handle = f.handler.Open(people_path, &f.conn);
+ REQUIRE(people_handle != nullptr);
+ auto* eq_handle = f.handler.Open(eq_path, &f.conn);
+ REQUIRE(eq_handle != nullptr);
+
+ auto* people_cursor = f.cursors.Open(people_handle);
+ REQUIRE(people_cursor != nullptr);
+ auto* eq_cursor = f.cursors.Open(eq_handle); // starts exhausted — JOIN resets it
+ REQUIRE(eq_cursor != nullptr);
+
+ // Plan: JOIN( SCAN(people), SCAN(eq[ Var("age"), Const("25") ]) )
+ nt::PlanNode right;
+ right.op = nt::PlanNode::Op::SCAN;
+ right.scan_cursor = eq_cursor;
+ right.scan_args = { nt::PathArg::Var("age"), nt::PathArg::Const("25") };
+
+ nt::PlanNode left;
+ left.op = nt::PlanNode::Op::SCAN;
+ left.scan_cursor = people_cursor;
+
+ nt::PlanNode root;
+ root.op = nt::PlanNode::Op::JOIN;
+ root.left = &left;
+ root.right = &right;
+
+ // Execute and collect matched names
+ nt::VM vm(f.cursors);
+ std::vector<std::string> matched;
+ while (nt::Tuple* t = vm.Next(&root))
+ {
+ for (const auto& attr : t->attrs())
+ if (attr.name == "name")
+ matched.push_back(attr.value);
+ }
+
+ // Jude (age=25) and Peter (age=25) should match; Jogn (age=30) should not
+ REQUIRE(matched.size() == 2);
+ REQUIRE(std::find(matched.begin(), matched.end(), "Jude") != matched.end());
+ REQUIRE(std::find(matched.begin(), matched.end(), "Peter") != matched.end());
+ REQUIRE(std::find(matched.begin(), matched.end(), "John") == matched.end());
+
+ f.cursors.Close(people_cursor);
+ f.cursors.Close(eq_cursor);
+ f.handler.Close(people_handle);
+ f.handler.Close(eq_handle);
+}
+
+TEST_CASE("TAKE limits tuples emitted from a SCAN", "[take]")
+{
+ Fixture f;
+ const std::vector<std::string> path = { "relations", "items" };
+
+ f.objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type());
+ store_tuple(f.backend, path, { { "id", "1" } });
+ store_tuple(f.backend, path, { { "id", "2" } });
+ store_tuple(f.backend, path, { { "id", "3" } });
+
+ auto* handle = f.handler.Open(path, &f.conn);
+ REQUIRE(handle != nullptr);
+ auto* cursor = f.cursors.Open(handle);
+ REQUIRE(cursor != nullptr);
+
+ nt::PlanNode scan;
+ scan.op = nt::PlanNode::Op::SCAN;
+ scan.scan_cursor = cursor;
+
+ nt::PlanNode take;
+ take.op = nt::PlanNode::Op::TAKE;
+ take.left = &scan;
+ take.take_limit = 2;
+
+ nt::VM vm(f.cursors);
+ int count = 0;
+ while (vm.Next(&take)) ++count;
+
+ REQUIRE(count == 2);
+
+ f.cursors.Close(cursor);
+ f.handler.Close(handle);
+}