Skip to content

Commit a2b803b

Browse files
Add failing integration test proving insert_overwrite duplication bug
Demonstrates that insert_overwrite can produce duplicate rows when a partition's Glue metadata is missing but S3 files still exist — the state left after a run that completed clean_up_partitions but failed before the INSERT finished. The test uses ALTER TABLE DROP PARTITION to reproduce this state deterministically: it removes the Glue partition metadata without touching S3 files, then re-runs the model and asserts no duplicates. The assertion currently fails (count=2), confirming the bug.
1 parent 8274c6d commit a2b803b

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Regression test for insert_overwrite data duplication caused by stale S3 files that have no
3+
corresponding Glue partition metadata.
4+
5+
This state can arise when a previous run:
6+
1. Called clean_up_partitions (removes Glue metadata + S3 files for a partition)
7+
2. Started the INSERT INTO — writing new files to S3
8+
3. Failed before the INSERT completed (so no Glue partition was registered)
9+
10+
On the next run, clean_up_partitions queries Glue, finds nothing, and skips S3 cleanup. The new
11+
INSERT then writes files alongside the stale ones. When Glue finally registers the partition, it
12+
points to the directory containing both old and new files — causing duplicates.
13+
14+
The test simulates this state directly using ALTER TABLE DROP PARTITION, which removes the Glue
15+
partition metadata without touching S3 files, exactly replicating the post-failure state.
16+
"""
17+
18+
import pytest
19+
20+
from dbt.contracts.results import RunStatus
21+
from dbt.tests.util import run_dbt
22+
23+
insert_overwrite_model_sql = """
24+
{{ config(
25+
materialized='incremental',
26+
incremental_strategy='insert_overwrite',
27+
partitioned_by=['date_column'],
28+
s3_data_naming='table',
29+
format='parquet'
30+
) }}
31+
select
32+
1 as value,
33+
cast(from_iso8601_date('{{ var("logical_date") }}') as date) as date_column
34+
"""
35+
36+
37+
class TestInsertOverwriteNoDuplicatesAfterStaleS3Files:
38+
@pytest.fixture(scope="class")
39+
def models(self):
40+
return {"insert_overwrite_no_dupes.sql": insert_overwrite_model_sql}
41+
42+
def test_no_duplicates_after_stale_s3_files(self, project):
43+
relation_name = "insert_overwrite_no_dupes"
44+
count_query = f"select count(*) as cnt from {project.test_schema}.{relation_name}"
45+
date_filter_query = (
46+
f"select count(*) as cnt from {project.test_schema}.{relation_name} "
47+
f"where date_column = date '2024-01-01'"
48+
)
49+
50+
first_run = run_dbt(
51+
["run", "--select", relation_name, "--vars", '{"logical_date": "2024-01-01"}']
52+
)
53+
assert first_run.results[0].status == RunStatus.Success
54+
assert project.run_sql(count_query, fetch="all")[0][0] == 1
55+
56+
# Simulate a previous run's partial failure: drop the Glue partition metadata but leave
57+
# the S3 files in place. This is exactly the state left when clean_up_partitions succeeds
58+
# but the subsequent INSERT fails mid-execution.
59+
project.run_sql(
60+
f"alter table {project.test_schema}.{relation_name} "
61+
f"drop partition (date_column='2024-01-01')"
62+
)
63+
64+
# Without the fix, this run would find no Glue metadata for date_column=2024-01-01,
65+
# skip S3 cleanup, then write new files alongside the stale ones — producing 2 rows.
66+
second_run = run_dbt(
67+
["run", "--select", relation_name, "--vars", '{"logical_date": "2024-01-01"}']
68+
)
69+
assert second_run.results[0].status == RunStatus.Success
70+
assert project.run_sql(date_filter_query, fetch="all")[0][0] == 1

0 commit comments

Comments
 (0)