apache/seatunnel · error · SchemaValidationException

UNSUPPORTED_SCHEMA_CHANGE_TYPE

UNSUPPORTED_SCHEMA_CHANGE_TYPE

Error message

PostgreSQL CDC currently supports only ADD COLUMN relation changes. The job stopped before processing rows with the new schema. Restoring the same checkpoint will encounter this relation again until the change is supported or the job is restarted through a controlled schema migration. Cached columns: %s, relation columns: %s

What it means

Postgres CDC's schema-change resolver compares the cached CatalogTable with the live relation and only supports pure ADD COLUMN changes. If the new relation has fewer columns than the cached schema (a drop or rename happened), the job fails fast with UNSUPPORTED_SCHEMA_CHANGE_TYPE. The job stops before processing rows with the new schema so a checkpoint restore does not silently skip or corrupt data.

Source

Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresRelationSchemaChangeResolver.java:134

                .orElseThrow(
                        () ->
                                invalidRelationRecord(
                                        "Cannot find cached schema for PostgreSQL table "
                                                + after.id()));
    }

    public static String relationSchemaName(Table relation) {
        // SeaTunnel's ConnectTableChangeSerializer parses a two-part quoted PostgreSQL identifier
        // into TableId.catalog + TableId.table. Prefer the real schema field when present and fall
        // back to catalog for synthetic relation records after deserialization.
        return relation.id().schema() != null ? relation.id().schema() : relation.id().catalog();
    }

    private List<AlterTableColumnEvent> resolveAddedColumns(CatalogTable before, Table after) {
        List<Column> beforeColumns = before.getTableSchema().getColumns();

        if (after.columns().size() < beforeColumns.size()) {
            throw unsupportedChange(before, after);
        }

        for (int i = 0; i < beforeColumns.size(); i++) {
            Column beforeColumn = beforeColumns.get(i);
            Column afterColumn = convertToSeaTunnelColumn(after, i);
            if (!Objects.equals(beforeColumn.getName(), afterColumn.getName())
                    || !Objects.equals(beforeColumn.getDataType(), afterColumn.getDataType())
                    || beforeColumn.isNullable() != afterColumn.isNullable()) {
                throw unsupportedChange(before, after);
            }
        }

        List<AlterTableColumnEvent> events = new ArrayList<>();
        for (int i = beforeColumns.size(); i < after.columns().size(); i++) {
            Column addedColumn = convertToSeaTunnelColumn(after, i);
            AlterTableAddColumnEvent event;
            if (i == 0) {
                event = AlterTableAddColumnEvent.addFirst(before.getTableId(), addedColumn);

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Undo the schema change on PostgreSQL (re-add the dropped column with the same name/type/nullability) so the relation matches the cached schema, then restore from checkpoint
  2. Perform a controlled migration: stop the job, rebuild/start it fresh against the new schema (state is schema-bound and cannot resume across a drop)
  3. Rename strategy: if a rename was intended, express it as ADD new column + backfill, then drop the old column only after switching the job to the new schema
  4. Upgrade SeaTunnel or file a feature request if DROP/RENAME COLUMN support is needed

Example fix

// before: DROP COLUMN email; on source -> job fails
// after: keep old column, add new one
ALTER TABLE orders ADD COLUMN user_email text;
UPDATE orders SET user_email = email;
-- keep 'email' until the CDC job has been migrated
Defensive patterns

Strategy: validation

Validate before calling

// before submitting the CDC job, ensure only additive changes happened
List<String> before = cachedColumns.stream().map(Column::getName).collect(toList());
List<String> after = currentColumns.stream().map(Column::getName).collect(toList());
if (!after.subList(0, before.size()).equals(before))
    throw new IllegalStateException("Non-additive schema change detected; migrate manually");

Try / catch

try {
    resolver.resolveAddedColumns(before, after);
} catch (SeaTunnelSqlException e) {
    if (String.valueOf(e.getMessage()).contains("UNSUPPORTED_SCHEMA_CHANGE_TYPE")
            || String.valueOf(e.getMessage()).contains("only supports ADD COLUMN")) {
        // stop pipeline, run controlled schema migration, restart without old checkpoint
    }
}

Prevention

When it happens

Trigger: resolveAddedColumns detects after.columns().size() < beforeColumns.size() while comparing the cached CatalogTable against the live PostgreSQL table during change-event handling (events -> resolveAddedColumns).

Common situations: A DBA dropped a column on the source table while the CDC job was running; a column was renamed (seen as drop+add); a table rebuild produced a narrower schema; migration tooling reverted a previous ADD COLUMN.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/88241c5d576e6498. Report an issue: GitHub.