java.lang.Object
com.darkcollective.relix.plan.internal.Planner

public final class Planner extends Object
Translates an optimised logical RelNode tree into an executable PhysicalNode plan, fixing every physical decision up front:
  • Join algorithm — PhysicalNode.JoinAlgorithm.MERGE when at least one input already delivers an ordering satisfying the join keys (the other gets a PhysicalNode.Sort enforcer inserted); PhysicalNode.JoinAlgorithm.HASH when an equi-join key exists but neither input is sorted; otherwise PhysicalNode.JoinAlgorithm.NESTED_LOOP. MERGE is eligible for INNER, NATURAL, SEMI, and ANTI joins.
  • Build side — for HASH/nested-loop: the cheaper input is built for symmetric joins (inner, natural, full outer, product); the non-preserved side is built for asymmetric joins. For MERGE joins the field is PhysicalNode.BuildSide.RIGHT by convention (not used by the merge executor).
  • Schemas — each physical node's output schema is resolved from the supplied SchemaAnnotations (or the relation's declared schema for a scan), so the executor never re-infers or looks schemas up by identity.

Named views (QueryRelationSymbol) are inlined: a reference to a view is planned by planning the view's body, so views never appear as plan nodes.

The supplied SchemaAnnotations must cover every node the planner visits — both the input tree and the bodies of any inlined views. A model's SemanticModel.nodeSchemas() satisfies this for an un-rewritten tree; a rewritten tree should be annotated via SchemaInference first.

Thread safety

A Planner is bound to one symbol table and annotation set; it is stateless beyond those and may be reused across plan(com.darkcollective.relix.ast.RelNode) calls.

  • Constructor Details

  • Method Details

    • withObservedCardinalities

      public Planner withObservedCardinalities(ObservedCardinalities observed)
      Lets this planner prefer a row count a previous run actually produced over the one the cost model would compute for the same expression.

      A wither rather than a constructor parameter for the reason withSolvers(com.darkcollective.relix.solver.SolverCatalog) is one: this class already has six constructors, and the seventh argument nobody passes is how a constructor list becomes unreadable.

      Parameters:
      observed - the recorded counts; must not be null
      Returns:
      this planner, for chaining
    • withFunctionContext

      public Planner withFunctionContext(FunctionContext context)
      Lets this planner evaluate a call that is constant for the run — NOW() — and push the resulting value, rather than declining the fold because the backend would answer from its own clock.

      The context must be the one the execution will use, because the substituted value has to equal the one the unfolded half of the same query computes. That is why it is passed rather than made here: ExecutionContext pins the run's instant, and this is that instant travelling to the renderers.

      Parameters:
      context - the run's ambient state; must not be null
      Returns:
      this planner, for chaining
    • withSolvers

      public Planner withSolvers(SolverCatalog catalog)
    • estimates

      public PlanEstimates estimates()
      The estimated row counts recorded while planning, keyed by plan node.

      Populated by plan(RelNode) and empty before it runs. A node the planner built inside another arm rather than through plan — an order-enforcing Sort wrapper, say — carries no estimate, which reads as unknown; see PlanEstimates for why unknown is deliberately not zero.

      Returns:
      the estimates for the plan this instance produced; never null
    • plan

      public PhysicalNode plan(RelNode node)
      Produces the physical plan for node.

      Each node is costed as it is built, and the estimate is filed in estimates() rather than carried on the node — see PlanEstimates for the reasoning. The estimate is taken from the logical node, which is where the cost model's statistics apply; a pushed-down scan is therefore costed as the sub-tree it replaced, which is the number a reader wants (how many rows come back), not the number of tables it folded.

      Parameters:
      node - the logical root to plan; must not be null
      Returns:
      the executable physical plan
    • mergeOrdering

      public static Ordering mergeOrdering(List<Integer> keyIndices, Schema schema)
      Builds the required Ordering for a merge join on keyIndices (column positions in schema). Uses SortDirection.ASC for all keys — the planner only promotes MERGE when the delivered ordering already satisfies this (or inserts an ASC enforcer), so direction is consistent on both sides.

      Also the ordering a merge join delivers: shared with PhysicalNode.Join.deliveredOrdering() so the required and delivered forms are one definition rather than two that can drift.