Repository: dbt-labs/dbt-core

Issue:
[CT-3210] [Bug] Error using `dbt list --select` when there is a cross-project model that is `version=0` in the parent project
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

When you attempt to reference a model version 0, you get a stack trace error.



### Expected Behavior

We should allow you to set model version to be 0.

### Steps To Reproduce

1. On parent/hub project, add a versioned model with `v: 0`
2. On the child/spoke project, attempt to reference that versioned model in a model:
  `select * from {{ ref('example_hub', 'my_second_dbt_model', v=0) }}`
3. run `dbt list --select anything`

Outstanding question - is this only affecting cross-project refs? Or all refs to a model with `v: 0`?

### Relevant log output

_No response_

### Environment

```markdown
- OS:
- Python:
- dbt:
```


### Which database adapter are you using with dbt?

_No response_

### Additional Context

_No response_
[CT-3210] [Bug] Error using `dbt list --select` when there is a cross-project model that is `version=0` in the parent project
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

When you attempt to reference a model version 0, you get a stack trace error.



### Expected Behavior

We should allow you to set model version to be 0.

### Steps To Reproduce

1. On parent/hub project, add a versioned model with `v: 0`
2. On the child/spoke project, attempt to reference that versioned model in a model:
  `select * from {{ ref('example_hub', 'my_second_dbt_model', v=0) }}`
3. run `dbt list --select anything`

Outstanding question - is this only affecting cross-project refs? Or all refs to a model with `v: 0`?

### Relevant log output

_No response_

### Environment

```markdown
- OS:
- Python:
- dbt:
```


### Which database adapter are you using with dbt?

_No response_

### Additional Context

_No response_


Discussion:
Just a little bit more context-- not every `dbt list` will cause this error-- there has to be a selector in the command, as the error only happens if it gets to [this check](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/graph/selector_methods.py#L229), and an `*` selector will return True from the conditional before it. Took us a bit to figure that one out 🙂 
I was able to reproduce this error, but it was not restricted to models with version == 0, so I'm going to update the issue title. It _was_ restricted to versioned models within the parent project though.

Here's the line of code that ultimately threw the error:
https://github.com/dbt-labs/dbt-core/blob/efa6339e1804e07f7f47aebccb46492fd897f7f5/core/dbt/graph/selector_methods.py#L66

### Reprex

#### Parent project

`dbt_project.yml `
```yaml
name: "my_parent_project"
...
```

`models/dim_customers.sql`
```sql
select 1 as id
```

`models/_models.yml`
```yaml
models:
  - name: dim_customers
    access: public
    versions:
      - v: 1
```

#### Child project

`models/child_dim_customers.sql`
```sql
select * from {{ ref('my_parent_project', 'dim_customers') }}
```

Within the child project, try to do _any_ selection with `dbt list`:
```
dbt list -s anything
```

```
23:02:42  Encountered an error:
list index out of range
```

<details>
<summary>Stacktrace</summary>

```
23:02:42  Encountered an error:
list index out of range
23:02:42  Traceback (most recent call last):
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 87, in wrapper
    result, success = func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 72, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 143, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 172, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 219, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 259, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/main.py", line 506, in list
    results = task.run()
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 139, in run
    return self.output_results(generator())
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 143, in output_results
    for result in results:
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 83, in generate_selectors
    for node in self._iterate_selected_nodes():
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 61, in _iterate_selected_nodes
    nodes = sorted(selector.get_selected(spec))
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 303, in get_selected
    selected_nodes, indirect_only = self.select_nodes(spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 158, in select_nodes
    direct_nodes, indirect_nodes = self.select_nodes_recursively(spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in select_nodes_recursively
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in <listcomp>
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in select_nodes_recursively
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in <listcomp>
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in select_nodes_recursively
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in <listcomp>
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 128, in select_nodes_recursively
    direct_nodes, indirect_nodes = self.get_nodes_from_criteria(spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 82, in get_nodes_from_criteria
    collected = self.select_included(nodes, spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 68, in select_included
    return set(method.search(included_nodes, spec.value))
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector_methods.py", line 215, in search
    if self.node_is_match(selector, real_node.fqn, real_node.is_versioned):
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector_methods.py", line 203, in node_is_match
    elif is_selected_node(unscoped_fqn, qualified_name, is_versioned):
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector_methods.py", line 62, in is_selected_node
    if fqn[-2] == node_selector:
IndexError: list index out of range
```

</details>

Just a little bit more context-- not every `dbt list` will cause this error-- there has to be a selector in the command, as the error only happens if it gets to [this check](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/graph/selector_methods.py#L229), and an `*` selector will return True from the conditional before it. Took us a bit to figure that one out 🙂 
I was able to reproduce this error, but it was not restricted to models with version == 0, so I'm going to update the issue title. It _was_ restricted to versioned models within the parent project though.

Here's the line of code that ultimately threw the error:
https://github.com/dbt-labs/dbt-core/blob/efa6339e1804e07f7f47aebccb46492fd897f7f5/core/dbt/graph/selector_methods.py#L66

### Reprex

#### Parent project

`dbt_project.yml `
```yaml
name: "my_parent_project"
...
```

`models/dim_customers.sql`
```sql
select 1 as id
```

`models/_models.yml`
```yaml
models:
  - name: dim_customers
    access: public
    versions:
      - v: 1
```

#### Child project

`models/child_dim_customers.sql`
```sql
select * from {{ ref('my_parent_project', 'dim_customers') }}
```

Within the child project, try to do _any_ selection with `dbt list`:
```
dbt list -s anything
```

```
23:02:42  Encountered an error:
list index out of range
```

<details>
<summary>Stacktrace</summary>

```
23:02:42  Encountered an error:
list index out of range
23:02:42  Traceback (most recent call last):
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 87, in wrapper
    result, success = func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 72, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 143, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 172, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 219, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/requires.py", line 259, in wrapper
    return func(*args, **kwargs)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/cli/main.py", line 506, in list
    results = task.run()
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 139, in run
    return self.output_results(generator())
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 143, in output_results
    for result in results:
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 83, in generate_selectors
    for node in self._iterate_selected_nodes():
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/task/list.py", line 61, in _iterate_selected_nodes
    nodes = sorted(selector.get_selected(spec))
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 303, in get_selected
    selected_nodes, indirect_only = self.select_nodes(spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 158, in select_nodes
    direct_nodes, indirect_nodes = self.select_nodes_recursively(spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in select_nodes_recursively
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in <listcomp>
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in select_nodes_recursively
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in <listcomp>
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in select_nodes_recursively
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 130, in <listcomp>
    bundles = [self.select_nodes_recursively(component) for component in spec]
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 128, in select_nodes_recursively
    direct_nodes, indirect_nodes = self.get_nodes_from_criteria(spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 82, in get_nodes_from_criteria
    collected = self.select_included(nodes, spec)
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector.py", line 68, in select_included
    return set(method.search(included_nodes, spec.value))
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector_methods.py", line 215, in search
    if self.node_is_match(selector, real_node.fqn, real_node.is_versioned):
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector_methods.py", line 203, in node_is_match
    elif is_selected_node(unscoped_fqn, qualified_name, is_versioned):
  File "/Users/dbeatty/projects/environments/postgres_1.6/lib/python3.8/site-packages/dbt/graph/selector_methods.py", line 62, in is_selected_node
    if fqn[-2] == node_selector:
IndexError: list index out of range
```

</details>


Resolving patch:
diff --git a/core/dbt/contracts/graph/node_args.py b/core/dbt/contracts/graph/node_args.py
--- a/core/dbt/contracts/graph/node_args.py
+++ b/core/dbt/contracts/graph/node_args.py
@@ -33,7 +33,8 @@ def unique_id(self) -> str:
     @property
     def fqn(self) -> List[str]:
         fqn = [self.package_name, self.name]
-        if self.version:
+        # Test for None explicitly because version can be 0
+        if self.version is not None:
             fqn.append(f"v{self.version}")
 
         return fqn


Test patch:
diff --git a/tests/unit/test_contracts_graph_node_args.py b/tests/unit/test_contracts_graph_node_args.py
--- a/tests/unit/test_contracts_graph_node_args.py
+++ b/tests/unit/test_contracts_graph_node_args.py
@@ -14,7 +14,7 @@ def test_model_node_args_unique_id_with_version(self) -> None:
             package_name="package",
             identifier="identifier",
             schema="schema",
-            version="1",
+            version=1,
         )
         assert model_node_args.unique_id == "model.package.name.v1"
 
@@ -33,6 +33,16 @@ def test_model_node_args_fqn_with_version(self) -> None:
             package_name="package",
             identifier="identifier",
             schema="schema",
-            version="1",
+            version=1,
         )
         assert model_node_args.fqn == ["package", "name", "v1"]
+
+    def test_model_node_args_fqn_with_version_zero(self) -> None:
+        model_node_args = ModelNodeArgs(
+            name="name",
+            package_name="package",
+            identifier="identifier",
+            schema="schema",
+            version=0,
+        )
+        assert model_node_args.fqn == ["package", "name", "v0"]