Repository: dbt-labs/dbt-core

Issue:
When dbt encounters two sources with the same name, it crashes with an exception
### Describe the bug
When dbt hits a duplicate source, it crashes with a bad error about `'UnpatchedSourceDefinition' object has no attribute 'source_name'`.

### Steps To Reproduce
1) Define a source and a table
2) Define the table a second time (either in a new source with the same name or a second table in the original source)
3) `dbt compile`.

```
version: 2
sources:
 - name: my_source
   tables:
    - name: foo
    - name: foo
```

### Expected behavior
I should get this error:
```
  dbt found two resources with the name "my_source_foo". Since these resources have the same name,
  dbt will be unable to find the correct resource when source("my_source", "foo") is used. To fix this,
  change the name of one of these resources:
  - source.minimal.my_source.foo (models/schema.yml)
  - source.minimal.my_source.foo (models/schema.yml)
```

This happens on any db, any python, dbt 0.17.0rc1


Discussion:

Resolving patch:
diff --git a/core/dbt/contracts/graph/parsed.py b/core/dbt/contracts/graph/parsed.py
--- a/core/dbt/contracts/graph/parsed.py
+++ b/core/dbt/contracts/graph/parsed.py
@@ -327,9 +327,15 @@ class UnpatchedSourceDefinition(UnparsedBaseNode, HasUniqueID, HasFqn):
     resource_type: NodeType = field(metadata={'restrict': [NodeType.Source]})
     patch_path: Optional[Path] = None
 
+    def get_full_source_name(self):
+        return f'{self.source.name}_{self.table.name}'
+
+    def get_source_representation(self):
+        return f'source("{self.source.name}", "{self.table.name}")'
+
     @property
     def name(self) -> str:
-        return '{0.name}_{1.name}'.format(self.source, self.table)
+        return self.get_full_source_name()
 
     @property
     def quote_columns(self) -> Optional[bool]:
@@ -391,6 +397,12 @@ class ParsedSourceDefinition(
     config: SourceConfig = field(default_factory=SourceConfig)
     patch_path: Optional[Path] = None
 
+    def get_full_source_name(self):
+        return f'{self.source_name}_{self.name}'
+
+    def get_source_representation(self):
+        return f'source("{self.source.name}", "{self.table.name}")'
+
     @property
     def is_refable(self):
         return False
diff --git a/core/dbt/exceptions.py b/core/dbt/exceptions.py
--- a/core/dbt/exceptions.py
+++ b/core/dbt/exceptions.py
@@ -724,7 +724,8 @@ def raise_duplicate_resource_name(node_1, node_2):
     if node_1.resource_type in NodeType.refable():
         get_func = 'ref("{}")'.format(duped_name)
     elif node_1.resource_type == NodeType.Source:
-        get_func = 'source("{}", "{}")'.format(node_1.source_name, duped_name)
+        duped_name = node_1.get_full_source_name()
+        get_func = node_1.get_source_representation()
     elif node_1.resource_type == NodeType.Documentation:
         get_func = 'doc("{}")'.format(duped_name)
     elif node_1.resource_type == NodeType.Test and 'schema' in node_1.tags:


Test patch:
diff --git a/test/integration/025_duplicate_model_test/models-source-dupes/schema.yml b/test/integration/025_duplicate_model_test/models-source-dupes/schema.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/025_duplicate_model_test/models-source-dupes/schema.yml
@@ -0,0 +1,6 @@
+version: 2
+sources:
+  - name: something
+    tables:
+     - name: dupe
+     - name: dupe
diff --git a/test/integration/025_duplicate_model_test/test_duplicate_source.py b/test/integration/025_duplicate_model_test/test_duplicate_source.py
new file mode 100644
--- /dev/null
+++ b/test/integration/025_duplicate_model_test/test_duplicate_source.py
@@ -0,0 +1,42 @@
+from dbt.exceptions import CompilationException
+from test.integration.base import DBTIntegrationTest, use_profile
+
+
+class TestDuplicateSourceEnabled(DBTIntegrationTest):
+
+    @property
+    def schema(self):
+        return "duplicate_model_025"
+
+    @property
+    def models(self):
+        return "models-source-dupes"
+
+    @property
+    def profile_config(self):
+        return {
+            "test": {
+                "outputs": {
+                    "dev": {
+                        "type": "postgres",
+                        "threads": 1,
+                        "host": self.database_host,
+                        "port": 5432,
+                        "user": "root",
+                        "pass": "password",
+                        "dbname": "dbt",
+                        "schema": self.unique_schema()
+                    },
+                },
+                "target": "dev"
+            }
+        }
+
+    @use_profile("postgres")
+    def test_postgres_duplicate_model_enabled(self):
+        message = "dbt found two resources with the name"
+        try:
+            self.run_dbt(["compile"])
+            self.assertTrue(False, "dbt did not throw for duplicate sources")
+        except CompilationException as e:
+            self.assertTrue(message in str(e), "dbt did not throw the correct error message")