{"record":{"id":"09db13299b11d470","repo":"risingwavelabs/risingwave","slug":"streamvectorindexlookupjoin-only-support-append-on","errorCode":null,"errorMessage":"StreamVectorIndexLookupJoin only support append only input","messagePattern":"StreamVectorIndexLookupJoin only support append only input","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/frontend/src/optimizer/plan_node/stream_vector_index_lookup_join.rs","lineNumber":39,"sourceCode":"use crate::optimizer::plan_node::generic::{PhysicalPlanRef, VectorIndexLookupJoin};\nuse crate::optimizer::plan_node::stream::StreamPlanNodeMetadata;\nuse crate::optimizer::plan_node::utils::{Distill, childless_record};\nuse crate::optimizer::plan_node::{\n    ExprRewritable, PlanBase, PlanTreeNodeUnary, Stream, StreamNode, StreamPlanRef,\n};\nuse crate::optimizer::property::StreamKind;\nuse crate::stream_fragmenter::BuildFragmentGraphState;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct StreamVectorIndexLookupJoin {\n    pub base: PlanBase<Stream>,\n    pub core: VectorIndexLookupJoin<StreamPlanRef>,\n}\n\nimpl StreamVectorIndexLookupJoin {\n    pub fn new(core: VectorIndexLookupJoin<StreamPlanRef>) -> crate::error::Result<Self> {\n        if core.input.stream_kind() != StreamKind::AppendOnly {\n            bail!(\"StreamVectorIndexLookupJoin only support append only input\")\n        }\n        Ok(Self::with_core(core))\n    }\n\n    fn with_core(core: VectorIndexLookupJoin<StreamPlanRef>) -> Self {\n        assert_eq!(core.input.stream_kind(), StreamKind::AppendOnly);\n        let base = PlanBase::new_stream_with_core(\n            &core,\n            core.input.distribution().clone(),\n            core.input.stream_kind(),\n            core.input.emit_on_window_close(),\n            core.input.watermark_columns().clone(),\n            core.input.columns_monotonicity().clone(),\n        );\n        Self { base, core }\n    }\n}\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/optimizer/plan_node/stream_vector_index_lookup_join.rs#L21-L57","documentation":"StreamVectorIndexLookupJoin::new validates that the input of a stream vector index lookup join is append-only. RisingWave stream fragments must know the input never emits UPDATE/DELETE retraction records for this node; if the input's StreamKind is not AppendOnly, the constructor bails with this error instead of producing an invalid plan.","triggerScenarios":"Calling StreamVectorIndexLookupJoin::new with a core whose core.input.stream_kind() != StreamKind::AppendOnly, i.e. planning a vector index lookup join over a stream input that can produce retractions (e.g. an MV with upserts/deletes or a non-append-only source).","commonSituations":"Building queries that use vector index time travel/lookup joins on top of mutable tables or sources with updates; users attempt AS OF joins over frequently updated MVs where only append-only streams are supported.","solutions":["Rewrite the query so the join input is append-only (e.g. query an append-only source or MV that never deletes/updates rows).","If input is an MV receiving updates, materialize an append-only derived table (e.g. filter/aggregation that is append-only) and join against that.","Check the input's StreamKind before constructing the node and surface a user-friendly error suggesting append-only input."],"exampleFix":"// before\nlet node = StreamVectorIndexLookupJoin::new(core)?; // bails if input not append-only\n// after\nassert!(core.input.stream_kind() == StreamKind::AppendOnly, \"vector index lookup join requires append-only input\");\nlet node = StreamVectorIndexLookupJoin::new(core)?;","handlingStrategy":"validation","validationCode":"if core.input.stream_kind() != StreamKind::AppendOnly {\n    return Err(anyhow!(\"vector index lookup join requires an append-only input\"));\n}\nlet node = StreamVectorIndexLookupJoin::new(core)?;","typeGuard":"fn is_append_only(plan: &StreamPlanRef) -> bool { plan.stream_kind() == StreamKind::AppendOnly }","tryCatchPattern":null,"preventionTips":["Check StreamKind before planning vector index lookup joins.","Keep inputs to such joins append-only sources or append-only MVs.","Document the append-only requirement near the query pattern in your app code."],"tags":["streaming","plan-validation","append-only"],"backgroundTag":"invalid-argument-value","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}