aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authorMarcos Magueta <maguetamarcos@gmail.com>2026-06-22 21:58:54 -0300
committerGitHub <noreply@github.com>2026-06-22 21:58:54 -0300
commit663586b8d07c979218ceff79576bb549ee1973c5 (patch)
tree8a7dabebe05d0c1ac7a509e8dcf6762364c0c095 /include
parent9f0e1c642680c69e10eb1760be07984f9ecc6d39 (diff)
downloadRNT-master-old.tar.gz
RNT-master-old.zip
Ephemeral relationsmaster-old
Diffstat (limited to '')
-rw-r--r--include/EphemeralRelation.h143
-rw-r--r--include/LifecycleManager.h30
-rw-r--r--include/ObjectManager.h8
-rw-r--r--include/RNT_C_API.h99
-rw-r--r--include/VM.h6
5 files changed, 279 insertions, 7 deletions
diff --git a/include/EphemeralRelation.h b/include/EphemeralRelation.h
new file mode 100644
index 0000000..9834337
--- /dev/null
+++ b/include/EphemeralRelation.h
@@ -0,0 +1,143 @@
+#pragma once
+
+#include "LifecycleManager.h"
+#include "ObjectManager.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+/**
+ * @file EphemeralRelation.h
+ * @brief Writer path and lifetime helpers for ephemeral relations.
+ *
+ * An ephemeral relation has no tuple storage of its own; its tuples are
+ * produced on demand by a generator (see ObjectManager.h). Relational-algebra
+ * operations (select, project, join) all yield one. The functions here register
+ * such a relation into the object manager, hash its identity, pin the base
+ * relations it is defined atop, and release session-owned entries at session
+ * close.
+ *
+ * They are free functions over an ObjectManager and a LifecycleManager, not a
+ * stateful manager object.
+ *
+ * See docs/ephemeral-relations.org for the lifetime model.
+ */
+
+namespace nt::Ephemeral {
+ /**
+ * @brief Lifetime class of a freshly produced ephemeral relation.
+ *
+ * Naming is what makes a result outlive its cursor: an anonymous result is
+ * owned by nobody.
+ *
+ * - Scratch: an anonymous per-statement intermediate. Registered under
+ * /system/sessions/<session>/scratch/<root> and not session-pinned, so it
+ * is collected by reference counting once its consuming cursor closes.
+ * - Named: a view-like binding. Registered under
+ * /system/sessions/<session>/ephemeral/<name> with one session-ownership
+ * Pin, so it survives between commands and can be re-requested. Released
+ * at rnt_session_close or (later) by the idle reaper.
+ */
+ enum class Tier { Scratch, Named };
+
+ /**
+ * @brief Splits a slash-joined logical path into registry components.
+ *
+ * Drops a leading slash and any empty segments, matching split_path in the
+ * C API layer. Used by the writer path here and by
+ * LifecycleManager::CascadeEphemeral when re-splitting stored dependency
+ * paths at GC time.
+ */
+ std::vector<std::string> SplitPath(const std::string& path);
+
+ /**
+ * @brief Deterministic merkle_root for an ephemeral relation.
+ *
+ * Hashes SHA256 over the generator identity, the schema, and the
+ * merkle_roots of the declared base relations. Two ephemerals with the same
+ * operator, schema, and base roots hash identically, so a repeated query
+ * reuses one registry entry instead of creating duplicates; rebinding a base
+ * produces a new root that propagates like a tuple insertion in a stored
+ * relation.
+ *
+ * Schema entries and base roots are sorted internally, so input order does
+ * not affect the result.
+ *
+ * @param generator_identity Stable label of the producing operator/builtin
+ * (e.g. "join", "select:age", "eq").
+ * @param schema (attribute_name, attribute_type) pairs.
+ * @param base_roots merkle_roots of the base relations, any order.
+ * @return 64-char lowercase-hex SHA256 digest.
+ */
+ std::string ComposeRoot(const std::string& generator_identity,
+ const std::vector<std::pair<std::string, std::string>>& schema,
+ const std::vector<std::string>& base_roots);
+
+ /**
+ * @brief Registers an ephemeral relation produced by a relational operation.
+ *
+ * Resolves each dependency in the registry to read its current merkle_root
+ * (for the identity hash) and to take a structural Pin on it: a base
+ * relation cannot be GC'd while an ephemeral is defined atop it. Then
+ * registers an EphemeralRelation IObject with an ephemeral_object_type
+ * carrying the generator and cardinality. For Tier::Named, an extra
+ * session-ownership Pin is taken so the entry outlives its cursor.
+ *
+ * Idempotent: an existing entry at the target path (identical scratch root,
+ * or a live named binding) is returned without re-registering or
+ * double-pinning.
+ *
+ * The caller should Monitor the returned entry when it opens a cursor over
+ * it; a Scratch entry with no cursor and no session pin lingers until
+ * session close.
+ *
+ * @param objects Registry to register into.
+ * @param lifecycles Lifecycle manager for the dependency / ownership pins.
+ * @param session_hash Owning session's hash (the namespace parent).
+ * @param tier Scratch (anonymous) or Named (session-owned).
+ * @param name Leaf name for Tier::Named; ignored for Tier::Scratch
+ * (the composed root is used as the leaf instead).
+ * @param generator Tuple-producing generator for this relation.
+ * @param cardinality Finite / ConstrainedFinite / AlephZero / Continuum.
+ * @param generator_identity Stable operator label, see ComposeRoot.
+ * @param schema (attribute_name, attribute_type) pairs.
+ * @param dependencies Slash-joined logical paths (no leading slash) of the
+ * base relations this is defined atop, e.g.
+ * "system/snapshots/<h>/relations/<rel>". Stored verbatim
+ * on the object and re-split at GC time. Every path must
+ * resolve; an unresolvable dependency fails the whole
+ * registration, since a stored-but-unpinned path would
+ * make CascadeEphemeral Unpin a hold it never took.
+ * @return Borrowed pointer to the registered (or pre-existing) entry, or
+ * nullptr on error (empty session hash, empty name for Tier::Named,
+ * or any unresolvable dependency).
+ */
+ ObjectManager::registry* Register(
+ ObjectManager& objects, LifecycleManager& lifecycles, const std::string& session_hash,
+ Tier tier, const std::string& name,
+ ObjectManager::ephemeral_object_type::Generator generator,
+ ObjectManager::ephemeral_object_type::Cardinality cardinality,
+ const std::string& generator_identity,
+ const std::vector<std::pair<std::string, std::string>>& schema,
+ const std::vector<std::string>& dependencies);
+
+ /**
+ * @brief Releases all ephemeral relations owned by a session.
+ *
+ * Called by rnt_session_close before the session entry is removed. Named
+ * entries directly under the session's "ephemeral" subdir have their
+ * session-ownership Pin released (collecting them when no cursor or
+ * dependent still holds them, cascading their base-relation Unpins). Scratch
+ * entries under the session's "scratch" subdir were never session-pinned;
+ * any that linger with no open cursor are collected directly. Entries still
+ * held by an open cursor are left for the normal handle-release path.
+ *
+ * @param objects Registry to walk.
+ * @param lifecycles Lifecycle manager driving the releases.
+ * @param session_hash Session whose ephemerals to release.
+ * @return Number of entries released or collected.
+ */
+ std::size_t ReleaseSession(ObjectManager& objects, LifecycleManager& lifecycles,
+ const std::string& session_hash);
+} // namespace nt::Ephemeral
diff --git a/include/LifecycleManager.h b/include/LifecycleManager.h
index c1e63d8..daf82dd 100644
--- a/include/LifecycleManager.h
+++ b/include/LifecycleManager.h
@@ -96,6 +96,24 @@ namespace nt {
bool IsEligibleForGC(ObjectManager::registry* object) const;
/**
+ * @brief Forces a GC attempt on an object without decrementing a counter.
+ *
+ * Runs the same eligibility check, type-specific cascade, and Unregister
+ * as Unmonitor / Unpin, but is callable directly. No-op when the object
+ * is still referenced (either counter non-zero) or is a named-lifetime
+ * type (BRANCH / SESSION).
+ *
+ * Used by the session-close path to collect scratch ephemerals that were
+ * never session-pinned and whose consuming cursor has already closed, and
+ * (later) by the idle reaper to sweep named entries after Unpinning their
+ * session-ownership pin.
+ *
+ * @param object Object to attempt to collect.
+ * @return True when the object was eligible and collected.
+ */
+ bool Collect(ObjectManager::registry* object);
+
+ /**
* @brief Serializes contention for changes to mutable reference states.
*
* Immutable objects — relation snapshots, multigroup snapshots,
@@ -156,6 +174,18 @@ namespace nt {
*/
void CascadeBranchTree(ObjectManager::registry* branch_tree);
+ /**
+ * @brief Releases pins an EPHEMERAL_RELATION holds on its base relations.
+ *
+ * An ephemeral relation Pins every entry named in its `dependencies`
+ * list at registration time (a stored or ephemeral base cannot be GC'd
+ * while something is defined atop it). When the ephemeral itself becomes
+ * eligible for GC, this resolves each dependency path and Unpins it.
+ * Dependency paths are stored slash-joined with no leading slash and
+ * split on '/' to address the registry. Called only from TryCollect.
+ */
+ void CascadeEphemeral(ObjectManager::registry* ephemeral);
+
ObjectManager& objects_;
IStorageBackend& storage_;
};
diff --git a/include/ObjectManager.h b/include/ObjectManager.h
index 355f4c7..adc780f 100644
--- a/include/ObjectManager.h
+++ b/include/ObjectManager.h
@@ -88,10 +88,10 @@ namespace nt {
* also the foundation for attribute-level provenance tracking added
* later.
*
- * @todo Define the merkle_root composition function (hashes the
- * generator identity + schema + sorted base merkle_roots).
- * Until ephemeral relations are registered through a writer
- * path this remains a documentation-only contract.
+ * The merkle_root composition function is nt::Ephemeral::ComposeRoot
+ * (SHA256 over generator identity + schema + sorted base merkle_roots);
+ * nt::Ephemeral::Register is the writer path that populates both
+ * fields. See include/EphemeralRelation.h.
*/
class EphemeralRelation : public IObject {
public:
diff --git a/include/RNT_C_API.h b/include/RNT_C_API.h
index 19f6165..d12b23c 100644
--- a/include/RNT_C_API.h
+++ b/include/RNT_C_API.h
@@ -138,6 +138,105 @@ NT_API int rnt_session_set_branch(const char* session_hash, const char* branch_n
const char* target_hash);
/* ------------------------------------------------------------------ */
+/* Ephemeral relations */
+/* ------------------------------------------------------------------ */
+
+/**
+ * Opaque sink passed to a generator callback. The callback pushes each tuple
+ * it produces through rnt_sink_emit; the runtime copies the tuple out
+ * immediately, so the string only needs to stay valid for the duration of
+ * the rnt_sink_emit call.
+ */
+typedef void* rnt_tuple_sink_t;
+
+/**
+ * @brief Tuple-producing callback backing an ephemeral relation.
+ *
+ * Invoked by the cursor layer each time a page of tuples is needed. The
+ * callback must emit at most @p limit tuples starting at logical position
+ * @p offset by calling rnt_sink_emit once per tuple. Calls may re-enter the
+ * RNT API (e.g. to execute a VM plan over base relations); the runtime is
+ * single-threaded, so no locking is required.
+ *
+ * @param ctx Opaque context pointer given at registration time.
+ * @param args Bound argument values written by a JOIN before probing,
+ * newline-separated. Empty string when scanned standalone.
+ * @param offset Zero-based logical tuple offset (for pagination).
+ * @param limit Maximum number of tuples to emit.
+ * @param sink Pass to rnt_sink_emit for each produced tuple.
+ * @return 0 on success, negative on error (the page is treated as empty).
+ */
+typedef int (*rnt_generator_fn)(void* ctx, const char* args, size_t offset, size_t limit,
+ rnt_tuple_sink_t sink);
+
+/**
+ * @brief Emits one tuple from inside a generator callback.
+ *
+ * @param sink Sink received by the callback.
+ * @param tuple_kv Newline-separated "name=value" attribute lines — the same
+ * wire format rnt_link_tuple consumes and rnt_cursor_next
+ * returns. Copied before returning.
+ * @return 0 on success, negative on error.
+ */
+NT_API int rnt_sink_emit(rnt_tuple_sink_t sink, const char* tuple_kv);
+
+/**
+ * @brief Registers an ephemeral relation owned by a session.
+ *
+ * An ephemeral relation has no tuple storage of its own: its tuples are
+ * produced on demand by @p generator. The entry lands under the session's
+ * namespace — /system/sessions/<hash>/ephemeral/<name> when @p named is
+ * non-zero (a named binding that survives between commands until dropped or
+ * session close), or /system/sessions/<hash>/scratch/<composed_root> when
+ * zero (an anonymous intermediate collected as soon as its consuming cursor
+ * closes). Scan it through the normal pipeline: rnt_open_handle on the
+ * registered path, then rnt_cursor_open / rnt_plan_scan.
+ *
+ * Registration pins every dependency, so a base relation cannot be collected
+ * while this relation is defined atop it; the pins are released when the
+ * ephemeral itself is collected. Registration is idempotent for an identical
+ * scratch result or a live named binding. See docs/ephemeral-relations.org.
+ *
+ * @param session_hash Hash returned by rnt_session_open.
+ * @param named Non-zero for a named binding, 0 for scratch.
+ * @param name Leaf name for named bindings; ignored for scratch.
+ * @param generator Tuple-producing callback. Must stay callable until
+ * the entry is collected.
+ * @param generator_ctx Opaque pointer passed back to @p generator.
+ * @param cardinality 0 Finite, 1 ConstrainedFinite, 2 AlephZero,
+ * 3 Continuum.
+ * @param generator_identity Stable operator label for the identity hash,
+ * e.g. "join:id", "select:age".
+ * @param schema_kv Newline-separated "name=type" attribute lines.
+ * @param dependencies Newline-separated logical paths of the base
+ * relations this is defined atop, e.g.
+ * "system/snapshots/<h>/relations/<rel>". Every
+ * path must resolve or the call fails.
+ * @param path_out Optional (may be NULL): set to the heap-allocated
+ * slash-joined registered path. Release with
+ * rnt_free_string().
+ * @return 0 on success, negative on error.
+ */
+NT_API int rnt_register_ephemeral_relation(const char* session_hash, int named, const char* name,
+ rnt_generator_fn generator, void* generator_ctx,
+ int cardinality, const char* generator_identity,
+ const char* schema_kv, const char* dependencies,
+ char** path_out);
+
+/**
+ * @brief Drops a named ephemeral relation, releasing its session-ownership pin.
+ *
+ * The entry is collected as soon as no cursor or dependent ephemeral still
+ * holds it, cascading the pins it took on its base relations. Scratch entries
+ * cannot be dropped by name; they die with their consuming cursor.
+ *
+ * @param session_hash Owning session's hash.
+ * @param name Leaf name given at registration.
+ * @return 0 on success, negative when no such named binding exists.
+ */
+NT_API int rnt_drop_ephemeral_relation(const char* session_hash, const char* name);
+
+/* ------------------------------------------------------------------ */
/* Handle lifecycle */
/* ------------------------------------------------------------------ */
diff --git a/include/VM.h b/include/VM.h
index 3f2f665..2ee7fe8 100644
--- a/include/VM.h
+++ b/include/VM.h
@@ -119,9 +119,9 @@ namespace nt {
PlanNode* right = nullptr;
/**
- * SCAN: pre-opened cursor. For ephemeral relations this cursor is
- * initially exhausted; the JOIN resets it with resolved args before
- * each probe.
+ * SCAN: pre-opened cursor. For ephemeral relations the JOIN resets it
+ * (args, offset, exhausted flag) with resolved args before each probe,
+ * overwriting whatever standalone-scan state it opened with.
*/
CursorManager::cursor* scan_cursor = nullptr;