Skip to content

Add RuleSetCustomizer SPI for multi-stage planner Calcite rules#18387

Open
gortiz wants to merge 1 commit intoapache:masterfrom
gortiz:broker-rule-customizer-spi
Open

Add RuleSetCustomizer SPI for multi-stage planner Calcite rules#18387
gortiz wants to merge 1 commit intoapache:masterfrom
gortiz:broker-rule-customizer-spi

Conversation

@gortiz
Copy link
Copy Markdown
Contributor

@gortiz gortiz commented Apr 30, 2026

Introduces a ServiceLoader-discovered SPI that lets plugins add, remove, reorder, or replace the Calcite HEP rules used by the multi-stage query engine. The OSS defaults are themselves contributed by DefaultRuleSetCustomizer (registered via META-INF/services), and plugin customizers run on top.

  • New Phase enum covers every HEP phase QueryEnvironment runs: BASIC, FILTER_PUSHDOWN, PROJECT_PUSHDOWN, PRUNE, POST_LOGICAL, POST_LOGICAL_V2, POST_LOGICAL_ENRICHED_JOIN. Append-only contract.
  • New RuleSetCustomizer interface — plugins implement customize(Phase, List<RelOptRule>) and modify the per-phase list in place.
  • New PinotRuleSet owns the per-phase, immutable rule lists and is the single source of truth read by QueryEnvironment. The default instance is built lazily from ServiceLoader discovery.
  • DefaultRuleSetCustomizer replaces the static PinotQueryRuleSets lists; the deleted class is the rename source.
  • QueryEnvironment reads every phase's rules through PinotRuleSet via a new Config#getRuleSet() @Value.Default. The per-query sortExchangeCopyLimit override stays inside getTraitProgram (per-query copy of POST_LOGICAL with all PinotSortExchangeCopyRule instances replaced by the override).

Per-query usePlannerRules / skipPlannerRules filtering is unchanged (still applied on top of the rule lists in getOptProgram).

Introduces a ServiceLoader-discovered SPI that lets plugins add, remove,
reorder, or replace the Calcite HEP rules used by the multi-stage query
engine. The OSS defaults are themselves contributed by
DefaultRuleSetCustomizer (registered via META-INF/services), and plugin
customizers run on top.

- New `Phase` enum covers every HEP phase QueryEnvironment runs:
  BASIC, FILTER_PUSHDOWN, PROJECT_PUSHDOWN, PRUNE, POST_LOGICAL,
  POST_LOGICAL_V2, POST_LOGICAL_ENRICHED_JOIN. Append-only contract.
- New `RuleSetCustomizer` interface — plugins implement
  `customize(Phase, List<RelOptRule>)` and modify the per-phase list
  in place.
- New `PinotRuleSet` owns the per-phase, immutable rule lists and is
  the single source of truth read by `QueryEnvironment`. The default
  instance is built lazily from `ServiceLoader` discovery.
- `DefaultRuleSetCustomizer` replaces the static `PinotQueryRuleSets`
  lists; the deleted class is the rename source.
- `QueryEnvironment` reads every phase's rules through `PinotRuleSet`
  via a new `Config#getRuleSet()` `@Value.Default`. The per-query
  `sortExchangeCopyLimit` override stays inside `getTraitProgram`
  (per-query copy of POST_LOGICAL with all `PinotSortExchangeCopyRule`
  instances replaced by the override).

Per-query `usePlannerRules` / `skipPlannerRules` filtering is unchanged
(still applied on top of the rule lists in `getOptProgram`).
@gortiz gortiz added extension-point Adds or modifies an extension/SPI point multi-stage Related to the multi-stage query engine labels Apr 30, 2026
Copy link
Copy Markdown
Contributor

@xiangfu0 xiangfu0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a few high-signal SPI/plugin issues; see inline comments.

import org.apache.calcite.plan.RelOptRule;


/// Plugin SPI for customizing the multi-stage planner's Calcite rule sets.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is documented as a plugin SPI, but Pinot plugin realms only import org.apache.pinot.spi by default. A standard plugin packaged with pinot-plugin.properties cannot even link org.apache.pinot.query.planner.rules.RuleSetCustomizer without extra realm-import wiring, so the advertised extension point is broken for normal plugins. Please move this SPI into pinot-spi or import this package by default before landing.

/// Discovers every [RuleSetCustomizer] via [ServiceLoader] and builds a
/// rule set from them. Used by [#defaultInstance()] and by callers that
/// don't have an externally-managed customizer list.
public static PinotRuleSet loadFromServiceLoader() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if a plugin adds the extra class-realm import, this discovery path only does ServiceLoader.load(RuleSetCustomizer.class). Broker startup does not enumerate Pinot plugin classloaders here, and PluginClassLoader keeps plugin jars isolated, so third-party customizers will never be discovered and only DefaultRuleSetCustomizer will run. Please load providers from PluginManager/plugin realms explicitly instead of relying on the default ServiceLoader lookup.

/// applied later by `QueryEnvironment.getTraitProgram`, which swaps the
/// configured `PinotSortExchangeCopyRule` on a per-query copy of the
/// `POST_LOGICAL` list.
public final class DefaultRuleSetCustomizer implements RuleSetCustomizer {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rename removes the existing public org.apache.pinot.calcite.rel.rules.PinotQueryRuleSets type entirely. Any out-of-tree planner extension or test compiled against current releases will fail to load or compile after upgrade. Please keep a deprecated bridge in the old package instead of replacing the class outright.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extension-point Adds or modifies an extension/SPI point multi-stage Related to the multi-stage query engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants