Repository: dbt-labs/dbt-core

Issue:
[SL] Add `cache` configuration to `SavedQuery` config
### Housekeeping

- [X] I am a maintainer of dbt-core

### Short description

The `SavedQuery` config is getting a new top level property, `cache`. This is to to enable semantic layer implementations to setup caching as they see fit. This is being added to the [dbt-semantic-interfaces](https://github.com/dbt-labs/dbt-semantic-interfaces/) protocol definitions in https://github.com/dbt-labs/dbt-semantic-interfaces/issues/252. The value for the `cache` key is an object with one attribute (currently) `enabled` which is either `true` or `false`.

### Acceptance criteria

- `cache` can optionally be specified in the `config` of `SavedQuery`s
- if `cache` is specified for `SavedQuery`s in a dbt_project.toml, it is properly inherited
- if a `cache` isn't specified, a default `cache` object is still created with `enabled = false`

### Suggested Tests

- `cache` can be specified in a `SavedQuery` config
- `cache` can be specified for a `SavedQuery` config via the `dbt_project.toml`
- the `cache` defined in yaml for the `SavedQuery` overrides settings from the `dbt_project.toml`
- if no `cache` is specified, a default one is supplied with `enabled = false`

### Impact to Other Teams

Affects the Cloud Artifacts team as new properties will be present
Affects the Semantic Layer team as they need this to be able to implement functionality

### Will backports be required?

No

### Context

_No response_


Discussion:

Resolving patch:
diff --git a/core/dbt/artifacts/resources/v1/saved_query.py b/core/dbt/artifacts/resources/v1/saved_query.py
--- a/core/dbt/artifacts/resources/v1/saved_query.py
+++ b/core/dbt/artifacts/resources/v1/saved_query.py
@@ -40,6 +40,11 @@ class QueryParams(dbtClassMixin):
     where: Optional[WhereFilterIntersection]
 
 
+@dataclass
+class SavedQueryCache(dbtClassMixin):
+    enabled: bool = False
+
+
 @dataclass
 class SavedQueryConfig(BaseConfig):
     """Where config options for SavedQueries are stored.
@@ -61,6 +66,7 @@ class SavedQueryConfig(BaseConfig):
     )
     export_as: Optional[ExportDestinationType] = None
     schema: Optional[str] = None
+    cache: SavedQueryCache = field(default_factory=SavedQueryCache)
 
 
 @dataclass


Test patch:
diff --git a/tests/functional/saved_queries/fixtures.py b/tests/functional/saved_queries/fixtures.py
--- a/tests/functional/saved_queries/fixtures.py
+++ b/tests/functional/saved_queries/fixtures.py
@@ -91,3 +91,27 @@
     exports:
         - name: my_export
 """
+
+saved_query_with_cache_configs_defined_yml = """
+saved_queries:
+  - name: test_saved_query
+    description: "{{ doc('saved_query_description') }}"
+    label: Test Saved Query
+    config:
+      cache:
+        enabled: True
+    query_params:
+        metrics:
+            - simple_metric
+        group_by:
+            - "Dimension('user__ds')"
+        where:
+            - "{{ Dimension('user__ds', 'DAY') }} <= now()"
+            - "{{ Dimension('user__ds', 'DAY') }} >= '2023-01-01'"
+    exports:
+        - name: my_export
+          config:
+            alias: my_export_alias
+            export_as: table
+            schema: my_export_schema_name
+"""
diff --git a/tests/functional/saved_queries/test_configs.py b/tests/functional/saved_queries/test_configs.py
--- a/tests/functional/saved_queries/test_configs.py
+++ b/tests/functional/saved_queries/test_configs.py
@@ -8,6 +8,7 @@
 from tests.functional.saved_queries.fixtures import (
     saved_queries_yml,
     saved_query_description,
+    saved_query_with_cache_configs_defined_yml,
     saved_query_with_extra_config_attributes_yml,
     saved_query_with_export_configs_defined_at_saved_query_level_yml,
     saved_query_without_export_configs_defined_yml,
@@ -29,6 +30,7 @@ def project_config_update(self):
                         "+enabled": True,
                         "+export_as": ExportDestinationType.VIEW.value,
                         "+schema": "my_default_export_schema",
+                        "+cache": {"enabled": True},
                     }
                 },
             },
@@ -58,6 +60,7 @@ def test_basic_saved_query_config(
         saved_query = result.result.saved_queries["saved_query.test.test_saved_query"]
         assert saved_query.config.export_as == ExportDestinationType.VIEW
         assert saved_query.config.schema == "my_default_export_schema"
+        assert saved_query.config.cache.enabled is True
 
         # disable the saved_query via project config and rerun
         config_patch = {"saved-queries": {"test": {"test_saved_query": {"+enabled": False}}}}
@@ -67,6 +70,33 @@ def test_basic_saved_query_config(
         assert len(result.result.saved_queries) == 0
 
 
+# Test that the cache will default to enabled = false if not set in the saved_query config
+class TestSavedQueryDefaultCacheConfigs(BaseConfigProject):
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "saved_queries.yml": saved_query_with_extra_config_attributes_yml,
+            "schema.yml": schema_yml,
+            "fct_revenue.sql": fct_revenue_sql,
+            "metricflow_time_spine.sql": metricflow_time_spine_sql,
+            "docs.md": saved_query_description,
+        }
+
+    def test_basic_saved_query_config(
+        self,
+        project,
+    ):
+        runner = dbtTestRunner()
+
+        # parse with default fixture project config
+        result = runner.invoke(["parse"])
+        assert result.success
+        assert isinstance(result.result, Manifest)
+        assert len(result.result.saved_queries) == 1
+        saved_query = result.result.saved_queries["saved_query.test.test_saved_query"]
+        assert saved_query.config.cache.enabled is False
+
+
 class TestExportConfigsWithAdditionalProperties(BaseConfigProject):
     @pytest.fixture(scope="class")
     def models(self):
@@ -184,3 +214,79 @@ def test_export_config_inherits_from_project(
         assert len(result.result.saved_queries) == 1
         saved_query = result.result.saved_queries["saved_query.test.test_saved_query"]
         assert saved_query.config.export_as == ExportDestinationType.TABLE
+
+
+# cache can be specified just in a SavedQuery config
+class TestSavedQueryLevelCacheConfigs(BaseConfigProject):
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "saved_queries.yml": saved_query_with_cache_configs_defined_yml,
+            "schema.yml": schema_yml,
+            "fct_revenue.sql": fct_revenue_sql,
+            "metricflow_time_spine.sql": metricflow_time_spine_sql,
+            "docs.md": saved_query_description,
+        }
+
+    def test_basic_saved_query_config(
+        self,
+        project,
+    ):
+        runner = dbtTestRunner()
+
+        # parse with default fixture project config
+        result = runner.invoke(["parse"])
+        assert result.success
+        assert isinstance(result.result, Manifest)
+        assert len(result.result.saved_queries) == 1
+        saved_query = result.result.saved_queries["saved_query.test.test_saved_query"]
+        assert saved_query.config.cache.enabled is True
+
+
+# the cache defined in yaml for the SavedQuery overrides settings from the dbt_project.toml
+class TestSavedQueryCacheConfigsOverride(BaseConfigProject):
+    @pytest.fixture(scope="class")
+    def project_config_update(self):
+        return {
+            "saved-queries": {
+                "test": {
+                    "test_saved_query": {
+                        "+cache": {"enabled": True},
+                    }
+                },
+            },
+        }
+
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "saved_queries.yml": saved_query_with_cache_configs_defined_yml,
+            "schema.yml": schema_yml,
+            "fct_revenue.sql": fct_revenue_sql,
+            "metricflow_time_spine.sql": metricflow_time_spine_sql,
+            "docs.md": saved_query_description,
+        }
+
+    def test_override_saved_query_config(
+        self,
+        project,
+    ):
+        runner = dbtTestRunner()
+
+        # parse with default fixture project config
+        result = runner.invoke(["parse"])
+        assert result.success
+        assert isinstance(result.result, Manifest)
+        assert len(result.result.saved_queries) == 1
+        saved_query = result.result.saved_queries["saved_query.test.test_saved_query"]
+        assert saved_query.config.cache.enabled is True
+
+        # set cache to enabled=False via project config but since it's set to true at the saved_query
+        # level, it should stay enabled
+        config_patch = {
+            "saved-queries": {"test": {"test_saved_query": {"+cache": {"enabled": False}}}}
+        }
+        update_config_file(config_patch, project.project_root, "dbt_project.yml")
+        result = runner.invoke(["parse"])
+        assert result.success
+        assert saved_query.config.cache.enabled is True