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

  1. Filter out UPDATE_BEFORE/DELETE rows before the sink (e.g. use a filter transform or configure the source to emit only UPDATE_AFTER).
  2. Use an upsert dialect (e.g. ON CONFLICT / ON DUPLICATE KEY) instead of the exist-check executor so all row kinds are handled.
  3. Enable delete support in the sink config if deletes must be applied.
  4. 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

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


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