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, ornullto consider every node.match(node): test whether the pattern applies atnode; return the captured bindings, ornullif 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] });