diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2026-06-22 21:58:54 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-22 21:58:54 -0300 |
| commit | 663586b8d07c979218ceff79576bb549ee1973c5 (patch) | |
| tree | 8a7dabebe05d0c1ac7a509e8dcf6762364c0c095 /include | |
| parent | 9f0e1c642680c69e10eb1760be07984f9ecc6d39 (diff) | |
| download | RNT-663586b8d07c979218ceff79576bb549ee1973c5.tar.gz RNT-663586b8d07c979218ceff79576bb549ee1973c5.zip | |
Ephemeral relationsmaster-old
Diffstat (limited to 'include')
| -rw-r--r-- | include/EphemeralRelation.h | 143 | ||||
| -rw-r--r-- | include/LifecycleManager.h | 30 | ||||
| -rw-r--r-- | include/ObjectManager.h | 8 | ||||
| -rw-r--r-- | include/RNT_C_API.h | 99 | ||||
| -rw-r--r-- | include/VM.h | 6 |
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; |
