{"record":{"id":"80f916010ca7159e","repo":"risingwavelabs/risingwave","slug":"mergesortexchangeexecutor-should-not-have-child","errorCode":null,"errorMessage":"MergeSortExchangeExecutor should not have child!","messagePattern":"MergeSortExchangeExecutor should not have child!","errorType":"validation","errorClass":"BatchError","httpStatus":null,"severity":"error","filePath":"src/batch/executors/src/executor/merge_sort_exchange.rs","lineNumber":131,"sourceCode":"            self.chunk_size,\n            self.mem_ctx,\n        ));\n\n        #[for_await]\n        for chunk in merge_sort_executor.execute() {\n            yield chunk?;\n        }\n    }\n}\n\npub struct MergeSortExchangeExecutorBuilder {}\n\nimpl BoxedExecutorBuilder for MergeSortExchangeExecutorBuilder {\n    async fn new_boxed_executor(\n        source: &ExecutorBuilder<'_>,\n        inputs: Vec<BoxedExecutor>,\n    ) -> Result<BoxedExecutor> {\n        ensure!(\n            inputs.is_empty(),\n            \"MergeSortExchangeExecutor should not have child!\"\n        );\n        let sort_merge_node = try_match_expand!(\n            source.plan_node().get_node_body().unwrap(),\n            NodeBody::MergeSortExchange\n        )?;\n\n        let column_orders = sort_merge_node\n            .column_orders\n            .iter()\n            .map(ColumnOrder::from_protobuf)\n            .collect();\n        let column_orders = Arc::new(column_orders);\n\n        let exchange_node = sort_merge_node.get_exchange()?;\n        let proto_sources: Vec<PbExchangeSource> = exchange_node.get_sources().clone();\n        let source_creators =","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/batch/executors/src/executor/merge_sort_exchange.rs#L113-L149","documentation":"`MergeSortExchangeExecutorBuilder::new_boxed_executor` requires that a MergeSortExchange plan node has no child executors; the merge-sort exchange pulls its inputs from remote exchange sources rather than a local child. A non-empty `inputs` vector means the executor tree is structurally invalid, so plan construction fails with this message (which says \"should not have child\").","triggerScenarios":"Building a batch executor tree where a `MergeSortExchange` NodeBody has a child attached in the plan fragment — i.e. malformed plan distribution where the node should sit above a MergeSortExchangeReceiver but was given local input.","commonSituations":"Version skew or bugs in meta's plan distribution placing the node incorrectly; hand-crafted plan fragments in tests; corrupted exchange wiring after scheduler changes.","solutions":["Verify the distributed plan: MergeSortExchange must have no local child; its receiver should be a separate MergeSortExchangeReceiver node.","Upgrade meta and compute nodes to matching versions to rule out plan-format skew.","If reproducible from a normal query, capture the fragment and file a RisingWave issue with the EXPLAIN output."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// validate plan shape before dispatching the fragment\nfn assert_exchange_shape(node: &PlanNode) -> Result<()> {\n    if matches!(node.node_body, Some(NodeBody::MergeSortExchange(_))) {\n        ensure!(node.children.is_empty(), \"MergeSortExchange must not have a local child\");\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat distributed plan fragments as meta-owned; never hand-assemble exchange trees.","Keep meta and compute versions in sync.","Add fragment-structure assertions in distributed-plan tests."],"tags":["batch-executor","plan-validation","exchange","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"}