risingwavelabs/risingwave · error · MetaError

expected exactly one mview fragment for table

Error message

expected exactly one mview fragment for table {}, found {}

What it means

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 {}".

Solutions

  1. Verify the table_id actually corresponds to a streaming/materialized table; skip non-streaming tables before calling this helper.
  2. Recreate the table or MV if its fragment rows are missing after a failed creation; check catalog tables for duplicates and clean them.
  3. Retry after in-flight DDL completes if the read raced with table creation.
  4. If duplicates persist post-upgrade, run the catalog repair/migration tooling for fragment metadata.
Defensive patterns

Strategy: validation

Validate before calling

// Only call for streaming-backed tables
if !is_streaming_table(table_id) { return Ok(()); } // skip plain tables
let n = count_mview_fragments(table_id).await?;
assert_eq!(n, 1, "table {} has {} mview fragments", table_id, n);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/01d1b1e541a1fd2c. Report an issue: GitHub.

Appendix: source

Thrown at src/meta/src/controller/utils.rs:2418

pub async fn has_table_been_migrated<C>(txn: &C, table_id: TableId) -> MetaResult<bool>
where
    C: ConnectionTrait,
{
    let mview_fragment: Vec<i32> = Fragment::find()
        .select_only()
        .column(fragment::Column::FragmentTypeMask)
        .filter(
            fragment::Column::JobId
                .eq(table_id)
                .and(FragmentTypeMask::intersects(FragmentTypeFlag::Mview)),
        )
        .into_tuple()
        .all(txn)
        .await?;

    let mview_fragment_len = mview_fragment.len();
    if mview_fragment_len != 1 {
        bail!(
            "expected exactly one mview fragment for table {}, found {}",
            table_id,
            mview_fragment_len
        );
    }

    let mview_fragment = mview_fragment.into_iter().next().unwrap();
    let migrated =
        FragmentTypeMask::from(mview_fragment).contains(FragmentTypeFlag::UpstreamSinkUnion);

    Ok(migrated)
}

pub async fn check_if_belongs_to_iceberg_table<C>(txn: &C, job_id: JobId) -> MetaResult<bool>
where
    C: ConnectionTrait,
{
    if let Some(engine) = Table::find_by_id(job_id.as_mv_table_id())

View on GitHub (pinned to 6469eb736d)