{"record":{"id":"35d66413542415c9","repo":"risingwavelabs/risingwave","slug":"root-schema-of-debezium-shall-be-a-record-but-got","errorCode":null,"errorMessage":"Root schema of debezium shall be a record but got: {root:?}","messagePattern":"Root schema of debezium shall be a record but got: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/connector/src/parser/debezium/avro_parser.rs","lineNumber":176,"sourceCode":"        // - op\n        // - ts_ms\n        // - transaction\n        // See <https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-events>\n\n        avro_schema_to_fields(\n            // This assumes no external `Ref`s (e.g. \"before\" referring to \"after\" or \"source\").\n            // Internal `Ref`s inside the \"before\" tree are allowed.\n            extract_debezium_table_schema(&self.outer_schema)?,\n            // TODO: do we need to support map type here?\n            None,\n        )\n        .map_err(Into::into)\n    }\n}\n\nfn extract_debezium_table_schema(root: &Schema) -> anyhow::Result<&Schema> {\n    let Schema::Record(root_record) = root else {\n        anyhow::bail!(\"Root schema of debezium shall be a record but got: {root:?}\");\n    };\n    let idx = (root_record.lookup.get(\"before\"))\n        .context(\"Root schema of debezium shall contain \\\"before\\\" field.\")?;\n    let schema = &root_record.fields[*idx].schema;\n    // It is wrapped inside a union to allow null, so we look inside.\n    let Schema::Union(union_schema) = schema else {\n        return Ok(schema);\n    };\n    get_nullable_union_inner(union_schema).context(format!(\n        \"illegal avro union schema, expected [null, T], got {:?}\",\n        union_schema\n    ))\n}\n\n#[cfg(test)]\nmod tests {\n    use std::io::Read;\n    use std::path::PathBuf;","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/parser/debezium/avro_parser.rs#L158-L194","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the source actually emits Debezium-encoded Avro whose root is the Debezium envelope record containing before/after","Check the schema registry subject resolution (subject naming strategy) so the correct envelope schema is fetched","If consuming a plain Avro stream, use the non-Debezium Avro parser instead"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// JS pseudo-guard against a fetched Avro schema\nif (schema.type !== 'record' || !schema.fields.some(f => f.name === 'before')) throw new Error('not a Debezium Avro envelope');","typeGuard":null,"tryCatchPattern":"if err.to_string().contains(\"Root schema of debezium shall be a record\") { verify_schema_registry_subject(); } else { propagate }","preventionTips":["Verify the registry subject returns the Debezium envelope schema, not a value schema","Inspect one message with avro-tools before creating the source","Use the plain Avro parser for non-Debezium streams"],"tags":["debezium","avro","schema","streaming-parser"],"backgroundTag":"schema-validation-failed","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"}