Repository: dbt-labs/dbt-core

Issue:
[CT-273] [Bug] modifying snapshot files with multiple snapshots causes error (partial parsing)
If you modify a snapshot file with multiple snapshot blocks, and then do 'dbt run', you see the error "dbt found two resources with the name "date_orders_snapshot".  Running with "--no-partial-parse" will re-parse the entire project and then you can run again.

The partial parsing code does not handle multiple snapshot blocks.


Discussion:
Nice find! Could this also:
- Fix the same bug for macros: https://github.com/dbt-labs/dbt-core/issues/4233
- Happen for `docs` blocks? The bugs we've heard about have been more around, a `docs` block being missed, than accidentally added twice

Resolving patch:
diff --git a/core/dbt/parser/partial.py b/core/dbt/parser/partial.py
--- a/core/dbt/parser/partial.py
+++ b/core/dbt/parser/partial.py
@@ -272,10 +272,10 @@ def update_mssat_in_saved(self, new_source_file, old_source_file):
         if self.already_scheduled_for_parsing(old_source_file):
             return
 
-        # These files only have one node.
-        unique_id = None
+        # These files only have one node except for snapshots
+        unique_ids = []
         if old_source_file.nodes:
-            unique_id = old_source_file.nodes[0]
+            unique_ids = old_source_file.nodes
         else:
             # It's not clear when this would actually happen.
             # Logging in case there are other associated errors.
@@ -286,7 +286,7 @@ def update_mssat_in_saved(self, new_source_file, old_source_file):
         self.deleted_manifest.files[file_id] = old_source_file
         self.saved_files[file_id] = deepcopy(new_source_file)
         self.add_to_pp_files(new_source_file)
-        if unique_id:
+        for unique_id in unique_ids:
             self.remove_node_in_saved(new_source_file, unique_id)
 
     def remove_node_in_saved(self, source_file, unique_id):
@@ -358,7 +358,7 @@ def remove_mssat_file(self, source_file):
         if not source_file.nodes:
             fire_event(PartialParsingMissingNodes(file_id=source_file.file_id))
             return
-        # There is generally only 1 node for SQL files, except for macros
+        # There is generally only 1 node for SQL files, except for macros and snapshots
         for unique_id in source_file.nodes:
             self.remove_node_in_saved(source_file, unique_id)
             self.schedule_referencing_nodes_for_parsing(unique_id)


Test patch:
diff --git a/test/integration/068_partial_parsing_tests/test-files/snapshot.sql b/test/integration/068_partial_parsing_tests/test-files/snapshot.sql
--- a/test/integration/068_partial_parsing_tests/test-files/snapshot.sql
+++ b/test/integration/068_partial_parsing_tests/test-files/snapshot.sql
@@ -13,3 +13,17 @@ select * from {{ ref('orders') }}
 
 {% endsnapshot %}
 
+{% snapshot orders2_snapshot %}
+
+{{
+    config(
+      target_schema=schema,
+      strategy='check',
+      unique_key='id',
+      check_cols=['order_date'],
+    )
+}}
+
+select * from {{ ref('orders') }}
+
+{% endsnapshot %}
diff --git a/test/integration/068_partial_parsing_tests/test-files/snapshot2.sql b/test/integration/068_partial_parsing_tests/test-files/snapshot2.sql
--- a/test/integration/068_partial_parsing_tests/test-files/snapshot2.sql
+++ b/test/integration/068_partial_parsing_tests/test-files/snapshot2.sql
@@ -1,3 +1,4 @@
+- add a comment
 {% snapshot orders_snapshot %}
 
 {{
@@ -8,7 +9,22 @@
       check_cols=['status'],
     )
 }}
+
 select * from {{ ref('orders') }}
 
 {% endsnapshot %}
 
+{% snapshot orders2_snapshot %}
+
+{{
+    config(
+      target_schema=schema,
+      strategy='check',
+      unique_key='id',
+      check_cols=['order_date'],
+    )
+}}
+
+select * from {{ ref('orders') }}
+
+{% endsnapshot %}
diff --git a/test/integration/068_partial_parsing_tests/test_partial_parsing.py b/test/integration/068_partial_parsing_tests/test_partial_parsing.py
--- a/test/integration/068_partial_parsing_tests/test_partial_parsing.py
+++ b/test/integration/068_partial_parsing_tests/test_partial_parsing.py
@@ -503,10 +503,12 @@ def test_postgres_pp_snapshots(self):
         manifest = get_manifest()
         snapshot_id = 'snapshot.test.orders_snapshot'
         self.assertIn(snapshot_id, manifest.nodes)
+        snapshot2_id = 'snapshot.test.orders2_snapshot'
+        self.assertIn(snapshot2_id, manifest.nodes)
 
         # run snapshot
         results = self.run_dbt(["--partial-parse", "snapshot"])
-        self.assertEqual(len(results), 1)
+        self.assertEqual(len(results), 2)
 
         # modify snapshot
         self.copy_file('test-files/snapshot2.sql', 'snapshots/snapshot.sql')