{"record":{"id":"01d1b1e541a1fd2c","repo":"risingwavelabs/risingwave","slug":"expected-exactly-one-mview-fragment-for-table","errorCode":null,"errorMessage":"expected exactly one mview fragment for table {}, found {}","messagePattern":"expected exactly one mview fragment for table (.+?), found (.+?)","errorType":"exception","errorClass":"MetaError","httpStatus":null,"severity":"error","filePath":"src/meta/src/controller/utils.rs","lineNumber":2418,"sourceCode":"pub async fn has_table_been_migrated<C>(txn: &C, table_id: TableId) -> MetaResult<bool>\nwhere\n    C: ConnectionTrait,\n{\n    let mview_fragment: Vec<i32> = Fragment::find()\n        .select_only()\n        .column(fragment::Column::FragmentTypeMask)\n        .filter(\n            fragment::Column::JobId\n                .eq(table_id)\n                .and(FragmentTypeMask::intersects(FragmentTypeFlag::Mview)),\n        )\n        .into_tuple()\n        .all(txn)\n        .await?;\n\n    let mview_fragment_len = mview_fragment.len();\n    if mview_fragment_len != 1 {\n        bail!(\n            \"expected exactly one mview fragment for table {}, found {}\",\n            table_id,\n            mview_fragment_len\n        );\n    }\n\n    let mview_fragment = mview_fragment.into_iter().next().unwrap();\n    let migrated =\n        FragmentTypeMask::from(mview_fragment).contains(FragmentTypeFlag::UpstreamSinkUnion);\n\n    Ok(migrated)\n}\n\npub async fn check_if_belongs_to_iceberg_table<C>(txn: &C, job_id: JobId) -> MetaResult<bool>\nwhere\n    C: ConnectionTrait,\n{\n    if let Some(engine) = Table::find_by_id(job_id.as_mv_table_id())","sourceCodeStart":2400,"sourceCodeEnd":2436,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/controller/utils.rs#L2400-L2436","documentation":"The helper expects a materialized-view (mview) fragment to exist for the given table — exactly one — because streaming tables are backed by a single mview fragment. If zero or multiple fragments are found, it bails with \"expected exactly one mview fragment for table {}, found {}\".","triggerScenarios":"Querying fragments for `table_id` when the table has no mview fragment (plain table without streaming topology, or catalog rows missing after failed creation), or when duplicates exist after a partial create/recovery.","commonSituations":"Corrupted or partially migrated catalog after crashes or upgrades; calling the helper on a non-streaming table by mistake; concurrent DDL observed mid-materialization by internal tooling.","solutions":["Verify the table_id actually corresponds to a streaming/materialized table; skip non-streaming tables before calling this helper.","Recreate the table or MV if its fragment rows are missing after a failed creation; check catalog tables for duplicates and clean them.","Retry after in-flight DDL completes if the read raced with table creation.","If duplicates persist post-upgrade, run the catalog repair/migration tooling for fragment metadata."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Only call for streaming-backed tables\nif !is_streaming_table(table_id) { return Ok(()); } // skip plain tables\nlet n = count_mview_fragments(table_id).await?;\nassert_eq!(n, 1, \"table {} has {} mview fragments\", table_id, n);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check that the table is a materialized/streaming table before expecting an mview fragment","Recreate tables whose creation may have failed midway, leaving fragment rows missing/duplicated","Run catalog consistency checks after crash recovery or upgrades"],"tags":["meta","fragments","table","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-23T08:17:48.524Z"}