Repository: dbt-labs/dbt-core

Issue:
Ambiguous alias exception should check `database` config
### Describe the bug
dbt's [`raise_ambiguous_alias`](https://github.com/fishtown-analytics/dbt/blob/dev/octavius-catto/core/dbt/exceptions.py#L711) exception only takes into account a node's `schema` and `alias` when searching for collisions. It should also take into account a node's custom `database`.

### Steps To Reproduce
1. Create two models with different file names and `database` configs, but the same `schema` and `alias` configs. E.g.:
```
{{
    config(
        materialized = 'table',
        database = 'analytics',
        schema = 'dbt_jcohen',
        alias = 'fct_sprints'
    )
}}
```
```
{{
    config(
        materialized = 'table',
        database = 'not_analytics',
        schema = 'dbt_jcohen',
        alias = 'fct_sprints'
    )
}}
```

2. Run `dbt compile`

```
Encountered an error:
Compilation Error
  dbt found two resources with the database representation "dbt_jcohen.fct_sprints".
  dbt cannot create two resources with identical database representations. To fix this,
  change the "schema" or "alias" configuration of one of these resources:
  - model.fishtown_internal_analytics.fct_sprintssss (models/marts/consulting/fct_sprintssss.sql)
  - model.fishtown_internal_analytics.fct_sprints (models/marts/consulting/fct_sprints.sql)
```

### Expected behavior

dbt's `raise_ambiguous_alias` method should take into account custom databases to determine if two models have identical database representations.

### System information
**Which database are you using dbt with?**
- [ ] postgres
- [ ] redshift
- [ ] bigquery
- [x] snowflake
- [ ] other (specify: ____________)

I have tested on Snowflake. I imagine it could be relevant on BigQuery as well.

**The output of `dbt --version`:**
```
installed version: 0.16.1
   latest version: 0.16.1

Up to date!
```


Discussion:

Resolving patch:
diff --git a/core/dbt/exceptions.py b/core/dbt/exceptions.py
--- a/core/dbt/exceptions.py
+++ b/core/dbt/exceptions.py
@@ -741,14 +741,15 @@ def raise_duplicate_resource_name(node_1, node_2):
             node_2.unique_id, node_2.original_file_path))
 
 
-def raise_ambiguous_alias(node_1, node_2):
-    duped_name = "{}.{}".format(node_1.schema, node_1.alias)
+def raise_ambiguous_alias(node_1, node_2, duped_name=None):
+    if duped_name is None:
+        duped_name = f"{node_1.database}.{node_1.schema}.{node_1.alias}"
 
     raise_compiler_error(
         'dbt found two resources with the database representation "{}".\ndbt '
         'cannot create two resources with identical database representations. '
-        'To fix this,\nchange the "schema" or "alias" configuration of one of '
-        'these resources:\n- {} ({})\n- {} ({})'.format(
+        'To fix this,\nchange the configuration of one of these resources:'
+        '\n- {} ({})\n- {} ({})'.format(
             duped_name,
             node_1.unique_id, node_1.original_file_path,
             node_2.unique_id, node_2.original_file_path))
diff --git a/core/dbt/parser/manifest.py b/core/dbt/parser/manifest.py
--- a/core/dbt/parser/manifest.py
+++ b/core/dbt/parser/manifest.py
@@ -9,6 +9,7 @@
 import dbt.flags
 
 from dbt import deprecations
+from dbt.adapters.factory import get_relation_class_by_name
 from dbt.helper_types import PathSet
 from dbt.include.global_project import PACKAGES
 from dbt.logger import GLOBAL_LOGGER as logger, DbtProcessState
@@ -367,7 +368,10 @@ def load_internal(cls, root_config: RuntimeConfig) -> Manifest:
             return loader.load_only_macros()
 
 
-def _check_resource_uniqueness(manifest: Manifest) -> None:
+def _check_resource_uniqueness(
+    manifest: Manifest,
+    config: RuntimeConfig,
+) -> None:
     names_resources: Dict[str, NonSourceNode] = {}
     alias_resources: Dict[str, NonSourceNode] = {}
 
@@ -378,7 +382,10 @@ def _check_resource_uniqueness(manifest: Manifest) -> None:
         assert not isinstance(node, ParsedSourceDefinition)
 
         name = node.name
-        alias = "{}.{}".format(node.schema, node.alias)
+        # the full node name is really defined by the adapter's relation
+        relation_cls = get_relation_class_by_name(config.credentials.type)
+        relation = relation_cls.create_from(config=config, node=node)
+        full_node_name = str(relation)
 
         existing_node = names_resources.get(name)
         if existing_node is not None:
@@ -386,14 +393,14 @@ def _check_resource_uniqueness(manifest: Manifest) -> None:
                 existing_node, node
             )
 
-        existing_alias = alias_resources.get(alias)
+        existing_alias = alias_resources.get(full_node_name)
         if existing_alias is not None:
             dbt.exceptions.raise_ambiguous_alias(
-                existing_alias, node
+                existing_alias, node, full_node_name
             )
 
         names_resources[name] = node
-        alias_resources[alias] = node
+        alias_resources[full_node_name] = node
 
 
 def _warn_for_unused_resource_config_paths(
@@ -405,7 +412,7 @@ def _warn_for_unused_resource_config_paths(
 
 
 def _check_manifest(manifest: Manifest, config: RuntimeConfig) -> None:
-    _check_resource_uniqueness(manifest)
+    _check_resource_uniqueness(manifest, config)
     _warn_for_unused_resource_config_paths(manifest, config)
 
 


Test patch:
diff --git a/test/integration/026_aliases_test/models-dupe-custom-database/README.md b/test/integration/026_aliases_test/models-dupe-custom-database/README.md
new file mode 100644
--- /dev/null
+++ b/test/integration/026_aliases_test/models-dupe-custom-database/README.md
@@ -0,0 +1,2 @@
+these should succeed, as both models have the same alias,
+but they are configured to be built in _different_ schemas
diff --git a/test/integration/026_aliases_test/models-dupe-custom-database/model_a.sql b/test/integration/026_aliases_test/models-dupe-custom-database/model_a.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/026_aliases_test/models-dupe-custom-database/model_a.sql
@@ -0,0 +1 @@
+select {{ string_literal(this.name) }} as tablename
diff --git a/test/integration/026_aliases_test/models-dupe-custom-database/model_b.sql b/test/integration/026_aliases_test/models-dupe-custom-database/model_b.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/026_aliases_test/models-dupe-custom-database/model_b.sql
@@ -0,0 +1 @@
+select {{ string_literal(this.name) }} as tablename
diff --git a/test/integration/026_aliases_test/models-dupe-custom-database/schema.yml b/test/integration/026_aliases_test/models-dupe-custom-database/schema.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/026_aliases_test/models-dupe-custom-database/schema.yml
@@ -0,0 +1,12 @@
+version: 2
+models:
+- name: model_a
+  tests:
+  - expect_value:
+      field: tablename
+      value: duped_alias
+- name: model_b
+  tests:
+  - expect_value:
+      field: tablename
+      value: duped_alias
diff --git a/test/integration/026_aliases_test/test_aliases.py b/test/integration/026_aliases_test/test_aliases.py
--- a/test/integration/026_aliases_test/test_aliases.py
+++ b/test/integration/026_aliases_test/test_aliases.py
@@ -93,3 +93,39 @@ def test__postgres_same_alias_succeeds_in_different_schemas(self):
 
         # Make extra sure the tests ran
         self.assertTrue(len(res) > 0)
+
+
+class TestSameAliasDifferentDatabases(DBTIntegrationTest):
+    setup_alternate_db = True
+
+    @property
+    def schema(self):
+        return "aliases_026"
+
+    @property
+    def models(self):
+        return "models-dupe-custom-database"
+
+    @property
+    def project_config(self):
+        return {
+            'config-version': 2,
+            "macro-paths": ['macros'],
+            'models': {
+                'test': {
+                    'alias': 'duped_alias',
+                    'model_b': {
+                        'database': self.alternative_database,
+                    },
+                },
+            }
+        }
+
+    @use_profile('bigquery')
+    def test__bigquery_same_alias_succeeds_in_different_schemas(self):
+        results = self.run_dbt(['run'])
+        self.assertEqual(len(results), 2)
+        res = self.run_dbt(['test'])
+
+        # Make extra sure the tests ran
+        self.assertTrue(len(res) > 0)