risingwavelabs/risingwave · error · BatchError

Source should not have input executor!

Error message

Source should not have input executor!

What it means

SourceExecutor's BoxedExecutorBuilder requires the Source executor to be a leaf with zero child executors. A source executor reads rows from an external connector (Kafka, etc.) or from a table, so it must never receive child executors from the plan. Receiving one indicates the plan tree was built incorrectly.

Source

Thrown at src/batch/executors/src/executor/source.rs:58

    // used to create reader
    column_ids: Vec<ColumnId>,
    metrics: Arc<SourceMetrics>,
    source_id: SourceId,
    split_list: Vec<SplitImpl>,

    schema: Schema,
    identity: String,

    chunk_size: usize,
}

impl BoxedExecutorBuilder for SourceExecutor {
    async fn new_boxed_executor(
        source: &ExecutorBuilder<'_>,
        inputs: Vec<BoxedExecutor>,
    ) -> Result<BoxedExecutor> {
        ensure!(inputs.is_empty(), "Source should not have input executor!");
        let source_node = try_match_expand!(
            source.plan_node().get_node_body().unwrap(),
            NodeBody::Source
        )?;

        // prepare connector source
        let options_with_secret = WithOptionsSecResolved::new(
            source_node.with_properties.clone(),
            source_node.secret_refs.clone(),
        );
        let config = ConnectorProperties::extract(options_with_secret.clone(), false)
            .map_err(BatchError::connector)?;

        let info = source_node.get_info().unwrap();
        let parser_config = SpecificParserConfig::new(info, &options_with_secret)?;

        let columns: Vec<_> = source_node
            .columns

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Verify the plan tree: the Source node must be a leaf; remove any child edges produced by the planner.
  2. Fix the frontend/batch planner code so Source nodes are built with an empty children list.
  3. When constructing SourceExecutor in tests, pass an empty inputs vector to the builder.
  4. File an internal bug with the failing query if the planner output seems correct.

Example fix

// before
let node = BatchPlanNode::new(NodeBody::Source(source_node), vec![child]);
// after
let node = BatchPlanNode::new(NodeBody::Source(source_node), vec![]);
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: validate before building the source executor
assert!(inputs.is_empty(), "Source node must have no children");

Type guard

fn has_no_children(inputs: &[BoxedExecutor]) -> bool { inputs.is_empty() }

Try / catch

if let Err(e) = builder.new_boxed_executor(&src, inputs).await {
    if e.to_string().contains("Source should not have input executor") {
        // dump plan tree for debugging
    }
}

Prevention

When it happens

Trigger: new_boxed_executor is called with a non-empty `inputs` vec because a Source plan node in the batch plan has children attached.

Common situations: Planner bugs that attach a child under a Source/scan node; hand-built plan trees in tests or internal tooling that wire executors under a source; refactors of plan-node arity rules.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/9049ffd1497c84c7. Report an issue: GitHub.