aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.envrc1
-rw-r--r--.gitignore3
-rw-r--r--include/VM.h11
-rw-r--r--src/VM.cpp17
-rw-r--r--test/VM.cpp40
5 files changed, 69 insertions, 3 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
index 58d5a99..a69df86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.vs
build
+_build
out
x64
Debug
@@ -32,6 +33,8 @@ RNT.lib
libRNT.so
libRNT.dylib
docs/generated/
+.cache
+_build_nix
# vcpkg manifest mode artifacts
vcpkg_installed
diff --git a/include/VM.h b/include/VM.h
index 96cf493..3bfe521 100644
--- a/include/VM.h
+++ b/include/VM.h
@@ -6,6 +6,7 @@
#include <optional>
#include <string>
+#include <unordered_set>
#include <vector>
/**
@@ -88,12 +89,11 @@ namespace nt
* (O(1) membership probe when all args are bound).
* - TAKE : passes at most take_limit tuples through, then returns nullptr. Use
* this to bound scans over AlephZero ephemeral relations.
- *
- * TODO: Add PROJECT operator as the query layer grows.
+ * - PROJECT: filters each input tuple to the named attributes in project_attrs.
*/
struct PlanNode
{
- enum class Op { SCAN, JOIN, TAKE };
+ enum class Op { SCAN, JOIN, TAKE, PROJECT };
Op op;
PlanNode* left = nullptr;
@@ -118,6 +118,10 @@ namespace nt
std::size_t take_limit = 0;
std::size_t take_count = 0; /**< Runtime counter; mutable during execution. */
+ /** PROJECT: unordered set of attribute names to keep. */
+ std::unordered_set<std::string> project_attrs;
+ std::optional<Tuple> project_buffer;
+
/**
* JOIN runtime state.
*
@@ -156,6 +160,7 @@ namespace nt
* probes until a match is found or the inner is exhausted.
* - TAKE counts emitted tuples and returns nullptr once the limit
* is reached.
+ * - PROJECT filters an input tuple to the requested attribute names.
*
* @param node Root of the plan subtree to evaluate.
* @return Next matching tuple, or nullptr when the plan is exhausted.
diff --git a/src/VM.cpp b/src/VM.cpp
index 1f1ed7f..a042aed 100644
--- a/src/VM.cpp
+++ b/src/VM.cpp
@@ -1,4 +1,5 @@
#include "VM.h"
+#include "Types.h"
namespace nt
{
@@ -50,6 +51,22 @@ namespace nt
case PlanNode::Op::SCAN:
return cursors_.Next(node->scan_cursor);
+ case PlanNode::Op::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 PlanNode::Op::JOIN:
{
while (true)
diff --git a/test/VM.cpp b/test/VM.cpp
index b0a602f..f4ce453 100644
--- a/test/VM.cpp
+++ b/test/VM.cpp
@@ -221,3 +221,43 @@ TEST_CASE("TAKE limits tuples emitted from a SCAN", "[take]")
f.cursors.Close(cursor);
f.handler.Close(handle);
}
+
+TEST_CASE("PROJECT filters tuple attributes by name", "[project]")
+{
+ Fixture f;
+ const std::vector<std::string> path = { "relations", "people" };
+
+ f.objects.Register(path, std::make_unique<nt::ObjectManager::Relation>(), make_relation_type());
+ store_tuple(f.backend, f.objects, path, { { "name", "John" }, { "age", "30" }, { "city", "Lisbon" } });
+
+ auto* handle = f.handler.Open(path, &f.conn);
+ REQUIRE(handle != nullptr);
+ auto* rel = dynamic_cast<nt::ObjectManager::Relation*>(f.objects.Find(path)->object.get());
+ auto* cursor = f.cursors.Open(handle, rel->merkle_root);
+ REQUIRE(cursor != nullptr);
+
+ nt::PlanNode scan;
+ scan.op = nt::PlanNode::Op::SCAN;
+ scan.scan_cursor = cursor;
+
+ nt::PlanNode project;
+ project.op = nt::PlanNode::Op::PROJECT;
+ project.left = &scan;
+ project.project_attrs = { "name", "city" };
+
+ nt::VM vm(f.cursors);
+ nt::Tuple* t = vm.Next(&project);
+ REQUIRE(t != nullptr);
+
+ std::vector<std::string> names;
+ for (const auto& attr : t->attrs()) names.push_back(attr.name);
+
+ REQUIRE(names.size() == 2);
+ REQUIRE(std::find(names.begin(), names.end(), "name") != names.end());
+ REQUIRE(std::find(names.begin(), names.end(), "city") != names.end());
+ REQUIRE(std::find(names.begin(), names.end(), "age") == names.end());
+ REQUIRE(vm.Next(&project) == nullptr);
+
+ f.cursors.Close(cursor);
+ f.handler.Close(handle);
+}