apache/seatunnel · error · SeaTunnelException

Incremental snapshot for tables requires primary key, but ta

Error message

Incremental snapshot for tables requires primary key, but table %s doesn't have primary key.

What it means

SqlServerUtils.getSplitType derives the chunk-split key from the table's primary key; if the table has no primary key columns, incremental snapshot cannot chunk it, so it throws SeaTunnelException naming the table. This is a fail-fast validation at job startup.

Source

Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/utils/SqlServerUtils.java:247

                        quotedColumn);
        return jdbc.prepareQueryAndMap(
                query,
                ps -> ps.setObject(1, includedLowerBound),
                rs -> {
                    if (!rs.next()) {
                        // this should never happen
                        throw new SQLException(
                                String.format(
                                        "No result returned after running query [%s]", query));
                    }
                    return rs.getObject(1);
                });
    }

    public static SeaTunnelRowType getSplitType(Table table) {
        List<Column> primaryKeys = table.primaryKeyColumns();
        if (primaryKeys.isEmpty()) {
            throw new SeaTunnelException(
                    String.format(
                            "Incremental snapshot for tables requires primary key,"
                                    + " but table %s doesn't have primary key.",
                            table.id()));
        }

        // use first field in primary key as the split key
        return getSplitType(primaryKeys.get(0));
    }

    public static SeaTunnelRowType getSplitType(Column splitColumn) {
        return new SeaTunnelRowType(
                new String[] {splitColumn.name()},
                new SeaTunnelDataType<?>[] {SqlServerTypeUtils.convertFromColumn(splitColumn)});
    }

    public static Offset getLsn(SourceRecord record) {
        return getLsnPosition(record.sourceOffset());

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Add a primary key (or a NOT NULL unique index the connector recognizes) to the table
  2. If DDL is not possible, use a snapshot-only read mode instead of incremental snapshot
  3. Exclude the keyless table from table-names and capture it via a different mechanism
  4. Ensure the PK exists in the same database the connector reads (not just a logical replica)

Example fix

// before
table-names = ["dbo.Events"]   // no primary key -> fails
// after  (SQL Server)
ALTER TABLE dbo.Events ADD CONSTRAINT PK_Events PRIMARY KEY (EventId);
Defensive patterns

Strategy: validation

Validate before calling

-- Fail fast before submitting the job
IF NOT EXISTS (
  SELECT 1 FROM sys.key_constraints
  WHERE type = 'PK' AND parent_object_id = OBJECT_ID('dbo.Events'))
  THROW 50000, 'Table dbo.Events has no primary key', 1;

Type guard

// Java pre-check mirroring the connector's validation
static void requirePrimaryKey(Table table) {
    if (table.primaryKeyColumns().isEmpty()) {
        throw new IllegalArgumentException(
            "Table " + table.id() + " needs a primary key for incremental snapshot");
    }
}

Prevention

When it happens

Trigger: getSplitType called during incremental-snapshot setup for a table whose Table.primaryKeyColumns() is empty — a heap table or table without a PK/unique key in SQL Server.

Common situations: Capturing a SQL Server table created without a PRIMARY KEY constraint; capturing a view or heap table; partitioned tables where the PK doesn't cover all columns; older databases migrated without constraints.

Related errors


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