# Opchain Converter Plugin

## Overview

The `OpChainConverter` SPI (Service Provider Interface) allows plugin developers to provide alternative implementations for converting logical query plans into executable OpChain objects in the multi-stage query engine. This enables extensibility for alternative execution backends, such as integrating specialized execution engines (e.g., timeseries engines) with Pinot's multi-stage execution (MSE).

## Why Use OpChain Converter SPI?

The OpChainConverter SPI is useful when you need to:

* Implement alternative plan-to-OpChain conversion logic for specific execution scenarios
* Integrate custom execution backends with the multi-stage engine
* Optimize query execution for specialized workloads or hardware
* Support alternative query execution strategies

## Implementing an OpChain Converter

To create a custom OpChain converter, implement the `OpChainConverter` interface:

```java
package org.apache.pinot.query.runtime.operator;

public interface OpChainConverter {
  /**
   * Converts a logical plan node into an OpChain for execution.
   *
   * @param context the plan request context containing query metadata
   * @param planNode the logical query plan node to convert
   * @return an OpChain ready for execution, or null if this converter cannot handle this plan
   */
  OpChain convert(PlanRequestContext context, PlanNode planNode);

  /**
   * Returns the priority of this converter. Higher priority converters are selected first.
   * When multiple converters have the same priority, they are ordered by converter ID.
   *
   * @return the priority value (higher = higher priority)
   */
  int priority();

  /**
   * Returns a unique identifier for this converter.
   *
   * @return the converter ID
   */
  String getId();
}
```

## Registering Your OpChain Converter

Once you've implemented the `OpChainConverter` interface, register it using Java's ServiceLoader mechanism:

1. Add the `@AutoService` annotation from Google's `com.google.auto:auto-service` library:

```java
import com.google.auto.service.AutoService;
import org.apache.pinot.query.runtime.operator.OpChainConverter;

@AutoService(OpChainConverter.class)
public class CustomOpChainConverter implements OpChainConverter {

  @Override
  public OpChain convert(PlanRequestContext context, PlanNode planNode) {
    // Implement your conversion logic here
    // Return null if this converter cannot handle the plan
    if (!canHandle(planNode)) {
      return null;
    }
    return convertToOpChain(context, planNode);
  }

  @Override
  public int priority() {
    return 100; // Set your converter's priority
  }

  @Override
  public String getId() {
    return "custom-converter";
  }

  private boolean canHandle(PlanNode planNode) {
    // Implement logic to determine if this converter can handle the plan
    return true;
  }

  private OpChain convertToOpChain(PlanRequestContext context, PlanNode planNode) {
    // Implement your conversion logic
    return null;
  }
}
```

2. Create a ServiceLoader configuration file at `META-INF/services/org.apache.pinot.query.runtime.operator.OpChainConverter` in your JAR:

```
com.example.CustomOpChainConverter
```

## Priority and Selection

The `OpChainConverterDispatcher` manages all registered converters and selects the active one based on:

1. **Priority**: Higher priority values are evaluated first
2. **Tie-breaking**: When multiple converters have the same priority, they are ordered by converter ID (alphabetically)
3. **Explicit override**: You can explicitly set the active converter using `OpChainConverterDispatcher.setActiveConverterIdOverride(String converterId)`

## Example: Custom Converter Implementation

Here's a complete example of a custom OpChain converter that handles specific plan node types:

```java
import com.google.auto.service.AutoService;
import org.apache.pinot.query.planner.PlanNode;
import org.apache.pinot.query.runtime.operator.OpChain;
import org.apache.pinot.query.runtime.operator.OpChainConverter;
import org.apache.pinot.query.runtime.plan.PlanRequestContext;

@AutoService(OpChainConverter.class)
public class TimeSeriesOpChainConverter implements OpChainConverter {
  private static final String CONVERTER_ID = "timeseries-converter";
  private static final int PRIORITY = 50;

  @Override
  public OpChain convert(PlanRequestContext context, PlanNode planNode) {
    // Check if this is a time-series query that we can optimize
    if (!isTimeSeriesQuery(planNode)) {
      return null;
    }

    // Perform custom conversion optimized for time-series execution
    return optimizeTimeSeriesExecution(context, planNode);
  }

  @Override
  public int priority() {
    return PRIORITY;
  }

  @Override
  public String getId() {
    return CONVERTER_ID;
  }

  private boolean isTimeSeriesQuery(PlanNode planNode) {
    // Implement logic to detect time-series queries
    // For example, check for time-based aggregations, windowing, etc.
    return false;
  }

  private OpChain optimizeTimeSeriesExecution(PlanRequestContext context, PlanNode planNode) {
    // Implement time-series-specific optimization logic
    return null;
  }
}
```

## Default Implementation

Pinot includes a `DefaultOpChainConverter` that delegates to the existing `PlanNodeToOpChain` converter. This converter has the lowest priority and serves as a fallback when no other converter can handle a query plan.

## Testing Your OpChain Converter

When testing your OpChain converter:

1. Ensure it properly implements the `OpChainConverter` interface
2. Test that it correctly handles the types of query plans it's designed for
3. Verify that returning `null` gracefully delegates to the next converter
4. Test priority-based selection when multiple converters are available
5. Verify explicit override functionality works correctly

## Related Documentation

* [Multi-stage query engine overview](/build-with-pinot/querying-and-sql/sse-vs-mse.md)
* [Plugin architecture](/develop-and-contribute/plugin-architecture.md)
* [Using the multi-stage query engine](/build-with-pinot/querying-and-sql/sse-vs-mse.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.pinot.apache.org/develop-and-contribute/plugin-architecture/write-custom-plugins/opchain-converter-plugin.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
