Skip to main content

Optimizer Rules

The optimizer applies an ordered list of rewrite rules to the logical plan. Each rule recognizes a pattern in the plan and replaces it with an equivalent, cheaper one. Writing a rule is especially useful alongside a custom plan operator, but rules can be as simple as removing a redundant pair of operators.

The PatternRule interface

A rule reduces to a starting operator plus two methods:

  • operator: the plan-operator class (or classes) the rule starts matching at, or null to consider every node.
  • match(node): test whether the pattern applies at node; return the captured bindings, or null if it does not apply.
  • transform(node, bindings): return the rewritten plan operator.
interface PatternRule<T extends PlanOperator, U> {
operator: (new (...args: any[]) => T) | (new (...args: any[]) => T)[] | null;
match(node: T): { bindings: U } | null;
transform(node: T, bindings: U): PlanOperator;
}

The provided rules range from small local rewrites (removing neighboring MapFromItem / MapToItem operators) to larger algorithms (merging Projections). The default rule set is a good source of examples.

Registering a rule

Rules are just entries in the optimizer's ordered rules array, and order matters, so place a new rule where it should run relative to the others. A rule that needs the database interface can be provided as a class (the optimizer instantiates it, see PatternRuleConstructor):

new DortDB({
mainLang: SQL(),
optimizer: { rules: [...defaultRules, MyRule] },
});

// or swap the rule set at runtime
db.optimizer.reconfigure({ rules: [MyRule] });