Skip to content

Commit 0c3f9a3

Browse files
fuziontechclaude
andcommitted
Remove insert-vs-alter transaction conflict check
Concurrent INSERT and ALTER TABLE operations (e.g., ADD COLUMN, RENAME COLUMN) on the same table should not conflict. Each data file carries its own mapping_id that describes the column layout at write time. The multi-file reader uses this mapping to correctly read files written under older schemas, so an INSERT that was planned before an ALTER can safely commit after it. This is needed for tools like sqlmesh that perform schema evolution (ALTER TABLE) on target tables while concurrent data pipelines INSERT into those same tables. Previously, the INSERT would fail with "Transaction conflict - attempting to insert into table - but another transaction has altered it" after exhausting all retries. The insert-vs-drop conflict check is preserved since a dropped table is genuinely gone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 038084c commit 0c3f9a3

2 files changed

Lines changed: 107 additions & 2 deletions

File tree

src/storage/ducklake_transaction.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,11 +1232,13 @@ void DuckLakeTransaction::CheckForConflicts(const TransactionChangeInformation &
12321232
}
12331233
for (auto &table_id : changes.tables_inserted_into) {
12341234
ConflictCheck(table_id, other_changes.dropped_tables, "insert into table", "dropped it");
1235-
ConflictCheck(table_id, other_changes.altered_tables, "insert into table", "altered it");
1235+
// NOTE: insert-vs-alter is safe because each data file carries its own mapping_id
1236+
// that describes the column layout at write time. The multi-file reader uses
1237+
// this mapping to correctly read files written under older schemas, even after
1238+
// ALTER TABLE ADD/DROP/RENAME COLUMN operations.
12361239
}
12371240
for (auto &table_id : changes.tables_inserted_inlined) {
12381241
ConflictCheck(table_id, other_changes.dropped_tables, "insert into table", "dropped it");
1239-
ConflictCheck(table_id, other_changes.altered_tables, "insert into table", "altered it");
12401242
}
12411243
for (auto &table_id : changes.tables_deleted_from) {
12421244
ConflictCheck(table_id, other_changes.dropped_tables, "delete from table", "dropped it");
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# name: test/sql/transaction/transaction_insert_alter_no_conflict.test
2+
# description: Test that concurrent INSERT and ALTER TABLE do not conflict
3+
# group: [transaction]
4+
5+
require ducklake
6+
7+
require parquet
8+
9+
test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db
10+
11+
test-env DATA_PATH __TEST_DIR__
12+
13+
statement ok
14+
ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_insert_alter_no_conflict_files')
15+
16+
statement ok
17+
SET immediate_transaction_mode=true
18+
19+
statement ok
20+
CREATE TABLE ducklake.test(i INTEGER, j INTEGER);
21+
22+
statement ok
23+
INSERT INTO ducklake.test VALUES (1, 10);
24+
25+
# one transaction inserts while another alters: should NOT conflict
26+
statement ok con1
27+
BEGIN
28+
29+
statement ok con2
30+
BEGIN
31+
32+
statement ok con1
33+
INSERT INTO ducklake.test VALUES (2, 20);
34+
35+
statement ok con2
36+
ALTER TABLE ducklake.test ADD COLUMN k INTEGER
37+
38+
statement ok con1
39+
COMMIT
40+
41+
statement ok con2
42+
COMMIT
43+
44+
# verify both changes applied: 2 rows, 3 columns, new column is NULL for existing rows
45+
query III
46+
SELECT i, j, k FROM ducklake.test ORDER BY i
47+
----
48+
1 10 NULL
49+
2 20 NULL
50+
51+
# one transaction alters while another inserts (reversed order): should NOT conflict
52+
statement ok con1
53+
BEGIN
54+
55+
statement ok con2
56+
BEGIN
57+
58+
statement ok con1
59+
ALTER TABLE ducklake.test ADD COLUMN m INTEGER
60+
61+
statement ok con2
62+
INSERT INTO ducklake.test VALUES (3, 30, 300);
63+
64+
statement ok con1
65+
COMMIT
66+
67+
statement ok con2
68+
COMMIT
69+
70+
# verify: 3 rows, 4 columns
71+
query IIII
72+
SELECT i, j, k, m FROM ducklake.test ORDER BY i
73+
----
74+
1 10 NULL NULL
75+
2 20 NULL NULL
76+
3 30 300 NULL
77+
78+
# concurrent insert and rename column: should NOT conflict
79+
statement ok con1
80+
BEGIN
81+
82+
statement ok con2
83+
BEGIN
84+
85+
statement ok con1
86+
INSERT INTO ducklake.test VALUES (4, 40, 400, 4000);
87+
88+
statement ok con2
89+
ALTER TABLE ducklake.test RENAME COLUMN m TO n
90+
91+
statement ok con1
92+
COMMIT
93+
94+
statement ok con2
95+
COMMIT
96+
97+
query IIII
98+
SELECT i, j, k, n FROM ducklake.test ORDER BY i
99+
----
100+
1 10 NULL NULL
101+
2 20 NULL NULL
102+
3 30 300 NULL
103+
4 40 400 4000

0 commit comments

Comments
 (0)