Repository: dbt-labs/dbt-core

Issue:
[CT-2510] [Bug] Raise an error if non-versioned model is defined with same name as existing versioned model
Set up:
```sql
-- models/my_model_v1.sql
select 1 as id
```
```yml
# models/whatever.yml
models:
  - name: my_model
    versions:
      - v: 1
```

```
$ dbt parse
12:48:12  Running with dbt=1.5.0
12:48:14  Performance info: target/perf_info.json
```
Now:
- create `models/my_model.sql` (no version suffix)
- do not add any new versions to the `versions` block in the yaml file

Re-parse with partial parsing, and everything looks fine:
```
$ dbt parse
12:48:29  Running with dbt=1.5.0
12:48:30  Performance info: target/perf_info.json
```
And now without partial parsing, I see an error (good! I should!):
```
$ dbt --no-partial-parse parse
12:48:43  Running with dbt=1.5.0
12:48:46  Encountered an error:
Compilation Error
  dbt found two models with the name "my_model".

  Since these resources have the same name, dbt will be unable to find the correct resource
  when looking for ref("my_model").

  To fix this, change the name of one of these resources:
  - model.my_dbt_project.my_model (models/my_model.sql)
  - model.my_dbt_project.my_model.v1 (models/my_model_v1.sql)
```


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
@@ -2187,6 +2187,26 @@ def get_message(self) -> str:
         return msg
 
 
+class DuplicateVersionedUnversionedError(ParsingError):
+    def __init__(self, versioned_node, unversioned_node):
+        self.versioned_node = versioned_node
+        self.unversioned_node = unversioned_node
+        super().__init__(msg=self.get_message())
+
+    def get_message(self) -> str:
+        msg = f"""
+dbt found versioned and unversioned models with the name "{self.versioned_node.name}".
+
+Since these resources have the same name, dbt will be unable to find the correct resource
+when looking for ref('{self.versioned_node.name}').
+
+To fix this, change the name of the unversioned resource
+{self.unversioned_node.unique_id} ({self.unversioned_node.original_file_path})
+or add the unversioned model to the versions in {self.versioned_node.patch_path}
+    """.strip()
+        return msg
+
+
 class PropertyYMLError(CompilationError):
     def __init__(self, path: str, issue: str):
         self.path = 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
@@ -1262,11 +1262,19 @@ def _check_resource_uniqueness(
     config: RuntimeConfig,
 ) -> None:
     alias_resources: Dict[str, ManifestNode] = {}
+    name_resources: Dict[str, Dict] = {}
 
     for resource, node in manifest.nodes.items():
         if not node.is_relational:
             continue
 
+        if node.package_name not in name_resources:
+            name_resources[node.package_name] = {"ver": {}, "unver": {}}
+        if node.is_versioned:
+            name_resources[node.package_name]["ver"][node.name] = node
+        else:
+            name_resources[node.package_name]["unver"][node.name] = node
+
         # 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)
@@ -1280,6 +1288,18 @@ def _check_resource_uniqueness(
 
         alias_resources[full_node_name] = node
 
+    for ver_unver_dict in name_resources.values():
+        versioned_names = ver_unver_dict["ver"].keys()
+        unversioned_names = ver_unver_dict["unver"].keys()
+        intersection_versioned = set(versioned_names).intersection(set(unversioned_names))
+        if intersection_versioned:
+            for name in intersection_versioned:
+                versioned_node = ver_unver_dict["ver"][name]
+                unversioned_node = ver_unver_dict["unver"][name]
+                raise dbt.exceptions.DuplicateVersionedUnversionedError(
+                    versioned_node, unversioned_node
+                )
+
 
 def _warn_for_unused_resource_config_paths(manifest: Manifest, config: RuntimeConfig) -> None:
     resource_fqns: Mapping[str, PathSet] = manifest.get_resource_fqns()


Test patch:
diff --git a/tests/functional/partial_parsing/test_partial_parsing.py b/tests/functional/partial_parsing/test_partial_parsing.py
--- a/tests/functional/partial_parsing/test_partial_parsing.py
+++ b/tests/functional/partial_parsing/test_partial_parsing.py
@@ -71,7 +71,7 @@
     groups_schema_yml_two_groups_private_orders_invalid_access,
 )
 
-from dbt.exceptions import CompilationError, ParsingError
+from dbt.exceptions import CompilationError, ParsingError, DuplicateVersionedUnversionedError
 from dbt.contracts.files import ParseFileType
 from dbt.contracts.results import TestStatus
 import re
@@ -358,6 +358,14 @@ def test_pp_versioned_models(self, project):
         write_file(model_two_sql, project.project_root, "models", "model_one_different.sql")
         results = run_dbt(["--partial-parse", "run"])
         assert len(results) == 3
+        manifest = get_manifest(project.project_root)
+        assert len(manifest.nodes) == 3
+        print(f"--- nodes: {manifest.nodes.keys()}")
+
+        # create a new model_one in model_one.sql and re-parse
+        write_file(model_one_sql, project.project_root, "models", "model_one.sql")
+        with pytest.raises(DuplicateVersionedUnversionedError):
+            run_dbt(["parse"])
 
 
 class TestSources: