{"record":{"id":"5ac193f5cead8adb","repo":"risingwavelabs/risingwave","slug":"circular-reference-detected-in-avro-schema","errorCode":null,"errorMessage":"circular reference detected in Avro schema: {} -> {}","messagePattern":"circular reference detected in Avro schema: (.+?) -> (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/connector/codec/src/decoder/avro/schema.rs","lineNumber":148,"sourceCode":"        Schema::LocalTimestampMillis => DataType::Timestamp,\n        Schema::LocalTimestampMicros => DataType::Timestamp,\n        Schema::TimestampMillis => DataType::Timestamptz,\n        Schema::TimestampMicros => DataType::Timestamptz,\n        Schema::Duration => DataType::Interval,\n        Schema::Bytes => DataType::Bytea,\n        Schema::Enum { .. } => DataType::Varchar,\n        Schema::TimeMillis => DataType::Time,\n        Schema::TimeMicros => DataType::Time,\n        Schema::Record(RecordSchema { fields, name, .. }) => {\n            if name.name == DBZ_VARIABLE_SCALE_DECIMAL_NAME\n                && name.namespace == Some(DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE.into())\n            {\n                return Ok(DataType::Decimal);\n            }\n\n            let unique_name = name.fullname(None);\n            if ancestor_records.contains(&unique_name) {\n                bail!(\n                    \"circular reference detected in Avro schema: {} -> {}\",\n                    ancestor_records.join(\" -> \"),\n                    unique_name\n                );\n            }\n\n            ancestor_records.push(unique_name);\n            let ty = StructType::new(\n                fields\n                    .iter()\n                    .map(|f| {\n                        Ok((\n                            &f.name,\n                            avro_type_mapping(&f.schema, ancestor_records, refs, map_handling)?,\n                        ))\n                    })\n                    .collect::<anyhow::Result<Vec<_>>>()?,\n            )","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/codec/src/decoder/avro/schema.rs#L130-L166","documentation":"avro_type_mapping tracks visited record full names in ancestor_records while recursively resolving Avro types. If a record's unique name is already in the ancestor chain, recursion would loop forever, so the function bails with this message naming the chain and the re-entered record.","triggerScenarios":"Calling avro_schema_to_fields on an Avro schema in which a record references itself (directly or via a chain of nested records) without an eligible union/null indirection that the resolver treats as breaking the cycle.","commonSituations":"Recursive Avro schemas produced by generators (e.g. a linked-list or tree type); hand-edited schemas with self references; schema registry evolution accidentally introducing recursion.","solutions":["Restructure the schema to remove the recursive record reference (flatten or inline one level).","Break the cycle by making the back-reference part of a union with \"null\" if the decoder supports optional recursion.","Pre-generate the recursive types or use a schema version without recursion for this connector."],"exampleFix":"// before\n{\"type\":\"record\",\"name\":\"Node\",\"fields\":[{\"name\":\"next\",\"type\":\"Node\"}]}\n// after\n{\"type\":\"record\",\"name\":\"Node\",\"fields\":[{\"name\":\"next\",\"type\":[\"null\",\"Node\"],\"default\":null}]}","handlingStrategy":"validation","validationCode":"function hasDirectSelfRef(s, seen = new Set()) {\n  const name = s?.name;\n  if (name && seen.has(name)) return true;\n  if (name) seen.add(name);\n  for (const f of s?.fields ?? []) {\n    const t = f.type;\n    const inner = Array.isArray(t) ? t.find(x => typeof x === 'object') : t;\n    if (inner && hasDirectSelfRef(inner, seen)) return true;\n  }\n  seen.delete(name);\n  return false;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Detect recursive records in schemas during schema review/registration.","Prefer flattening one level of recursion for streaming decode pipelines.","If recursion is required, use a null-union back-reference and verify decoder support first."],"tags":["avro","recursion","schema"],"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"}