apache/seatunnel · error · JdbcConnectorException
COMMON_UNSUPPORTED_OPERATION
COMMON_UNSUPPORTED_OPERATION
Error message
unsupported row kind:
What it means
InsertOrUpdateBatchStatementExecutor.existRow decides whether an incoming row requires an INSERT or an UPDATE based on its RowKind. Only INSERT (no existing row) and UPDATE_AFTER (row exists) are supported; any other RowKind (DELETE_BEFORE, DELETE_AFTER, UPDATE_BEFORE) reaches the default branch and throws this error.
Source
Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/InsertOrUpdateBatchStatementExecutor.java:160
}
}
}
private boolean upsertMode() {
return existStmtFactory != null;
}
private boolean existRow(SeaTunnelRow record) throws SQLException {
if (upsertMode()) {
return exist(keyExtractor.apply(record));
}
switch (record.getRowKind()) {
case INSERT:
return false;
case UPDATE_AFTER:
return true;
default:
throw new JdbcConnectorException(
CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION,
"unsupported row kind: " + record.getRowKind());
}
}
private boolean exist(SeaTunnelRow pk) throws SQLException {
rowConverter.toExternal(keyTableSchema, databaseTableSchema, pk, existStatement);
try (ResultSet resultSet = existStatement.executeQuery()) {
return resultSet.next();
}
}
}
View on GitHub (pinned to cf67b549a7)
Solutions
- Filter out UPDATE_BEFORE/DELETE rows before the sink (e.g. use a filter transform or configure the source to emit only UPDATE_AFTER).
- Use an upsert dialect (e.g. ON CONFLICT / ON DUPLICATE KEY) instead of the exist-check executor so all row kinds are handled.
- Enable delete support in the sink config if deletes must be applied.
- Verify the source's produce-CHANGE-EVENT setting matches the sink's expectations.
Example fix
// before: raw CDC stream to jdbc insertOrUpdate sink
// after: filter unsupported row kinds upstream
source {
MySQL-CDC { ... }
}
filter {
# drop UPDATE_BEFORE / DELETE events
} Defensive patterns
Strategy: validation
Validate before calling
// before the sink, drop unsupported row kinds rows.removeIf(r -> r.getRowKind() != RowKind.INSERT && r.getRowKind() != RowKind.UPDATE_AFTER);
Type guard
boolean isUpsertSafeRowKind(RowKind k) {
return k == RowKind.INSERT || k == RowKind.UPDATE_AFTER;
} Try / catch
try {
executor.executeBatch();
} catch (JdbcConnectorException e) {
if (CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION.equals(e.getErrorCode())) {
// switch to a dialect-based upsert sink or filter CDC events upstream
}
throw e;
} Prevention
- Filter CDC streams to INSERT/UPDATE_AFTER before JDBC sinks that don't handle deletes.
- Prefer dialect-native upsert (ON CONFLICT / ON DUPLICATE KEY) over exist-check executors.
- Enable sink delete support explicitly if DELETE events must be applied.
- Review source CHANGE-EVENT emission settings for compatibility.
When it happens
Trigger: A CDC stream (e.g. MySQL/Debezium) delivers UPDATE_BEFORE or DELETE events into a JDBC sink configured with insert/update (upsert via exist-check) mode.
Common situations: Sink receives full change-log including delete/update-before events; misconfigured CDC option that does not filter events; using the insertOrUpdate executor with a stream containing tombstones.
Related errors
- COMMON_UNSUPPORTED_OPERATION
- Failed to discover captured tables for enumerator
- Failed to discover remaining tables to capture
- Exactly once is enabled, but not found primary key or unique
- Failed to split chunks for table " + tableId
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/4b96aa4feef0cf58.
Report an issue: GitHub.