{"record":{"id":"1120de8b557b0929","repo":"apache/seatunnel","slug":"invalid-schema-structure","errorCode":"INVALID_SCHEMA_STRUCTURE","errorMessage":"Failed to resolve PostgreSQL RELATION schema change for ${relationId}. Continuing could make the produced row schema diverge from the source relation.","messagePattern":"Failed to resolve PostgreSQL RELATION schema change for (.+?)\\. Continuing could make the produced row schema diverge from the source relation\\.","errorType":"error_code","errorClass":"SchemaEvolutionException","httpStatus":null,"severity":"critical","filePath":"seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresRelationSchemaChangeResolver.java","lineNumber":76,"sourceCode":"        Table after = null;\n        CatalogTable before = null;\n        try {\n            after = extractTable(record);\n            before = findCatalogTable(after, catalogTables);\n            List<AlterTableColumnEvent> events = resolveAddedColumns(before, after);\n            if (events.isEmpty()) {\n                return null;\n            }\n\n            events.forEach(event -> event.setSourceDialectName(DatabaseIdentifier.POSTGRESQL));\n            AlterTableColumnsEvent result = new AlterTableColumnsEvent(before.getTableId(), events);\n            result.setSourceDialectName(DatabaseIdentifier.POSTGRESQL);\n            return result;\n        } catch (SchemaEvolutionException e) {\n            throw e;\n        } catch (Exception e) {\n            String relationId = after == null ? \"unknown\" : after.id().toString();\n            throw new SchemaEvolutionException(\n                    SchemaEvolutionErrorCode.INVALID_SCHEMA_STRUCTURE,\n                    \"Failed to resolve PostgreSQL RELATION schema change for \"\n                            + relationId\n                            + \". Continuing could make the produced row schema diverge from the source relation.\",\n                    before == null ? null : before.getTableId(),\n                    null,\n                    e);\n        }\n    }\n\n    private Table extractTable(SourceRecord record) {\n        Struct value = (Struct) record.value();\n        List<Struct> changes = value.getArray(HistoryRecord.Fields.TABLE_CHANGES);\n        if (changes == null || changes.isEmpty()) {\n            throw invalidRelationRecord(\"PostgreSQL relation record has no table change payload\");\n        }\n        TableChanges tableChanges = new ConnectTableChangeSerializer().deserialize(changes, true);\n        return StreamSupport.stream(tableChanges.spliterator(), false)","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresRelationSchemaChangeResolver.java#L58-L94","documentation":"PostgresRelationSchemaChangeResolver.resolve wraps any unexpected exception (while converting a Debezium RELATION message into a SeaTunnel catalog table) into a SchemaEvolutionException with code INVALID_SCHEMA_STRUCTURE. It signals that the resolver could not derive the new schema for the relation and that continuing would let the produced rows' schema diverge from the actual PostgreSQL relation.","triggerScenarios":"Any exception during RELATION-event schema resolution — e.g. malformed/unsupported column type in the relation message, null `after` payload, or an internal conversion error; `before == null ? null : before.getTableId()` shows it also handles missing prior table info.","commonSituations":"PostgreSQL DDL introducing types the mapping layer cannot translate; corrupted or partially applied RELATION stream after a slot lsn jump; concurrent DDL during snapshot handoff.","solutions":["Inspect the wrapped cause (`getCause()`) to find the actual conversion failure (usually an unsupported type or null metadata).","Check the table's column types and remove/alter unsupported ones (e.g. custom or unusual types) before the job consumes them.","Restart the pipeline with a fresh replication slot so RELATION messages are re-emitted consistently, or restore from a clean snapshot.","If the failure is persistent for schema-evolution-exempt cases, handle SchemaEvolutionException upstream per your evolution policy."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-check relation payload before resolution\nif (relationChange == null || relationChange.id() == null) {\n    skipEvent();\n}","typeGuard":null,"tryCatchPattern":"try {\n    resolver.resolve(...);\n} catch (SchemaEvolutionException e) {\n    log.error(\"RELATION resolution failed for relation; cause=\", e.getCause());\n    throw e; // do not continue with diverging schema\n}","preventionTips":["Avoid capturing tables with exotic/custom PostgreSQL column types","Use a fresh replication slot after WAL retention issues","Keep PostgreSQL DDL windows outside job runtime where possible","Enable checkpointing so schema state is consistent after restarts"],"tags":["postgresql","cdc","schema-evolution","schema-resolution"],"backgroundTag":"schema-validation-failed","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}