Repository: dbt-labs/dbt-core

Issue:
[CT-2440] `dbt show` throws `Database Error` for models with `sql_header` required for valid query 
If a model is configured with a `sql_header` that is necessary to successfully run the query, `dbt show` currently fails because the [`compiled_node.compiled_code` does not include the sql_header SQL](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/task/show.py#L21).

Reproduction case (run against BQ, but not a BQ-specific issue)

```
-- models/my_model.sql
{% call set_sql_header(config) %}
  CREATE TEMPORARY FUNCTION yes_no_to_boolean(answer STRING)
  RETURNS BOOLEAN AS (
    CASE
    WHEN LOWER(answer) = 'yes' THEN True
    WHEN LOWER(answer) = 'no' THEN False
    ELSE NULL
    END
  );
{%- endcall %}

select yes_no_to_boolean("yes") as column
```

```
dbt show --select my_model --project-dir
19:00:05  Found 1 model, 0 tests, 0 snapshots, 0 analyses, 551 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics, 0 groups
19:00:05  
19:00:06  Concurrency: 1 threads (target='dev')
19:00:06  
19:00:08  BigQuery adapter: https://console.cloud.google.com/bigquery?project=dbt-test-env&j=bq:US:9802c6ea-f771-4d46-9da3-bf6f521bd1da&page=queryresults
19:00:08  Encountered an error:
Runtime Error
  Database Error in model dummydep (models2/dummydep.sql)
    Function not found: yes_no_to_boolean at [8:8]
```

**Acceptance criteria:** 
Instead of directly executing `compiled_node.compiled_code`, template it into a multi-statement query that includes the `sql_header` (similar approach to the one proposed for https://github.com/dbt-labs/dbt-core/issues/7390)

[CT-2440] `dbt show` throws `Database Error` for models with `sql_header` required for valid query 
If a model is configured with a `sql_header` that is necessary to successfully run the query, `dbt show` currently fails because the [`compiled_node.compiled_code` does not include the sql_header SQL](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/task/show.py#L21).

Reproduction case (run against BQ, but not a BQ-specific issue)

```
-- models/my_model.sql
{% call set_sql_header(config) %}
  CREATE TEMPORARY FUNCTION yes_no_to_boolean(answer STRING)
  RETURNS BOOLEAN AS (
    CASE
    WHEN LOWER(answer) = 'yes' THEN True
    WHEN LOWER(answer) = 'no' THEN False
    ELSE NULL
    END
  );
{%- endcall %}

select yes_no_to_boolean("yes") as column
```

```
dbt show --select my_model --project-dir
19:00:05  Found 1 model, 0 tests, 0 snapshots, 0 analyses, 551 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics, 0 groups
19:00:05  
19:00:06  Concurrency: 1 threads (target='dev')
19:00:06  
19:00:08  BigQuery adapter: https://console.cloud.google.com/bigquery?project=dbt-test-env&j=bq:US:9802c6ea-f771-4d46-9da3-bf6f521bd1da&page=queryresults
19:00:08  Encountered an error:
Runtime Error
  Database Error in model dummydep (models2/dummydep.sql)
    Function not found: yes_no_to_boolean at [8:8]
```

**Acceptance criteria:** 
Instead of directly executing `compiled_node.compiled_code`, template it into a multi-statement query that includes the `sql_header` (similar approach to the one proposed for https://github.com/dbt-labs/dbt-core/issues/7390)



Discussion:
BLG - 4/24 assigned to @aranke for further research
Doing some research, the SQL header does indeed run before the query, but is in a different scope.

So, all tests pass in PostgreSQL if I change [`models_second_model`](https://github.com/dbt-labs/dbt-core/blob/28e34125565d06e68e1ade016733cf3f4ebc3f7f/tests/functional/show/fixtures.py#L5) to:

```sql
{% call set_sql_header(config) %}
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
        BEGIN
                RETURN i + 1;
        END;
$$ LANGUAGE plpgsql;
{%- endcall %}
select
    sample_num as col_one,
    sample_bool as col_two,
    increment(41) as answer
from {{ ref('sample_model') }}
```

It seems like this might only be a problem in BigQuery, so I'm inclined to close this issue for now, please let me know if this is inaccurate.

cc @jtcohen6 
BLG - 4/24 assigned to @aranke for further research
Doing some research, the SQL header does indeed run before the query, but is in a different scope.

So, all tests pass in PostgreSQL if I change [`models_second_model`](https://github.com/dbt-labs/dbt-core/blob/28e34125565d06e68e1ade016733cf3f4ebc3f7f/tests/functional/show/fixtures.py#L5) to:

```sql
{% call set_sql_header(config) %}
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
        BEGIN
                RETURN i + 1;
        END;
$$ LANGUAGE plpgsql;
{%- endcall %}
select
    sample_num as col_one,
    sample_bool as col_two,
    increment(41) as answer
from {{ ref('sample_model') }}
```

It seems like this might only be a problem in BigQuery, so I'm inclined to close this issue for now, please let me know if this is inaccurate.

cc @jtcohen6 

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
@@ -23,6 +23,11 @@ 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
+            )
+
         adapter_response, execute_result = self.adapter.execute(
             compiled_node.compiled_code, fetch=True, limit=limit
         )


Test patch:
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
@@ -10,6 +10,14 @@
 from {{ ref('sample_model') }}
 """
 
+models__sql_header = """
+{% call set_sql_header(config) %}
+set session time zone 'Asia/Kolkata';
+{%- endcall %}
+select current_setting('timezone') as timezone
+"""
+
+
 schema_yml = """
 models:
   - name: sample_model
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
@@ -9,6 +9,7 @@
     models__second_model,
     models__ephemeral_model,
     schema_yml,
+    models__sql_header,
 )
 
 
@@ -19,6 +20,7 @@ def models(self):
             "sample_model.sql": models__sample_model,
             "second_model.sql": models__second_model,
             "ephemeral_model.sql": models__ephemeral_model,
+            "sql_header.sql": models__sql_header,
         }
 
     @pytest.fixture(scope="class")
@@ -105,6 +107,11 @@ def test_seed(self, project):
         (results, log_output) = run_dbt_and_capture(["show", "--select", "sample_seed"])
         assert "Previewing node 'sample_seed'" in log_output
 
+    def test_sql_header(self, project):
+        run_dbt(["build"])
+        (results, log_output) = run_dbt_and_capture(["show", "--select", "sql_header"])
+        assert "Asia/Kolkata" in log_output
+
 
 class TestShowModelVersions:
     @pytest.fixture(scope="class")