Repository: dbt-labs/dbt-core

Issue:
[Bug] config.get(config.field, default_value) not working properly?
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

for the code below in adapters, 
```
{%- set enable_refresh = config.get('enable_refresh', false) -%}
```
When the field `enable_refresh` is not set by model config, a default value `false` is not used by `config.get()`.
Instead, enable_refresh is set to `none`.



### Expected Behavior


for the code below in adapters, 
```
{%- set enable_refresh = config.get('enable_refresh', false) -%}
```
When the field `enable_refresh` is not set by model config, a default value `false` shall be used. `enable_refresh` shall be set to `false.

Interestingly, if I use the code below, it works as expected.
```
{%- set enable_refresh = config.get('enable_refresh', default=false) -%}
```


### Steps To Reproduce

Background
-- 


I came across this unexpected behavior when I tried to upgrade BQ `materialized_view` in [dbt-labs-experimental-features ](https://github.com/fishtown-analytics/dbt-labs-experimental-features/blob/master/materialized-views/macros/bigquery/adapters.sql) to work with dbt 0.21.0

If I have a model materialized_view_A.

Not setting the `enable_refresh` field in the model config.
```
{{
  config(
    materialized = "materialized_view",
    partition_by = {
      "field" : "TIMESTAMP_TRUNC(stamp_hourly, DAY)",
      "data_type" : "date"
    },
  )
}}
```
in `bigquery__create_materialized_view_as` macro, 
```
    {%- set enable_refresh = config.get('enable_refresh', false) -%}
```
if you log the value of `enable_refresh`, you will see it's set to `none`, which is unexpected.

Now, if you change the code to 
```
    {%- set enable_refresh = config.get('enable_refresh', default=false) -%}
```
if you log the value of `enable_refresh`, you will see it's set to `false`, which is expected.

### Relevant log output

_No response_

### Environment

```markdown
- OS: MacOS
- Python: 3.7
- dbt: 0.21.0
```


### What database are you using dbt with?

bigquery

### Additional Context

_No response_


Discussion:
@hui-zheng You're right! This is a duplicate of a much older issue, one we closed a while ago: https://github.com/dbt-labs/dbt-core/issues/2441.

@gshank It looks like you resolved that issue after v0.20, and I agree we got 90% of the way there, by making `config` behave much more like a dict, and `config.get()` a lot more like `dict.get()`. The subtle difference, raised again in this issue, is that you can provide the default value as the second positional argument for `dict.get()`. By contrast, `default` is still not the second positional argument for our `config.get()`—it's the third, with `validator` coming in second:

https://github.com/dbt-labs/dbt-core/blob/0722922c034b48c21c8fca8937c2f6b08688e8cd/core/dbt/context/providers.py#L328-L329

https://github.com/dbt-labs/dbt-core/blob/0722922c034b48c21c8fca8937c2f6b08688e8cd/core/dbt/context/providers.py#L372-L373

I think the resolution to this issue would be as simple as switching the argument order:
```python
    def get(self, name, default=None, validator=None):
        return ''
```
```python
    def get(self, name, default=None, validator=None):
        to_return = self._lookup(name, default)
```

That way, `config.get('arg1', 'arg2')` will be the same as `config.get('arg1', default = 'arg2')`.

This would be a big breaking change. v1 is absolutely the right time to do it; v1 _after_ we've already put out the first release candidate, not as much. Reasons I'm still open to this:
- We're planning a longer release candidacy, and we're planning to put out a few more RCs before the final
- Setting `default` is _much_ more common than setting `validator`; 100x more common, if I had to guess. (The only place I can think where we use the `validator` arg is [here in a `dbt-spark` macro](https://github.com/dbt-labs/dbt-spark/blob/e239dfb4826b7fad258f655b01f34cc300c6f531/dbt/include/spark/macros/adapters.sql#L2), and even there we use it as a keyword argument, since it's a pretty uncommon thing.)

So, what do we think of making this change?

--- 
@hui-zheng As an unrelated point: Where did you see `enable_refresh`? I don't see that code anywhere in our adapter plugins; I think it's in the ["experimental feature" for materialized views](https://github.com/dbt-labs/dbt-labs-experimental-features/blob/master/materialized-views/macros/bigquery/adapters.sql#L13).
I think the 'get' in the providers is not where config.get() ought to be... that should be the get def in BaseConfig. Let me play with this a bit.
The python dictionary get method only returns the default if the key doesn't exist. So if 'enable_refresh' is set to None in the config, that's what it will return, without regard to the default. I'm thinking that this should work how it does in Python, which it does right now.
@gshank If that's the case, I'd expect:
```sql
-- models/any_model.sql
select {{ config.get('made_up_nonexistent_key', 'default_value') }} as col_value
```
To compile to:
```sql
select 'default_value' as col_value
```
Instead of (current):
```sql
select None as col_value
```
It works in Python. I think Jinja does something to the arguments.
You're right, it's the 'get' method in the RuntimeConfigObject. I didn't realize that we were wrapping that there. What is the 'validator' used for? 

Resolving patch:
diff --git a/core/dbt/context/providers.py b/core/dbt/context/providers.py
--- a/core/dbt/context/providers.py
+++ b/core/dbt/context/providers.py
@@ -325,7 +325,7 @@ def set(self, name, value):
     def require(self, name, validator=None):
         return ''
 
-    def get(self, name, validator=None, default=None):
+    def get(self, name, default=None, validator=None):
         return ''
 
     def persist_relation_docs(self) -> bool:
@@ -369,7 +369,7 @@ def require(self, name, validator=None):
 
         return to_return
 
-    def get(self, name, validator=None, default=None):
+    def get(self, name, default=None, validator=None):
         to_return = self._lookup(name, default)
 
         if validator is not None and default is not None:


Test patch:
diff --git a/test/integration/039_config_test/models-get/any_model.sql b/test/integration/039_config_test/models-get/any_model.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/039_config_test/models-get/any_model.sql
@@ -0,0 +1,2 @@
+-- models/any_model.sql
+select {{ config.get('made_up_nonexistent_key', 'default_value') }} as col_value
diff --git a/test/integration/039_config_test/test_configs.py b/test/integration/039_config_test/test_configs.py
--- a/test/integration/039_config_test/test_configs.py
+++ b/test/integration/039_config_test/test_configs.py
@@ -296,3 +296,23 @@ def test_postgres_configuring_individual_tests(self):
         self.assertEqual(len(results), 2)
         self.assertEqual(results[0].status, 'fail')
         self.assertEqual(results[1].status, 'fail')
+
+
+
+class TestConfigGetDefault(DBTIntegrationTest):
+    @property
+    def schema(self):
+        return "config_039"
+
+    @property
+    def models(self):
+        return "models-get"
+
+    @use_profile('postgres')
+    def test_postgres_config_with_get_default(self):
+        # This test runs a model with a config.get(key, default)
+        # The default value is 'default_value' and causes an error
+        results = self.run_dbt(['run'], expect_pass=False)
+        self.assertEqual(len(results), 1)
+        self.assertEqual(str(results[0].status), 'error')
+        self.assertIn('column "default_value" does not exist', results[0].message)