Repository: dbt-labs/dbt-core

Issue:
[CT-815] Partial parsing error at execution for metric modification
I think there might be a minor issue related to partial parsing. We now have metric references in models. If a model and metric have been parsed and the metric is deleted or renamed, I'm pretty sure we aren't scheduling the model for re-parsing, so the error would show up at execution time rather than parse time. I don't consider this a blocker -- we can fix it later.

_Originally posted by @gshank in https://github.com/dbt-labs/dbt-core/pull/5027#pullrequestreview-1030283100_


Discussion:
When a metric is deleted, need to schedule anything that refers to it to be reparsed.  This happens in multiple places in the code.  This would apply to any node that can reference a metric.

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
@@ -909,8 +909,7 @@ def delete_schema_exposure(self, schema_file, exposure_dict):
             elif unique_id in self.saved_manifest.disabled:
                 self.delete_disabled(unique_id, schema_file.file_id)
 
-    # metric are created only from schema files, so just delete
-    # the metric or the disabled metric.
+    # metrics are created only from schema files, but also can be referred to by other nodes
     def delete_schema_metric(self, schema_file, metric_dict):
         metric_name = metric_dict["name"]
         metrics = schema_file.metrics.copy()
@@ -918,6 +917,9 @@ def delete_schema_metric(self, schema_file, metric_dict):
             if unique_id in self.saved_manifest.metrics:
                 metric = self.saved_manifest.metrics[unique_id]
                 if metric.name == metric_name:
+                    # Need to find everything that referenced this metric and schedule for parsing
+                    if unique_id in self.saved_manifest.child_map:
+                        self.schedule_nodes_for_parsing(self.saved_manifest.child_map[unique_id])
                     self.deleted_manifest.metrics[unique_id] = self.saved_manifest.metrics.pop(
                         unique_id
                     )


Test patch:
diff --git a/test/integration/068_partial_parsing_tests/test-files/metric_model_a.sql b/test/integration/068_partial_parsing_tests/test-files/metric_model_a.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/068_partial_parsing_tests/test-files/metric_model_a.sql
@@ -0,0 +1,21 @@
+{%
+    set metric_list = [
+        metric('number_of_people'),
+        metric('collective_tenure')
+    ]
+%}
+
+{% if not execute %}                                
+        
+    {% set metric_names = [] %}                                         
+    {% for m in metric_list %}             
+        {% do metric_names.append(m.metric_name) %}           
+    {% endfor %}                                    
+                                 
+    -- this config does nothing, but it lets us check these values
+    {{ config(metric_names = metric_names) }}       
+                             
+{% endif %}
+ 
+
+select 1 as fun
diff --git a/test/integration/068_partial_parsing_tests/test-files/people_metrics3.yml b/test/integration/068_partial_parsing_tests/test-files/people_metrics3.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/068_partial_parsing_tests/test-files/people_metrics3.yml
@@ -0,0 +1,17 @@
+version: 2
+
+metrics:
+
+  - model: "ref('people')"
+    name: number_of_people
+    description: Total count of people
+    label: "Number of people"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'replaced'
diff --git a/test/integration/068_partial_parsing_tests/test_pp_metrics.py b/test/integration/068_partial_parsing_tests/test_pp_metrics.py
--- a/test/integration/068_partial_parsing_tests/test_pp_metrics.py
+++ b/test/integration/068_partial_parsing_tests/test_pp_metrics.py
@@ -52,7 +52,7 @@ def setup_directories(self):
 class MetricsTest(BasePPTest):
 
     @use_profile('postgres')
-    def test_postgres_env_vars_models(self):
+    def test_postgres_metrics(self):
         self.setup_directories()
         # initial run
         self.copy_file('test-files/people.sql', 'models/people.sql')
@@ -89,3 +89,18 @@ def test_postgres_env_vars_models(self):
         expected_depends_on_nodes = ['model.test.people']
         self.assertEqual(metric_people.depends_on.nodes, expected_depends_on_nodes)
 
+        # Add model referring to metric
+        self.copy_file('test-files/metric_model_a.sql', 'models/metric_model_a.sql')
+        results = self.run_dbt(["run"])
+        manifest = get_manifest()
+        model_a = manifest.nodes['model.test.metric_model_a']
+        expected_depends_on_nodes = ['metric.test.number_of_people', 'metric.test.collective_tenure']
+        self.assertEqual(model_a.depends_on.nodes, expected_depends_on_nodes)
+
+        # Then delete a metric
+        self.copy_file('test-files/people_metrics3.yml', 'models/people_metrics.yml')
+        with self.assertRaises(CompilationException):
+            # We use "parse" here and not "run" because we're checking that the CompilationException
+            # occurs at parse time, not compilation
+            results = self.run_dbt(["parse"])
+