{"record":{"id":"9049ffd1497c84c7","repo":"risingwavelabs/risingwave","slug":"source-should-not-have-input-executor","errorCode":null,"errorMessage":"Source should not have input executor!","messagePattern":"Source should not have input executor!","errorType":"validation","errorClass":"BatchError","httpStatus":null,"severity":"error","filePath":"src/batch/executors/src/executor/source.rs","lineNumber":58,"sourceCode":"\n    // used to create reader\n    column_ids: Vec<ColumnId>,\n    metrics: Arc<SourceMetrics>,\n    source_id: SourceId,\n    split_list: Vec<SplitImpl>,\n\n    schema: Schema,\n    identity: String,\n\n    chunk_size: usize,\n}\n\nimpl BoxedExecutorBuilder for SourceExecutor {\n    async fn new_boxed_executor(\n        source: &ExecutorBuilder<'_>,\n        inputs: Vec<BoxedExecutor>,\n    ) -> Result<BoxedExecutor> {\n        ensure!(inputs.is_empty(), \"Source should not have input executor!\");\n        let source_node = try_match_expand!(\n            source.plan_node().get_node_body().unwrap(),\n            NodeBody::Source\n        )?;\n\n        // prepare connector source\n        let options_with_secret = WithOptionsSecResolved::new(\n            source_node.with_properties.clone(),\n            source_node.secret_refs.clone(),\n        );\n        let config = ConnectorProperties::extract(options_with_secret.clone(), false)\n            .map_err(BatchError::connector)?;\n\n        let info = source_node.get_info().unwrap();\n        let parser_config = SpecificParserConfig::new(info, &options_with_secret)?;\n\n        let columns: Vec<_> = source_node\n            .columns","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/batch/executors/src/executor/source.rs#L40-L76","documentation":"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.","triggerScenarios":"new_boxed_executor is called with a non-empty `inputs` vec because a Source plan node in the batch plan has children attached.","commonSituations":"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.","solutions":["Verify the plan tree: the Source node must be a leaf; remove any child edges produced by the planner.","Fix the frontend/batch planner code so Source nodes are built with an empty children list.","When constructing SourceExecutor in tests, pass an empty inputs vector to the builder.","File an internal bug with the failing query if the planner output seems correct."],"exampleFix":"// before\nlet node = BatchPlanNode::new(NodeBody::Source(source_node), vec![child]);\n// after\nlet node = BatchPlanNode::new(NodeBody::Source(source_node), vec![]);","handlingStrategy":"try-catch","validationCode":"// Rust: validate before building the source executor\nassert!(inputs.is_empty(), \"Source node must have no children\");","typeGuard":"fn has_no_children(inputs: &[BoxedExecutor]) -> bool { inputs.is_empty() }","tryCatchPattern":"if let Err(e) = builder.new_boxed_executor(&src, inputs).await {\n    if e.to_string().contains(\"Source should not have input executor\") {\n        // dump plan tree for debugging\n    }\n}","preventionTips":["Ensure Source plan nodes are always constructed as leaves.","Test frontend-to-batch plan translation for source nodes.","Never wire child executors under connector source scans."],"tags":["batch-executor","source-connector","planner","rust","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}