Repository: dbt-labs/dbt-core

Issue:
[CT-1634] [Bug] Post-hook in dbt_project.yml using {{ this }} failing to insert table name
### 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

Adding a post-hook into the `dbt_project.yml` file does not appear to be resolving `{{ this }}` to a table reference. 


### Expected Behavior

I expect that the reference `{{ this }}` renders to the correct target.

### Steps To Reproduce

1. Set up new project with `dbt init`
2. Insert the following into the `dbt_profile.yml` file:
```
models:
  +post_hook: 
    - "ALTER TABLE {{ this }} ADD COLUMN updated_at TIMESTAMP DEFAULT now()"
  test:
    # Config indicated by + and applies to all files under models/example/
    example:
      +materialized: view
```

### Relevant log output

```shell
%: dbt run
21:19:32  Running with dbt=1.3.1
21:19:32  Unable to do partial parsing because a project config has changed
21:19:32  Found 2 models, 4 tests, 0 snapshots, 0 analyses, 289 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics
21:19:32  
21:19:33  Concurrency: 4 threads (target='dev')
21:19:33  
21:19:33  1 of 2 START sql table model test.my_first_dbt_model ........................... [RUN]
21:19:34  1 of 2 ERROR creating sql table model test.my_first_dbt_model .................. [ERROR in 0.51s]
21:19:34  2 of 2 SKIP relation test.my_second_dbt_model .................................. [SKIP]
21:19:34  
21:19:34  Finished running 1 table model, 1 view model in 0 hours 0 minutes and 2.19 seconds (2.19s).
21:19:34  
21:19:34  Completed with 1 error and 0 warnings:
21:19:34  
21:19:34  Database Error in model my_first_dbt_model (models/example/my_first_dbt_model.sql)
21:19:34    syntax error at or near "COLUMN"
21:19:34    LINE 3:         ALTER TABLE  ADD COLUMN updated_at TIMESTAMP DEFAULT...
21:19:34                                     ^
21:19:34    compiled Code at target/run/test/models/example/my_first_dbt_model.sql
21:19:34  
21:19:34  Done. PASS=0 WARN=0 ERROR=1 SKIP=1 TOTAL=2
```


### Environment

```markdown
- OS: Mac OSX
- Python: 3.10.7
- dbt: 1.3.1
- adapter: PostGres
```


### Which database adapter are you using with dbt?

postgres

### Additional Context

I have tried to use the dictionary approach outlined here: https://docs.getdbt.com/reference/resource-configs/pre-hook-post-hook#dbt_projectyml-use-a-dictionary, but that results in a different error (not included here). 

When I put the post hook directly into a config macro in my models, it works fine. I'm trying to avoid copy/pasting that code across all my models, if I can help it.

Thank you!


Discussion:
Thanks for reporting this issue @louisfern!

I was able to reproduce the same thing you are seeing -- namely, that model-level post-hooks in the `dbt_project.yml` file end up resolving `{{ this }}` to be an empty string rather than a table reference.

Based on the documentation and examples [here](https://docs.getdbt.com/reference/resource-configs/pre-hook-post-hook#apache-spark-analyze-tables-after-creation) and [here](https://github.com/dbt-labs/dbt-core/issues/4023#:~:text=You%20could%20also%20set%20that%20config%20in%20your%20project%20file%2C%20with%20quotes%20and%20curlies%3A), it sounds like this was expected to work!

<details>
<summary>Examples given in links above</summary>

Example from link 1
```sql
# dbt_project.yml

model:
  jaffle_shop: # this is the project name
    marts:
      finance:
        +post-hook:
          # this can be a list
          - "analyze table {{ this }} compute statistics for all columns"
          # or call a macro instead
          - "{{ analyze_table() }}"
```

Example from link 2
```sql
# dbt_project.yml

models:
  +post-hook: "delete from {{ this }} where id > 2"
```
</details>

### Workaround
Defining a macro worked for me though. Could you try the following and see if it works or not?

Create this macro in a file named `macros/add_column_updated_at.sql` (or whatever you prefer):
```sql
# macros/add_column_updated_at.sql

{% macro add_column_updated_at() %}
    ALTER TABLE {{ this }} ADD COLUMN updated_at TIMESTAMP DEFAULT now()
{% endmacro %}
```

Then update your `dbt_project.yml` like this:
```yaml
# dbt_project.yml

models:
  +post-hook:
    - "{{ add_column_updated_at() }}"
```
@dbeatty10 Thanks for the follow up on this! 

I tried your solution and I'm still hitting an issue. 

I added a file at `macros/add_column_updated_at.sql` with the following contents:

```
-- add_column_updated_at.sql

{% macro add_column_updated_at() %}
    ALTER TABLE {{ this }} ADD COLUMN updated_at TIMESTAMP DEFAULT now()
{% endmacro %}
```

I then added the `post-hook` line to my `dbt_project.yml` file like so: 

```
blah blah example text from dbt init
...
# In this example config, we tell dbt to build all models in the example/ directory
# as tables. These settings can be overridden in the individual model files
# using the `{{ config(...) }}` macro.
models:
  +post_hook:
    - "{{ add_column_updated_at() }}"
  test:
    # Config indicated by + and applies to all files under models/example/
    example:
      +materialized: view
```

Then I tried running dbt compile to check out the resolution of the macro and I'm getting the following output on the terminal: 

```
# dbt compile
16:38:14  Encountered an error:
Compilation Error
  Could not render {{ add_column_updated_at() }}: 'add_column_updated_at' is undefined
```

I've never made a custom macro before; hopefully this is operator error and you can suss out what I'm doing wrong. 

Thanks!
This should work if you define the config in `dbt_project.yml` as `post-hook` (hyphen) rather than `post_hook` (underscore):
```yml
# dbt_project.yml
models:
  +post-hook:  # notice the hyphen!
    - "ALTER TABLE {{ this }} ADD COLUMN updated_at TIMESTAMP DEFAULT now()"
    # or
    - "{{ add_column_updated_at() }}"
```

This is for a fairly silly reason (I think), where there's a special group of configs that are "late-rendered" (skipped during the first pass through `dbt_project.yml`):

https://github.com/dbt-labs/dbt-core/blob/4f5b9e686c7f8164de5a68d885bf2452722dc8f6/core/dbt/config/renderer.py#L161-L163

There's a chance that adding `pre_hook` + `post_hook` to the list above would make this _just work_, but I'm not positive. We also have some logic that identifies and renames them to the standard `pre-hook` and `post-hook`.

What should definitely work, in the meantime, is changing the code in your project from `post_hook` &rarr; `post-hook`.
Thanks @jtcohen6 for the tip, that resolves the `Could not render ` error and lets me get back to DRYing up my models.

Thanks for your help with this, @dbeatty10 and @jtcohen6 

Resolving patch:
diff --git a/core/dbt/config/renderer.py b/core/dbt/config/renderer.py
--- a/core/dbt/config/renderer.py
+++ b/core/dbt/config/renderer.py
@@ -159,7 +159,8 @@ def should_render_keypath(self, keypath: Keypath) -> bool:
         if first in {"seeds", "models", "snapshots", "tests"}:
             keypath_parts = {(k.lstrip("+ ") if isinstance(k, str) else k) for k in keypath}
             # model-level hooks
-            if "pre-hook" in keypath_parts or "post-hook" in keypath_parts:
+            late_rendered_hooks = {"pre-hook", "post-hook", "pre_hook", "post_hook"}
+            if keypath_parts.intersection(late_rendered_hooks):
                 return False
 
         return True


Test patch:
diff --git a/tests/functional/hooks/test_model_hooks.py b/tests/functional/hooks/test_model_hooks.py
--- a/tests/functional/hooks/test_model_hooks.py
+++ b/tests/functional/hooks/test_model_hooks.py
@@ -170,6 +170,29 @@ def test_pre_and_post_run_hooks(self, project, dbt_profile_target):
         self.check_hooks("end", project, dbt_profile_target["host"])
 
 
+class TestPrePostModelHooksUnderscores(TestPrePostModelHooks):
+    @pytest.fixture(scope="class")
+    def project_config_update(self):
+        return {
+            "models": {
+                "test": {
+                    "pre_hook": [
+                        # inside transaction (runs second)
+                        MODEL_PRE_HOOK,
+                        # outside transaction (runs first)
+                        {"sql": "vacuum {{ this.schema }}.on_model_hook", "transaction": False},
+                    ],
+                    "post_hook": [
+                        # outside transaction (runs second)
+                        {"sql": "vacuum {{ this.schema }}.on_model_hook", "transaction": False},
+                        # inside transaction (runs first)
+                        MODEL_POST_HOOK,
+                    ],
+                }
+            }
+        }
+
+
 class TestHookRefs(BaseTestPrePost):
     @pytest.fixture(scope="class")
     def project_config_update(self):