Repository: dbt-labs/dbt-core
Pull request: Added warning to nodes selector if nothing was matched
resolves #2115 

### Description

Added warning (or error when `--warn-error` provided) when resource selector fails due to lack of resources for example when the incorrect model name was provided.

### Checklist
 - [x] I have signed the [CLA](https://docs.getdbt.com/docs/contributor-license-agreements)
 - [x] I have run this code in development and it appears to resolve the stated issue
 - [x] This PR includes tests, or tests are not required/relevant for this PR
 - [x] I have updated the `CHANGELOG.md` and added information about my change to the "dbt next" section.


Problem statement:
Warning should be surfaced if any part of the `--models` specification doesn't exist
### Describe the bug
A clear and concise description of what the bug is. What command did you run? What happened?

Assume `model_a` exists and `model_b` does not exist in a given dbt project.

If you run `dbt run --models model_b` you get an error: `WARNING: Nothing to do...`.

If you run `dbt run --models model_a model_b` you don't get any error since one of the models exists.

It seems like it would make sense to surface an error like `WARNING: Model specification matching model_b not found` or something like that

### Steps To Reproduce
In as much detail as possible, please provide steps to reproduce the issue. Sample data that triggers the issue, example model code, etc is all very helpful here.

See above

### Expected behavior
A clear and concise description of what you expected to happen.

See above

### Screenshots and log output
If applicable, add screenshots or log output to help explain your problem.

See above

### System information
**Which database are you using dbt with?**
- [ ] postgres
- [x] redshift
- [ ] bigquery
- [ ] snowflake
- [ ] other (specify: ____________)


**The output of `dbt --version`:**
```
Running with dbt=0.15.0
```

**The operating system you're using:**

```
OSX
```

**The output of `python --version`:**

```
Python 3.7.4
```

### Additional context
Add any other context about the problem here.

Definitely not an urgent problem, but figure it's a good one to mention

Hints from issue discussion:
Thanks for the report @kingfink - this is a good idea! We should make this a highly visible warning that can be raised to an error using the `--warn-error` flag 
@drewbanin seems like this is somewhat related to https://github.com/fishtown-analytics/dbt/issues/1765?
ha! Thanks @jeremyyeo - I was looking for that one! Just closed #1765 in favor of this issue. Appreciate you digging for it :)

Commit message: Merge branch 'dev/octavius-catto' into warn-nothing-selected

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d1dec6671f..e4f723a04bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,16 @@
 ## dbt 0.17.0 (Release TBD)
 
 ### Features
+- Added warning to nodes selector if nothing was matched ([#2115](https://github.com/fishtown-analytics/dbt/issues/2115), [#2343](https://github.com/fishtown-analytics/dbt/pull/2343))
 - Suport column descriptions for BigQuery models ([#2335](https://github.com/fishtown-analytics/dbt/issues/2335), [#2402](https://github.com/fishtown-analytics/dbt/pull/2402))
 
 ### Fixes
 - When tracking is disabled due to errors, do not reset the invocation ID ([#2398](https://github.com/fishtown-analytics/dbt/issues/2398), [#2400](https://github.com/fishtown-analytics/dbt/pull/2400))
 - Fix for logic error in compilation errors for duplicate data test names ([#2406](https://github.com/fishtown-analytics/dbt/issues/2406), [#2407](https://github.com/fishtown-analytics/dbt/pull/2407))
 
+Contributors:
+- [@raalsky](https://github.com/Raalsky) ([#2343](https://github.com/fishtown-analytics/dbt/pull/2343))
+
 ## dbt 0.17.0b1 (May 5, 2020)
 
 ### Breaking changes
diff --git a/core/dbt/graph/selector.py b/core/dbt/graph/selector.py
index 4c8e21d4183..fdf8adedcfe 100644
--- a/core/dbt/graph/selector.py
+++ b/core/dbt/graph/selector.py
@@ -9,7 +9,7 @@
 from dbt.logger import GLOBAL_LOGGER as logger
 from dbt.utils import coalesce
 from dbt.node_types import NodeType
-import dbt.exceptions
+from dbt.exceptions import RuntimeException, InternalException, warn_or_error
 
 SELECTOR_PARENTS = '+'
 SELECTOR_CHILDREN = '+'
@@ -50,7 +50,7 @@ def __init__(self, node_spec: str):
             node_spec = node_spec[:-1]
 
         if self.select_children and self.select_childrens_parents:
-            raise dbt.exceptions.RuntimeException(
+            raise RuntimeException(
                 'Invalid node spec {} - "@" prefix and "+" suffix are '
                 'incompatible'.format(self.raw)
             )
@@ -79,6 +79,14 @@ def __str__(self):
         return self._value_
 
 
+def alert_non_existence(raw_spec, nodes):
+    if len(nodes) == 0:
+        warn_or_error(
+            f"The selector '{str(raw_spec)}' does not match any nodes and will"
+            f" be ignored"
+        )
+
+
 def split_specs(node_specs: Iterable[str]):
     specs: Set[str] = set()
     for spec in node_specs:
@@ -88,8 +96,8 @@ def split_specs(node_specs: Iterable[str]):
     return specs
 
 
-def get_package_names(graph):
-    return set([node.split(".")[1] for node in graph.nodes()])
+def get_package_names(nodes):
+    return set([node.split(".")[1] for node in nodes])
 
 
 def is_selected_node(real_node, node_selector):
@@ -190,7 +198,7 @@ def search(self, included_nodes, selector):
         :param str selector: The selector or node name
         """
         qualified_name = selector.split(".")
-        package_names = {node.split(".")[1] for node in included_nodes}
+        package_names = get_package_names(included_nodes)
         for node, real_node in self.parsed_nodes(included_nodes):
             if _node_is_match(qualified_name, package_names, real_node.fqn):
                 yield node
@@ -228,7 +236,7 @@ def search(self, included_nodes, selector):
                 '`${{source_name}}.${{target_name}}`, or '
                 '`${{package_name}}.${{source_name}}.${{target_name}}'
             ).format(selector)
-            raise dbt.exceptions.RuntimeException(msg)
+            raise RuntimeException(msg)
 
         for node, real_node in self.source_nodes(included_nodes):
             if target_package not in (real_node.package_name, SELECTOR_GLOB):
@@ -380,18 +388,47 @@ def get_nodes_from_spec(self, graph, spec):
 
         return collected
 
-    def select_nodes(self, graph, raw_include_specs, raw_exclude_specs):
-        selected_nodes: Set[str] = set()
+    def get_nodes_from_multiple_specs(
+            self,
+            graph,
+            specs,
+            nodes=None,
+            check_existence=False,
+            exclude=False
+    ):
+        selected_nodes: Set[str] = coalesce(nodes, set())
+        operator = set.difference_update if exclude else set.update
 
-        for raw_spec in split_specs(raw_include_specs):
+        for raw_spec in split_specs(specs):
             spec = SelectionCriteria(raw_spec)
-            included_nodes = self.get_nodes_from_spec(graph, spec)
-            selected_nodes.update(included_nodes)
+            nodes = self.get_nodes_from_spec(graph, spec)
 
-        for raw_spec in split_specs(raw_exclude_specs):
-            spec = SelectionCriteria(raw_spec)
-            excluded_nodes = self.get_nodes_from_spec(graph, spec)
-            selected_nodes.difference_update(excluded_nodes)
+            if check_existence:
+                alert_non_existence(raw_spec, nodes)
+
+            operator(selected_nodes, nodes)
+
+        return selected_nodes
+
+    def select_nodes(self, graph, raw_include_specs, raw_exclude_specs):
+        raw_exclude_specs = coalesce(raw_exclude_specs, [])
+        check_existance = True
+
+        if not raw_include_specs:
+            check_existance = False
+            raw_include_specs = ['fqn:*', 'source:*']
+
+        selected_nodes = self.get_nodes_from_multiple_specs(
+            graph,
+            raw_include_specs,
+            check_existence=check_existance
+        )
+        selected_nodes = self.get_nodes_from_multiple_specs(
+            graph,
+            raw_exclude_specs,
+            nodes=selected_nodes,
+            exclude=True
+        )
 
         return selected_nodes
 
@@ -407,7 +444,7 @@ def _is_match(self, node_name, resource_types, tags, required):
         elif node_name in self.manifest.sources:
             node = self.manifest.sources[node_name]
         else:
-            raise dbt.exceptions.InternalException(
+            raise InternalException(
                 f'Node {node_name} not found in the manifest!'
             )
         if node.resource_type not in resource_types:
@@ -422,8 +459,6 @@ def _is_match(self, node_name, resource_types, tags, required):
         return True
 
     def get_selected(self, include, exclude, resource_types, tags, required):
-        include = coalesce(include, ['fqn:*', 'source:*'])
-        exclude = coalesce(exclude, [])
         tags = coalesce(tags, [])
 
         graph_members = {
diff --git a/test/unit/test_graph_selection.py b/test/unit/test_graph_selection.py
index 5e214c59925..f496b9a9ef5 100644
--- a/test/unit/test_graph_selection.py
+++ b/test/unit/test_graph_selection.py
@@ -173,7 +173,7 @@ def test__spec_parsing(self):
         self.invalid_spec('@source:a+')
 
     def test__package_name_getter(self):
-        found = graph_selector.get_package_names(self.package_graph)
+        found = graph_selector.get_package_names(self.package_graph.nodes())
 
         expected = set(['X', 'Y'])
         self.assertEqual(found, expected)


Code review comments:
diff_hunk: @@ -380,18 +388,47 @@ def get_nodes_from_spec(self, graph, spec):
 
         return collected
 
-    def select_nodes(self, graph, raw_include_specs, raw_exclude_specs):
-        selected_nodes: Set[str] = set()
+    def get_nodes_from_multiple_specs(
+            self,
+            graph,
+            specs,
+            nodes=None,
+            check_existence=False,
+            exclude=False
+    ):
+        selected_nodes: Set[str] = coalesce(nodes, set())
+        operator = set.difference_update if exclude else set.update
 
-        for raw_spec in split_specs(raw_include_specs):
+        for raw_spec in split_specs(specs):
             spec = SelectionCriteria(raw_spec)
-            included_nodes = self.get_nodes_from_spec(graph, spec)
-            selected_nodes.update(included_nodes)
+            nodes = self.get_nodes_from_spec(graph, spec)
 
-        for raw_spec in split_specs(raw_exclude_specs):
-            spec = SelectionCriteria(raw_spec)
-            excluded_nodes = self.get_nodes_from_spec(graph, spec)
-            selected_nodes.difference_update(excluded_nodes)
+            if check_existence:
+                alert_non_existence(raw_spec, nodes)
+
+            operator(selected_nodes, nodes)
+
+        return selected_nodes
+
+    def select_nodes(self, graph, raw_include_specs, raw_exclude_specs):
+        raw_exclude_specs = coalesce(raw_exclude_specs, [])
+        check_existance = True
+
+        if not raw_include_specs:
+            check_existance = False
+            raw_include_specs = ['fqn:*', 'source:*']
+
+        selected_nodes = self.get_nodes_from_multiple_specs(
+            graph,
+            raw_include_specs,
+            check_existence=check_existance
path: core/dbt/graph/selector.py
text: @user1:
Super nit, but can you change all the instances of `check_existance` to `check_existence`? I see both!

@author:
Ohh. Sorry!