{"record":{"id":"0c4adb75895d1708","repo":"risingwavelabs/risingwave","slug":"logicalicebergintermediatescan-is-only-for-batch-q","errorCode":null,"errorMessage":"LogicalIcebergIntermediateScan is only for batch queries","messagePattern":"LogicalIcebergIntermediateScan is only for batch queries","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/frontend/src/optimizer/plan_node/logical_iceberg_intermediate_scan.rs","lineNumber":313,"sourceCode":"}\n\nimpl ToBatch for LogicalIcebergIntermediateScan {\n    fn to_batch(&self) -> Result<crate::optimizer::plan_node::BatchPlanRef> {\n        // This should not be called directly. The intermediate scan should be\n        // converted to LogicalIcebergScan first via the materialization rule.\n        Err(crate::error::ErrorCode::InternalError(\n            \"LogicalIcebergIntermediateScan should be converted to LogicalIcebergScan before to_batch\".to_owned()\n        )\n        .into())\n    }\n}\n\nimpl ToStream for LogicalIcebergIntermediateScan {\n    fn to_stream(\n        &self,\n        _ctx: &mut ToStreamContext,\n    ) -> Result<crate::optimizer::plan_node::StreamPlanRef> {\n        unreachable!(\"LogicalIcebergIntermediateScan is only for batch queries\")\n    }\n\n    fn logical_rewrite_for_stream(\n        &self,\n        _ctx: &mut RewriteStreamContext,\n    ) -> Result<(PlanRef, ColIndexMapping)> {\n        unreachable!(\"LogicalIcebergIntermediateScan is only for batch queries\")\n    }\n}\n","sourceCodeStart":295,"sourceCodeEnd":323,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/optimizer/plan_node/logical_iceberg_intermediate_scan.rs#L295-L323","documentation":"LogicalIcebergIntermediateScan is designed exclusively for batch query plans (Iceberg query dispatch), so its `ToStream` implementation calls `unreachable!()`. Reaching it means the stream optimizer was invoked over a plan that should never have entered streaming planning — an internal invariant violation rather than a user-facing error.","triggerScenarios":"Calling `to_stream` on a LogicalIcebergIntermediateScan, i.e. running stream planning (materialized view creation, streaming query) over a plan containing an Iceberg intermediate scan node.","commonSituations":"Optimizer bugs where an Iceberg batch-plan node leaks into the streaming pipeline; tests invoking ToStream generically over all logical plan nodes.","solutions":["Ensure Iceberg intermediate scans are only produced within batch planning paths and never in stream planning.","Change `unreachable!()` to a `bail!` with a descriptive error for more graceful handling.","Reproduce with EXPLAIN and the query, and file a bug including the plan dump."],"exampleFix":"// before\nunreachable!(\"LogicalIcebergIntermediateScan is only for batch queries\")\n\n// after\nbail!(\"LogicalIcebergIntermediateScan is only for batch queries\")","handlingStrategy":"try-catch","validationCode":"if streaming && plan.contains(\"LogicalIcebergIntermediateScan\") {\n    return Err(\"iceberg intermediate scan is batch-only\".into());\n}","typeGuard":"fn batch_only_node(plan: &LogicalPlan) -> bool {\n    matches!(plan.node_type(), PlanNodeType::LogicalIcebergIntermediateScan)\n}","tryCatchPattern":"match to_stream(plan) {\n    Err(e) if e.to_string().contains(\"only for batch queries\") =>\n        route_to_batch_engine(query),\n    r => r,\n}","preventionTips":["Keep Iceberg intermediate scan creation inside batch planning only","Assert plan engine context before running ToStream","Replace unreachable! with bail! for graceful diagnostics","Include EXPLAIN dumps when reporting such optimizer leaks"],"tags":["rust","iceberg","streaming","unreachable"],"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"}