Repository: dbt-labs/dbt-core

Issue:
[CT-2994] [Bug] `sql_header` with jinja not parsed correctly with `dbt show`
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

If you have some jinja in your `sql_header`, `dbt show` injects it verbatim, without doing any parsing - this results in invalid sql characters.

### Expected Behavior

`dbt show` should be well behaved and `sql_header` should be parsed/resolved prior to sending the query to the datawarehouse.

### Steps To Reproduce

1. Project setup:

```yml
# dbt_project.yml
name: my_dbt_project
profile: snowflake
config-version: 2
version: 1.0

# prettier-ignore
models:
  my_dbt_project:
    +materialized: table
    +sql_header: "{{ 'CALL SYSTEM$WAIT(' ~ var('wait_seconds', 0) ~ ');' }}"
```

```sql
-- models/foo.sql
select 1 id
```

2. With both dbt-core 1.5.latest (`1.5.4`) and 1.6.latest (`1.6.0`), do a show on foo:

```sh
$ dbt --debug show -s foo
23:43:46  Concurrency: 1 threads (target='default')
23:43:46  
23:43:46  Began running node model.my_dbt_project.foo
23:43:46  Re-using an available connection from the pool (formerly list_development_dbt_jyeo, now model.my_dbt_project.foo)
23:43:46  Began compiling node model.my_dbt_project.foo
23:43:46  Writing injected SQL for node "model.my_dbt_project.foo"
23:43:46  Timing info for model.my_dbt_project.foo (compile): 11:43:46.804486 => 11:43:46.817100
23:43:46  Began executing node model.my_dbt_project.foo
23:43:46  Using snowflake connection "model.my_dbt_project.foo"
23:43:46  On model.my_dbt_project.foo: /* {"app": "dbt", "dbt_version": "1.6.0", "profile_name": "snowflake", "target_name": "default", "node_id": "model.my_dbt_project.foo"} */
{{ 'CALL SYSTEM$WAIT(' ~ var('wait_seconds', 0) ~ ');' }}select 1 id
23:43:46  Opening a new connection, currently in state closed
23:43:48  Snowflake adapter: Snowflake query id: 01ae558f-0403-767c-000d-37831bf6791e
23:43:48  Snowflake adapter: Snowflake error: 001003 (42000): SQL compilation error:
syntax error line 1 at position 1 unexpected '{'.
23:43:48  Timing info for model.my_dbt_project.foo (execute): 11:43:46.820584 => 11:43:48.530728
23:43:48  On model.my_dbt_project.foo: Close
23:43:49  Database Error in model foo (models/foo.sql)
  001003 (42000): SQL compilation error:
  syntax error line 1 at position 1 unexpected '{'.
23:43:49  Finished running node model.my_dbt_project.foo
23:43:49  Connection 'master' was properly closed.
23:43:49  Connection 'model.my_dbt_project.foo' was properly closed.
23:43:49  Encountered an error:
Runtime Error
  Database Error in model foo (models/foo.sql)
    001003 (42000): SQL compilation error:
    syntax error line 1 at position 1 unexpected '{'.
23:43:49  Command `dbt show` failed at 11:43:49.175370 after 8.42 seconds
23:43:49  Sending event: {'category': 'dbt', 'action': 'invocation', 'label': 'end', 'context': [<snowplow_tracker.self_describing_json.SelfDescribingJson object at 0x10e13b820>, <snowplow_tracker.self_describing_json.SelfDescribingJson object at 0x110836130>, <snowplow_tracker.self_describing_json.SelfDescribingJson object at 0x10fe41850>]}
23:43:49  Flushing usage events
```

### Relevant log output

_No response_

### Environment

```markdown
- OS: macOS
- Python: 3.9.13
- dbt: 1.5.4 / 1.6.0
```


### Which database adapter are you using with dbt?

snowflake

### Additional Context

If we downgrade to `dbt-core==1.5.0`, show works as expected:

```sh
$ dbt --debug show -s foo
23:45:51  Concurrency: 1 threads (target='default')
23:45:51  
23:45:52  Began running node model.my_dbt_project.foo
23:45:52  Re-using an available connection from the pool (formerly list_development_dbt_jyeo, now model.my_dbt_project.foo)
23:45:52  Began compiling node model.my_dbt_project.foo
23:45:52  Writing injected SQL for node "model.my_dbt_project.foo"
23:45:52  Timing info for model.my_dbt_project.foo (compile): 11:45:52.017324 => 11:45:52.140537
23:45:52  Began executing node model.my_dbt_project.foo
23:45:52  Using snowflake connection "model.my_dbt_project.foo"
23:45:52  On model.my_dbt_project.foo: /* {"app": "dbt", "dbt_version": "1.5.0", "profile_name": "snowflake", "target_name": "default", "node_id": "model.my_dbt_project.foo"} */
select 1 id
23:45:52  Opening a new connection, currently in state closed
23:45:53  SQL status: SUCCESS 1 in 2.0 seconds
23:45:53  Timing info for model.my_dbt_project.foo (execute): 11:45:52.146470 => 11:45:53.773370
23:45:53  On model.my_dbt_project.foo: Close
23:45:54  Finished running node model.my_dbt_project.foo
23:45:54  Connection 'master' was properly closed.
23:45:54  Connection 'model.my_dbt_project.foo' was properly closed.
23:45:54  Command end result
23:45:54  Previewing node 'foo':
| ID |
| -- |
|  1 |

23:45:54  Command `dbt show` succeeded at 11:45:54.583669 after 9.60 seconds
23:45:54  Sending event: {'category': 'dbt', 'action': 'invocation', 'label': 'end', 'context': [<snowplow_tracker.self_describing_json.SelfDescribingJson object at 0x105ba2ce0>, <snowplow_tracker.self_describing_json.SelfDescribingJson object at 0x105e98190>, <snowplow_tracker.self_describing_json.SelfDescribingJson object at 0x119358d00>]}
23:45:54  Flushing usage events
```

But that's simply because we don't inject `sql_header` as we are now doing with #7568

----

Additionally, in the dbt Cloud IDE, 1.5.latest works because it does `show --inline` with the file contents:

![image](https://github.com/dbt-labs/dbt-core/assets/8496748/347d7829-cb50-4350-8290-ef0b72eb5c9d)



Discussion:

Resolving patch:
diff --git a/core/dbt/task/show.py b/core/dbt/task/show.py
--- a/core/dbt/task/show.py
+++ b/core/dbt/task/show.py
@@ -2,6 +2,7 @@
 import threading
 import time
 
+from dbt.context.providers import generate_runtime_model_context
 from dbt.contracts.graph.nodes import SeedNode
 from dbt.contracts.results import RunResult, RunStatus
 from dbt.events.base_types import EventLevel
@@ -23,14 +24,21 @@ def execute(self, compiled_node, manifest):
         # Allow passing in -1 (or any negative number) to get all rows
         limit = None if self.config.args.limit < 0 else self.config.args.limit
 
-        if "sql_header" in compiled_node.unrendered_config:
-            compiled_node.compiled_code = (
-                compiled_node.unrendered_config["sql_header"] + compiled_node.compiled_code
-            )
-
+        model_context = generate_runtime_model_context(compiled_node, self.config, manifest)
+        compiled_node.compiled_code = self.adapter.execute_macro(
+            macro_name="get_show_sql",
+            manifest=manifest,
+            context_override=model_context,
+            kwargs={
+                "compiled_code": model_context["compiled_code"],
+                "sql_header": model_context["config"].get("sql_header"),
+                "limit": limit,
+            },
+        )
         adapter_response, execute_result = self.adapter.execute(
-            compiled_node.compiled_code, fetch=True, limit=limit
+            compiled_node.compiled_code, fetch=True
         )
+
         end_time = time.time()
 
         return RunResult(


Test patch:
diff --git a/tests/adapter/dbt/tests/adapter/dbt_show/fixtures.py b/tests/adapter/dbt/tests/adapter/dbt_show/fixtures.py
new file mode 100644
--- /dev/null
+++ b/tests/adapter/dbt/tests/adapter/dbt_show/fixtures.py
@@ -0,0 +1,34 @@
+models__sql_header = """
+{% call set_sql_header(config) %}
+set session time zone '{{ var("timezone", "Europe/Paris") }}';
+{%- endcall %}
+select current_setting('timezone') as timezone
+"""
+
+models__ephemeral_model = """
+{{ config(materialized = 'ephemeral') }}
+select
+    coalesce(sample_num, 0) + 10 as col_deci
+from {{ ref('sample_model') }}
+"""
+
+models__second_ephemeral_model = """
+{{ config(materialized = 'ephemeral') }}
+select
+    col_deci + 100 as col_hundo
+from {{ ref('ephemeral_model') }}
+"""
+
+models__sample_model = """
+select * from {{ ref('sample_seed') }}
+"""
+
+seeds__sample_seed = """sample_num,sample_bool
+1,true
+2,false
+3,true
+4,false
+5,true
+6,false
+7,true
+"""
diff --git a/tests/adapter/dbt/tests/adapter/dbt_show/test_dbt_show.py b/tests/adapter/dbt/tests/adapter/dbt_show/test_dbt_show.py
new file mode 100644
--- /dev/null
+++ b/tests/adapter/dbt/tests/adapter/dbt_show/test_dbt_show.py
@@ -0,0 +1,62 @@
+import pytest
+from dbt.tests.util import run_dbt
+
+from dbt.tests.adapter.dbt_show.fixtures import (
+    models__sql_header,
+    models__ephemeral_model,
+    models__second_ephemeral_model,
+    models__sample_model,
+    seeds__sample_seed,
+)
+
+
+# -- Below we define base classes for tests you import based on if your adapter supports dbt show or not --
+class BaseShowLimit:
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "sample_model.sql": models__sample_model,
+            "ephemeral_model.sql": models__ephemeral_model,
+        }
+
+    @pytest.fixture(scope="class")
+    def seeds(self):
+        return {"sample_seed.csv": seeds__sample_seed}
+
+    @pytest.mark.parametrize(
+        "args,expected",
+        [
+            ([], 5),  # default limit
+            (["--limit", 3], 3),  # fetch 3 rows
+            (["--limit", -1], 7),  # fetch all rows
+        ],
+    )
+    def test_limit(self, project, args, expected):
+        run_dbt(["build"])
+        dbt_args = ["show", "--inline", models__second_ephemeral_model, *args]
+        results = run_dbt(dbt_args)
+        assert len(results.results[0].agate_table) == expected
+        # ensure limit was injected in compiled_code when limit specified in command args
+        limit = results.args.get("limit")
+        if limit > 0:
+            assert f"limit {limit}" in results.results[0].node.compiled_code
+
+
+class BaseShowSqlHeader:
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "sql_header.sql": models__sql_header,
+        }
+
+    def test_sql_header(self, project):
+        run_dbt(["build", "--vars", "timezone: Asia/Kolkata"])
+        run_dbt(["show", "--select", "sql_header", "--vars", "timezone: Asia/Kolkata"])
+
+
+class TestPostgresShowSqlHeader(BaseShowSqlHeader):
+    pass
+
+
+class TestPostgresShowLimit(BaseShowLimit):
+    pass
diff --git a/tests/functional/show/fixtures.py b/tests/functional/show/fixtures.py
--- a/tests/functional/show/fixtures.py
+++ b/tests/functional/show/fixtures.py
@@ -37,7 +37,7 @@
 
 models__sql_header = """
 {% call set_sql_header(config) %}
-set session time zone 'Asia/Kolkata';
+set session time zone '{{ var("timezone", "Europe/Paris") }}';
 {%- endcall %}
 select current_setting('timezone') as timezone
 """
diff --git a/tests/functional/show/test_show.py b/tests/functional/show/test_show.py
--- a/tests/functional/show/test_show.py
+++ b/tests/functional/show/test_show.py
@@ -11,7 +11,6 @@
     models__second_model,
     models__ephemeral_model,
     schema_yml,
-    models__sql_header,
     private_model_yml,
 )
 
@@ -25,7 +24,6 @@ def models(self):
             "sample_number_model_with_nulls.sql": models__sample_number_model_with_nulls,
             "second_model.sql": models__second_model,
             "ephemeral_model.sql": models__ephemeral_model,
-            "sql_header.sql": models__sql_header,
         }
 
     @pytest.fixture(scope="class")
@@ -143,35 +141,12 @@ def test_second_ephemeral_model(self, project):
         assert "col_hundo" in log_output
 
 
-class TestShowLimit(ShowBase):
-    @pytest.mark.parametrize(
-        "args,expected",
-        [
-            ([], 5),  # default limit
-            (["--limit", 3], 3),  # fetch 3 rows
-            (["--limit", -1], 7),  # fetch all rows
-        ],
-    )
-    def test_limit(self, project, args, expected):
-        run_dbt(["build"])
-        dbt_args = ["show", "--inline", models__second_ephemeral_model, *args]
-        results = run_dbt(dbt_args)
-        assert len(results.results[0].agate_table) == expected
-
-
 class TestShowSeed(ShowBase):
     def test_seed(self, project):
         (_, log_output) = run_dbt_and_capture(["show", "--select", "sample_seed"])
         assert "Previewing node 'sample_seed'" in log_output
 
 
-class TestShowSqlHeader(ShowBase):
-    def test_sql_header(self, project):
-        run_dbt(["build"])
-        (_, log_output) = run_dbt_and_capture(["show", "--select", "sql_header"])
-        assert "Asia/Kolkata" in log_output
-
-
 class TestShowModelVersions:
     @pytest.fixture(scope="class")
     def models(self):