risingwavelabs/risingwave · error

Root schema of debezium shall be a record but got: {root:?}

Error message

Root schema of debezium shall be a record but got: {root:?}

What it means

Debezium Avro events wrap the row payload in a `before`/`after` structure. extract_debezium_table_schema requires the root Avro schema to be a Record so it can look up the `before` field; any other root schema kind (union, array, map, primitive) is rejected.

Source

Thrown at src/connector/src/parser/debezium/avro_parser.rs:176

        // - op
        // - ts_ms
        // - transaction
        // See <https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-events>

        avro_schema_to_fields(
            // This assumes no external `Ref`s (e.g. "before" referring to "after" or "source").
            // Internal `Ref`s inside the "before" tree are allowed.
            extract_debezium_table_schema(&self.outer_schema)?,
            // TODO: do we need to support map type here?
            None,
        )
        .map_err(Into::into)
    }
}

fn extract_debezium_table_schema(root: &Schema) -> anyhow::Result<&Schema> {
    let Schema::Record(root_record) = root else {
        anyhow::bail!("Root schema of debezium shall be a record but got: {root:?}");
    };
    let idx = (root_record.lookup.get("before"))
        .context("Root schema of debezium shall contain \"before\" field.")?;
    let schema = &root_record.fields[*idx].schema;
    // It is wrapped inside a union to allow null, so we look inside.
    let Schema::Union(union_schema) = schema else {
        return Ok(schema);
    };
    get_nullable_union_inner(union_schema).context(format!(
        "illegal avro union schema, expected [null, T], got {:?}",
        union_schema
    ))
}

#[cfg(test)]
mod tests {
    use std::io::Read;
    use std::path::PathBuf;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Ensure the source actually emits Debezium-encoded Avro whose root is the Debezium envelope record containing before/after
  2. Check the schema registry subject resolution (subject naming strategy) so the correct envelope schema is fetched
  3. If consuming a plain Avro stream, use the non-Debezium Avro parser instead
Defensive patterns

Strategy: validation

Validate before calling

// JS pseudo-guard against a fetched Avro schema
if (schema.type !== 'record' || !schema.fields.some(f => f.name === 'before')) throw new Error('not a Debezium Avro envelope');

Try / catch

if err.to_string().contains("Root schema of debezium shall be a record") { verify_schema_registry_subject(); } else { propagate }

Prevention

When it happens

Trigger: Feeding a Debezium Avro parser a schema whose top level is not a record — e.g. an Avro union, array, map, enum, or primitive schema.

Common situations: Pointing the Debezium parser at a non-Debezium Avro stream; a misconfigured schema registry subject returning a wrapped/different root schema; hand-rolled Avro producers that don't follow Debezium envelope layout.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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