Repository: dbt-labs/dbt-core

Issue:
yaml quoting not working with NativeEnvironment jinja evaluator
### Describe the bug
dbt's NativeEnvironment introduced a functional change to how Jinja strings are evaluated. In dbt v0.17.0, a schema test can no longer be configured with a quoted column name.

### Steps To Reproduce
```
# schema.yml

version: 2
models:
  - name: debug

    columns:
      - name: MyId
        quote: true
        tests:
          - relationships:
              to: ref('debug')
              field: '"MyId"'
```

```
-- models/debug.sql

select 1 as "MyId"
```

**Results:**
```
Database Error in test relationships_debug__MyId____MyId___ref_debug_ (models/schema.yml)
  column "myid" does not exist
  LINE 12:     select MyId as id from "analytics"."test_schema"."debug"
                      ^
  HINT:  Perhaps you meant to reference the column "debug.MyId" or the column "child.id".
  compiled SQL at target/compiled/neondwh/models/schema.yml/schema_test/relationships_debug__MyId____MyId___ref_debug_.sql
```

### Expected behavior
I would expect the yaml/jinja string `'"MyId"'` to be resolved to the string `"MyId"`, not `MyId`.

**The output of `dbt --version`:**
```
dbt v0.17.0
```

**The operating system you're using:** macOS

**The output of `python --version`:** 3.7.7

### Additional context
Using `Jinja2==2.11.2`


Discussion:
Is this a dupe of https://github.com/fishtown-analytics/dbt/issues/2468 ?
I don't think so -- the issue here is that strings like `'"my string"'` are being stripped of double quotes, right?
As I understand it, a resolution to #2468 could offer a resolution here too, if `debug.MyId` were configured with `quote: true`.  But the more straightforward issue is around the ability to pass quotes as part of a string value through the native env, which I think more closely relates to #2531.

Cringe-y, but this does work:
```yml
- relationships:
    to: ref('my_first_dbt_model')
    field: '''"MyId"'''
```
yeah - I believe that this is a yaml/jinja parsing/rendering issue. If that's the case, we should fix this for 0.17.1. If not, then we can investigate other options. My instinct here is that this isn't directly related to the `relationships` test, but I have not yet verified that this is the case.
Well I'm not sure how we can handle that without customizing the yaml/jinja parsing stuff further. Yaml eats one level of quotes, jinja eats another. Neither part really "knows" about quoting. I think the best fix is to honor the `quote` parameter and tell people to use that.
These configs aren't typed as database identifiers -- I don't think the `quote` config is a good general solution to this problem. We need to provide a way for users to supply quoted strings to yaml configs.

Didn't we take specific action to preserve these quotes? I see the `_requote_result` method in `clients/jinja.py` but it doesn't appear to me that we're calling it anywhere. I don't recall intentionally ditching that functionality - was removing the call to this function intentional?
It was removed as part of #2395 because (I thought) we were successfully avoiding calling the renderer on column names. We can put it back, I guess.
Ok, thanks for the info. I just don't like the idea of dbt needing to be overly smart about where/when it chomps off wrapping quotes. The column name change in #2395 is a good one, but I'd hesitate to add similar logic for the `relationships` specifically test given that:
1. Any user could supply their own schema tests which won't benefit from this logic
2. This behavior should ideally be explicit / consistent for other configs too

Do you recall if there were any specific drawbacks related to the addition of `_requote_result`? From what I understand, I think it could be a good idea to start calling that method again, but my memory of removing it in the first place is pretty hazy! 
It was just another layer of difficult-to-predict behavior. It had some weird potential special-case behavior where we'd potentially requote things that shouldn't be requoted. I think something like `"{{ a }}" ~ "{{ b }}"` would get requoted if `len(a+b)==19`, but not otherwise (may require some fiddling around with curly braces to reproduce, but you get the idea).

Resolving patch:
diff --git a/core/dbt/clients/jinja.py b/core/dbt/clients/jinja.py
--- a/core/dbt/clients/jinja.py
+++ b/core/dbt/clients/jinja.py
@@ -133,6 +133,10 @@ def quoted_native_concat(nodes):
     except (ValueError, SyntaxError, MemoryError):
         return raw
 
+    # if it was a str and it still is a str, return it as-is.
+    if isinstance(result, str):
+        result = raw
+
     return result
 
 


Test patch:
diff --git a/test/unit/test_jinja.py b/test/unit/test_jinja.py
--- a/test/unit/test_jinja.py
+++ b/test/unit/test_jinja.py
@@ -1,4 +1,6 @@
+import pytest
 import unittest
+import yaml
 
 from dbt.clients.jinja import get_rendered
 from dbt.clients.jinja import get_template
@@ -413,3 +415,63 @@ def test_if_endfor_newlines(self):
 '''
 
 
+native_expected_behaviors = [
+    # strings
+    ('''foo: bar''', 'bar'),
+    ('''foo: "bar"''', 'bar'),
+    ('''foo: "'bar'"''', "'bar'"),
+    ("""foo: '"bar"'""", '"bar"'),
+    # ints
+    ('''foo: 1''', 1),
+    ('''foo: "1"''', 1),
+    ('''foo: "'1'"''', "'1'"),
+    ('''foo: "{{ 1 }}"''', 1),
+    ('''foo: "{{ '1' }}"''', 1),
+    ('''foo: "'{{ 1 }}'"''', "'1'"),
+    ('''foo: "'{{ '1' }}'"''', "'1'"),
+    ('''foo: "{{ 1 | as_text }}"''', '1'),
+    ('''foo: "{{ '1' | as_text }}"''', '1'),
+    # booleans.
+    # Note the discrepancy with true vs True: `true` is recognized by jinja but
+    # not literal_eval, but `True` is recognized by ast.literal_eval.
+    # For extra fun, yaml recognizes both.
+    ('''foo: "{{ true }}"''', True),
+    ('''foo: "{{ 'true' }}"''', 'true'),
+    ('''foo: "'{{ true }}'"''', "'True'"),
+    ('''foo: "{{ true | as_text }}"''', "True"),  # true -> boolean True -> text -> str(True) -> 'True'
+    ('''foo: "{{ 'true' | as_text }}"''', "true"),  # 'true' -> string 'true' -> text -> str('true') -> 'true'
+    ('''foo: "{{ True }}"''', True),
+    ('''foo: "{{ 'True' }}"''', True),
+    ('''foo: "'{{ True }}'"''', "'True'"),
+    ('''foo: "{{ True | as_text }}"''', "True"),  # True -> string 'True' -> text -> str('True') -> 'True'
+    ('''foo: "{{ 'True' | as_text }}"''', "True"),  # 'True' -> string 'True' -> text -> str('True') -> 'True'
+    ('''foo: yes''', True),  # yaml turns 'yes' into a boolean true
+    ('''foo: "yes"''', "yes"),
+    # concatenation
+    ('''foo: "{{ a_int + 100 }}"''', 200),
+    ('''foo: "{{ a_str ~ 100 }}"''', 100100),
+    ('''foo: "{{ a_int ~ 100 }}"''', 100100),
+    ('''foo: "{{ a_str }}{{ a_str }}"''', 100100),
+    ('''foo: "{{ a_int }}{{ a_int }}"''', 100100),
+    ('''foo: "'{{ a_int }}{{ a_int }}'"''', "'100100'"),
+
+]
+
+
+def expected_id(arg):
+    if isinstance(arg, list):
+        return '_'.join(arg)
+
+
+@pytest.mark.parametrize(
+    'inputvalue,expected', native_expected_behaviors, ids=expected_id
+)
+def test_native_rendering(inputvalue, expected):
+    # this test is pretty useless without preprocessing things in yaml.
+    value = yaml.safe_load(inputvalue)['foo']
+    ctx = {
+        'a_str': '100',
+        'a_int': 100,
+        'b_str': 'hello'
+    }
+    assert get_rendered(value, ctx, native=True) == expected