{"record":{"id":"2e8541dc4c462436","repo":"apache/seatunnel","slug":"incremental-snapshot-for-tables-requires-primary-k-2e8541","errorCode":null,"errorMessage":"Incremental snapshot for tables requires primary key, but table %s doesn't have primary key.","messagePattern":"Incremental snapshot for tables requires primary key, but table (.+?) doesn't have primary key\\.","errorType":"exception","errorClass":"SeaTunnelException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/utils/SqlServerUtils.java","lineNumber":247,"sourceCode":"                        quotedColumn);\n        return jdbc.prepareQueryAndMap(\n                query,\n                ps -> ps.setObject(1, includedLowerBound),\n                rs -> {\n                    if (!rs.next()) {\n                        // this should never happen\n                        throw new SQLException(\n                                String.format(\n                                        \"No result returned after running query [%s]\", query));\n                    }\n                    return rs.getObject(1);\n                });\n    }\n\n    public static SeaTunnelRowType getSplitType(Table table) {\n        List<Column> primaryKeys = table.primaryKeyColumns();\n        if (primaryKeys.isEmpty()) {\n            throw new SeaTunnelException(\n                    String.format(\n                            \"Incremental snapshot for tables requires primary key,\"\n                                    + \" but table %s doesn't have primary key.\",\n                            table.id()));\n        }\n\n        // use first field in primary key as the split key\n        return getSplitType(primaryKeys.get(0));\n    }\n\n    public static SeaTunnelRowType getSplitType(Column splitColumn) {\n        return new SeaTunnelRowType(\n                new String[] {splitColumn.name()},\n                new SeaTunnelDataType<?>[] {SqlServerTypeUtils.convertFromColumn(splitColumn)});\n    }\n\n    public static Offset getLsn(SourceRecord record) {\n        return getLsnPosition(record.sourceOffset());","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/utils/SqlServerUtils.java#L229-L265","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add a primary key (or a NOT NULL unique index the connector recognizes) to the table","If DDL is not possible, use a snapshot-only read mode instead of incremental snapshot","Exclude the keyless table from table-names and capture it via a different mechanism","Ensure the PK exists in the same database the connector reads (not just a logical replica)"],"exampleFix":"// before\ntable-names = [\"dbo.Events\"]   // no primary key -> fails\n// after  (SQL Server)\nALTER TABLE dbo.Events ADD CONSTRAINT PK_Events PRIMARY KEY (EventId);","handlingStrategy":"validation","validationCode":"-- Fail fast before submitting the job\nIF NOT EXISTS (\n  SELECT 1 FROM sys.key_constraints\n  WHERE type = 'PK' AND parent_object_id = OBJECT_ID('dbo.Events'))\n  THROW 50000, 'Table dbo.Events has no primary key', 1;","typeGuard":"// Java pre-check mirroring the connector's validation\nstatic void requirePrimaryKey(Table table) {\n    if (table.primaryKeyColumns().isEmpty()) {\n        throw new IllegalArgumentException(\n            \"Table \" + table.id() + \" needs a primary key for incremental snapshot\");\n    }\n}","tryCatchPattern":null,"preventionTips":["Audit all tables in table-names for PRIMARY KEY constraints before enabling incremental snapshot","Add PKs (or NOT NULL unique indexes) to keyless tables","Use snapshot-only mode for tables that cannot have keys","Exclude keyless tables from incremental capture"],"tags":["cdc","sqlserver","primary-key","incremental-snapshot","validation"],"backgroundTag":"missing-primary-key","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}