aboutsummaryrefslogblamecommitdiffstats
path: root/include/VM.h
blob: 2ee7fe8d8855d6816b4bf482e3e16c298dd52214 (plain) (tree)
1
2
3
4
5
6
7
8
9


                

                          
 

                   
                        

                 




                                                                                          
              
       
                
































                                                                                   
       











                                                                                 
                    

                                       
                  

                                                                                            











                                                 



                                  

      





                                                                                       







                                                                                      
                                                                                    









                                                                                   
       


                                 
                                  
 
           


                                                                               
           
                                                     












                                                                                      



                                                                 











                                                                               
                                   
                                         



















                                                                                 

      

              











                                                                                      






                                                                            
                                                                             





                                                                              
              
                                




                                                                              
                                                                                                   

                                                                              
                                                                                        
 







                                                                                  

                                                                             
      
                 
#pragma once

#include "Api.h"
#include "CursorManager.h"
#include "Types.h"

#include <optional>
#include <string>
#include <unordered_set>
#include <vector>

/**
 * @file VM.h
 * @brief Declares the logic execution boundary for relational and higher-order workloads.
 */

namespace nt {
    /**
     * @class VM
     * @brief Hosts the first-order logic (FOL) and second-order logic (SOL) cores.
     *
     * This partitioning exists to ensure retrieval terminates when execution is
     * constrained to first-order logic.
     *
     * - Tarski runtime (FOL): a relational engine that implements Tarski's
     *   relational calculus. It uses a deterministic Volcano-iterator model to
     *   perform relational algebra operations such as joins, projections, and
     *   selects directly against the CursorManager and a prepared plan.
     *
     * - Karuta runtime (SOL): an isolated WAM-based environment for
     *   higher-order logic programming. It handles recursion, choice points, and
     *   other programming constructs that do not guarantee termination.
     *
     * @remark Lazy evaluation and paging strategy
     *
     * Both the Tarski first-order engine and the Karuta second-order engine are
     * strictly pull-based. Data is streamed lazily from the CursorManager to
     * preserve a constant memory footprint regardless of relation size.
     *
     * Pipeline dynamics:
     * - Demand-driven: the Karuta WAM requests a fact only when a goal needs
     *   satisfaction. A request propagates down the algebra tree as a chain of
     *   Next() calls.
     *
     * - Short-circuiting: if the Karuta engine finds a solution or reaches a
     *   failure that invalidates a branch, the Tarski iterator is discarded or
     *   reset without materializing the remaining tuples.
     *
     * - Resource stewardship: by maintaining laziness up to the highest logical
     *   level, the runtime minimizes pressure on ObjectManager and keeps
     *   snapshots pinned through LifecycleManager::Monitor only for the minimum
     *   time required.
     */

    /**
     * @brief One segment of a parameterized relation path in a SCAN node.
     *
     * Var segments are resolved at runtime from the outer tuple of a JOIN:
     * the JOIN writes the bound value into the cursor's args vector before
     * each probe. Const segments are ground literals fixed at plan construction.
     *
     * The resolved args vector is passed to the ephemeral relation's generator
     * (for EPHEMERAL_RELATION cursors) or ignored (for stored RELATION cursors,
     * which need no per-probe parameterization).
     */
    struct PathArg {
        enum class Kind { Var, Const };

        Kind kind;
        std::string name; /**< Attribute name to look up (Var), or literal value (Const). */

        static PathArg Var(std::string attr) {
            return {Kind::Var, std::move(attr)};
        }
        static PathArg Const(std::string val) {
            return {Kind::Const, std::move(val)};
        }
    };

    enum Operation {
        FOL_OPERATION_SCAN = 1,
        FOL_OPERATION_JOIN,
        FOL_OPERATION_TAKE,
        FOL_OPERATION_PROJECT,
        FOL_OPERATION_MATERIALIZE,
        FOL_OPERATION_RENAME,
        FOL_OPERATION_UNION
    };

    /**
     * @brief A node in the Volcano operator tree executed by the Tarski (FOL) runtime.
     *
     * Each node represents one relational algebra operator. The tree is demand-driven:
     * VM::Next() propagates pull requests from root to leaves.
     *
     * Supported operators:
     * - SCAN  : pulls tuples from a pre-opened cursor (stored or ephemeral relation).
     * - JOIN  : nested-loop join. For each outer (left) tuple, resolves scan_args on
     *           the inner (right) SCAN, resets its cursor, and probes it. Works for
     *           both stored relations (full re-scan of inner) and ephemeral relations
     *           (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.
     * - PROJECT: filters each input tuple to the named attributes in project_attrs.
     * - MATERIALIZE: on its first scan it pulls the whole child and keeps a
     *           private copy of every tuple. On later scans it replays that copy
     *           and never reads the child again. Sits on the inner side of a
     *           nested-loop JOIN so the inner relation is read once instead of
     *           once per outer tuple. This only works when the child does not
     *           depend on the outer tuple, meaning it has no Var SCAN leaves that
     *           the JOIN binds, because the same cached tuples are handed back for
     *           every outer tuple. Rewind does not pass the outer tuple through
     *           this node, so it is up to the planner to place it only where the
     *           child is independent of the outer side.
     */
    struct PlanNode {
        Operation op;
        PlanNode* left = nullptr;
        PlanNode* right = nullptr;

        /**
         * 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;

        /**
         * SCAN: argument template for parameterized relations.
         * Var entries are resolved from the outer JOIN tuple; Const entries
         * are used as-is. The resolved values are written into scan_cursor->args
         * by the JOIN before it calls Next() on this node.
         */
        std::vector<PathArg> scan_args;

        /** TAKE: maximum number of tuples to emit before returning nullptr. */
        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.
         *
         * join_left holds the current outer tuple while probing the inner
         * (right) side. The pointer is stable for the lifetime of one outer
         * iteration because Next(left) is only called when join_left is reset
         * to nullptr, which happens only after the inner side is exhausted.
         *
         * join_buffer holds the merged output tuple and is overwritten on each
         * successful join. Callers must consume the returned pointer before
         * calling Next() again — same contract as a cursor page pointer.
         */
        Tuple* join_left = nullptr;
        std::optional<Tuple> join_buffer;
        std::unordered_set<std::string> join_attrs;

        /**
         * MATERIALIZE runtime state.
         *
         * mat_buffer holds owned copies of the child's tuples, filled on the
         * first scan. mat_done marks the cache complete (child exhausted).
         * mat_pos is the replay cursor, reset to 0 by Rewind on each later scan.
         */
        std::vector<Tuple> mat_buffer;
        std::size_t mat_pos = 0;
        bool mat_done = false;

        /** RENAME runtime state. */
        PlanNode* upstream = nullptr;
        std::unordered_map<std::string, std::string> attrs;

        /** UNION runtime state. */
        std::vector<PlanNode*> nodes;
        std::vector<PlanNode*>::iterator current_node;
    };

    class VM {
      public:
        // TODO: CursorManager should be injected as a shared runtime instance,
        // matching the pattern flagged for HandlerManager and LifecycleManager.
        //
        // A single CursorManager instance supports multiple simultaneous cursors —
        // all iteration state lives in each cursor struct, not in the manager.
        // A JOIN plan, for example, holds two independent cursors (one per SCAN leaf)
        // and both are driven through the same CursorManager reference.
        explicit VM(CursorManager& cursors);

        /**
         * @brief Pulls the next tuple from the plan tree (Tarski/FOL core).
         *
         * Propagates a Next() call down the operator tree:
         * - SCAN  delegates to CursorManager::Next.
         * - JOIN  drives the nested-loop: for each outer tuple it resolves
         *         scan_args on the inner SCAN, resets the inner cursor, and
         *         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.
         */
        Tuple* Next(PlanNode* node);

      private:
        CursorManager& cursors_;

        /**
         * Resolves a scan_args template against an outer tuple, producing the
         * concrete args vector to write into the inner cursor before probing.
         */
        static std::vector<std::string> ResolveArgs(const std::vector<PathArg>& tmpl, Tuple* from);

        /** Resets the inner cursor state and writes resolved args into it. */
        static void ResetInner(CursorManager::cursor* c, std::vector<std::string> args);

        /**
         * Restarts the inner (right) subtree of a JOIN for a new outer tuple.
         * Walks down to every SCAN leaf, rebinds its scan_args to the outer
         * tuple, and resets its cursor. On the way it also clears the operator
         * state it passes through, such as TAKE counters and nested JOIN buffers.
         */
        void Rewind(PlanNode* node, Tuple* outer);

        /** Merges left and right tuple attributes into node->join_buffer. */
        static Tuple* MergeInto(PlanNode* node, Tuple* left, Tuple* right);
    };
} // namespace nt