apache/seatunnel · error · IllegalStateException

Cannot deserialize TiDB DELETE CDC row because both value…

Error message

Cannot deserialize TiDB DELETE CDC row because both value and oldValue are empty, and table ${tableInfo.getName()}(${tableInfo.getId()}) is not a pk-handle table. key=${row.getKey()}, startTs=${row.getStartTs()}, commitTs=${row.getCommitTs()}

What it means

Thrown by decodeDeleteValuesFromHandle when a DELETE CDC event has neither value nor oldValue bytes, and the table is not a pk-handle table, so the deleted row's values cannot be reconstructed from the row handle. Without value bytes or a handle-capable primary key, the connector cannot produce a meaningful delete record.

Solutions

  1. Enable old-value capture in TiCDC (changefeed config enable-old-value=true) so DELETE events carry the row bytes
  2. If old values cannot be enabled, use a table with an integer handle primary key (INT/BIGINT single-column PK)
  3. Check TiKV client region/key decode: verify key and startTs/commitTs in the message to trace the event
  4. Upgrade TiDB/TiCDC — newer versions populate old values for delete events

Example fix

// before: TiCDC changefeed without old values
cdc cli changefeed create --sink-uri=... --config=...
// after
// tiCDC changefeed config: {"enable-old-value": true}
cdc cli changefeed update --changefeed-id=... --config=cf.toml
Defensive patterns

Strategy: validation

Validate before calling

// Verify TiCDC changefeed has old-value enabled before running CDC jobs
cdc cli changefeed query --changefeed-id=<id> | jq '.config.enable_old_value'
// must be true for tables without integer handle PKs

Type guard

boolean canDecodeDelete(Cdcpb.Event.Row row, TiTableInfo tableInfo) {
    boolean hasValue = row.getValue() != null && !row.getValue().isEmpty();
    boolean hasOld = row.getOldValue() != null && !row.getOldValue().isEmpty();
    return hasValue || hasOld || (tableInfo.isPkHandle() && tableInfo.getPKIsHandleColumn() != null);
}

Try / catch

try {
    deserializer.deserialize(row, output);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("not a pk-handle table")) {
        LOG.error("Enable TiCDC enable-old-value for table {}", tableInfo.getName());
    }
    throw e;
}

Prevention

When it happens

Trigger: A DELETE event arrives with empty value and empty oldValue while tableInfo.isPkHandle() is false (or getPKIsHandleColumn() is null) — i.e. the table's primary key is not an integer handle that can be decoded into column values.

Common situations: Tables with non-integer or composite primary keys receiving delete events where TiCDC/TiKV omits the old value (old-value capture disabled in TiCDC configuration); deletes on tables without handle-type PKs.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-tidb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/tidb/source/deserializer/SeaTunnelRowStreamingRecordDeserializer.java:104

    }

    private Object[] decodeDeleteValues(Cdcpb.Event.Row row, long handle) {
        ByteString oldValue = row.getOldValue();
        if (oldValue != null && !oldValue.isEmpty()) {
            return decodeObjects(oldValue.toByteArray(), handle, tableInfo);
        }
        ByteString value = row.getValue();
        if (value != null && !value.isEmpty()) {
            // Prefer an available row image before falling back to a PK-only delete row.
            return decodeObjects(value.toByteArray(), handle, tableInfo);
        }
        return decodeDeleteValuesFromHandle(row, handle);
    }

    private Object[] decodeDeleteValuesFromHandle(Cdcpb.Event.Row row, long handle) {
        TiColumnInfo handleColumn = tableInfo.getPKIsHandleColumn();
        if (!tableInfo.isPkHandle() || handleColumn == null) {
            throw new IllegalStateException(
                    String.format(
                            "Cannot deserialize TiDB DELETE CDC row because both value and"
                                    + " oldValue are empty, and table %s(%s) is not a pk-handle"
                                    + " table. key=%s, startTs=%s, commitTs=%s",
                            tableInfo.getName(),
                            tableInfo.getId(),
                            row.getKey(),
                            row.getStartTs(),
                            row.getCommitTs()));
        }
        Object[] values = new Object[tableInfo.getColumns().size()];
        values[handleColumn.getOffset()] = handle;
        return values;
    }
}

View on GitHub (pinned to cf67b549a7)