Repository: dbt-labs/dbt-core

Issue:
[CT-3487] [Bug] `dbt show --inline` does not work when all models are disabled in `dbt_project.yml`
### 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 I run `dbt show --inline` it works. When I add a `--target` flag it does not work.

### Expected Behavior

`dbt show` works when a target is supplied.

### Steps To Reproduce

I'm running the latest version of `dbt-biguery` with this `profiles.yml`:
```yml
dbt_docs:
    target: dev
    outputs:
        dev:
            type: bigquery
            method: oauth
            project: <PROJECT_ID>
            schema: <ENV_VAR>
            location: europe-west4
            threads: 4
        dev2:
            type: bigquery
            method: oauth
            project: <PROJECT_ID>
            schema: <ENV_VAR>
            location: europe-west4
            threads: 4
```

Note that `dev2` is an exact copy of `dev`.

Run the following commands and observe differing behaviour:
```shell
$ dbt show --inline "SELECT 1 AS id"
$ dbt show --inline "SELECT 1 AS id" --target dev
$ dbt show --inline "SELECT 1 AS id" --target dev2
```

The first 2 commands return a result, the third command does not.

### Relevant log output

```shell
$ dbt show --inline "SELECT 1 AS id"
15:13:44  Running with dbt=1.7.3
15:13:44  Registered adapter: bigquery=1.7.2
15:13:44  Unable to do partial parsing because of a version mismatch
15:13:46  [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- tests
15:13:47  Found 1 model, 1 seed, 0 sources, 0 exposures, 0 metrics, 949 macros, 0 groups, 0 semantic models
15:13:47  
15:13:48  Concurrency: 4 threads (target='dev')
15:13:48  
15:13:49  Previewing inline node:
| id |
| -- |
|  1 |

$ dbt show --inline "SELECT 1 AS id" --target dev
15:13:54  Running with dbt=1.7.3
15:13:55  Registered adapter: bigquery=1.7.2
15:13:55  Unable to do partial parsing because config vars, config profile, or config target have changed
15:13:57  [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- tests
15:13:57  Found 1 model, 1 seed, 0 sources, 0 exposures, 0 metrics, 949 macros, 0 groups, 0 semantic models
15:13:57  
15:13:58  Concurrency: 4 threads (target='dev')
15:13:58  
15:13:59  Previewing inline node:
| id |
| -- |
|  1 |

$ dbt show --inline "SELECT 1 AS id" --target dev2
15:14:03  Running with dbt=1.7.3
15:14:03  Registered adapter: bigquery=1.7.2
15:14:03  Unable to do partial parsing because config vars, config profile, or config target have changed
15:14:06  Found 1 seed, 0 sources, 0 exposures, 0 metrics, 949 macros, 0 groups, 0 semantic models
15:14:06  
15:14:06  Nothing to do. Try checking your model configs and model specification args
```


### Environment

```markdown
- OS:Ubuntu 22.04
- Python: Python 3.10.12
- dbt:

Core:
  - installed: 1.7.3
  - latest:    1.7.3 - Up to date!

Plugins:
  - bigquery: 1.7.2 - Up to date!
```


### Which database adapter are you using with dbt?

bigquery

### Additional Context

_No response_


Discussion:
@pgoslatara Do you know what is different between your dbt project when you use different targets?

It looks like you are able to successfully use `dbt show --inline` when `--target dev`, but not able to when `--target dev2`.

This makes me think it might have something to do with target-specific logic you have within your project.

The only thing that I can see that is different in your example output is that the first two have this warning (but the last one doesn't):
```
15:13:57  [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- tests
```

I tried to reproduce the same issue you are seeing, but I wasn't able. See below for the files and commands that I used.

Could you try tweaking these example files to see the same unexpected behavior that you described?

### Files

These are the only two files I put in my project (no models or anything else):

`dbt_project.yml`
```yaml
name: "my_project"
version: "1.0.0"
config-version: 2
profile: "bigquery"

clean-targets:
  - dbt_packages
  - '{{ env_var("DBT_TARGET_PATH", "target") }}'
  - '{{ env_var("DBT_LOG_PATH", "logs") }}'
```

`profiles.yml`
```yaml
bigquery:
  target: blue

  outputs:
    dev:
      method: service-account
      keyfile: '{{ env_var(''BIGQUERY_SERVICE_KEY_PATH'') }}'
      project: '{{ env_var(''BIGQUERY_TEST_DATABASE'') }}'
      schema: dbt_dbeatty
      threads: 10
      type: bigquery

    dev2:
      method: service-account
      keyfile: '{{ env_var(''BIGQUERY_SERVICE_KEY_PATH'') }}'
      project: '{{ env_var(''BIGQUERY_TEST_DATABASE'') }}'
      schema: dbt_dbeatty
      threads: 10
      type: bigquery
```

### Commands

```shell
dbt show --inline "SELECT 1 AS id" --target dev
dbt show --inline "SELECT 1 AS id" --target dev2
```

### Output

```
(env) $ dbt show --inline "SELECT 1 AS id" --target dev
22:45:27  Running with dbt=1.7.3
22:45:28  Registered adapter: bigquery=1.7.2
22:45:28  Found 0 sources, 0 exposures, 0 metrics, 447 macros, 0 groups, 0 semantic models
22:45:28  
22:45:28  Concurrency: 10 threads (target='dev')
22:45:28  
22:45:29  Previewing inline node:
| id |
| -- |
|  1 |

(env) $ dbt show --inline "SELECT 1 AS id" --target dev2
22:45:34  Running with dbt=1.7.3
22:45:35  Registered adapter: bigquery=1.7.2
22:45:35  Unable to do partial parsing because config vars, config profile, or config target have changed
22:45:36  Found 0 sources, 0 exposures, 0 metrics, 447 macros, 0 groups, 0 semantic models
22:45:36  
22:45:36  Concurrency: 10 threads (target='dev2')
22:45:36  
22:45:37  Previewing inline node:
| id |
| -- |
|  1 |
```

@dbeatty10 Thanks for looking into this. Good idea on the project configuration, I figured out what's causing this issue. We have some jinja logic that disables our models when any target other than "dev" is used (I know, it's complex :smiley:). Essentially what's happening is that when models are disabled then `dbt show` does not return anything, as evidenced by the commands in the issue description.

To reproduce, add this to your `dbt_project.yml`:
```yml
models:
  +enabled: false
```

Regardless of what target you select, running `dbt show --inline "SELECT 1 AS id"` will not return a value.
### Reprex

Depending on the content of `dbt_project.yml`, this command either shows results (as expected) or doesn't:
```shell
dbt show --inline "select 1 as id"
```

This works:

`models/my_model.sql`

```sql
{{ config(enabled=false) }}

select 1 as id
```

But this doesn't:

`dbt_project.yml`

```yaml
models:
  +enabled: false
```
I've traced this issue to this [line](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/task/runnable.py#L151):
```python
for uid in self.job_queue.get_selected_nodes():
```

`self.job_queue.get_selected_nodes()` returns zero nodes when models are disabled. What approach to take to fix this I do not know, this feels rather nested in terms of impact.
### Acceptance criteria

dbt core should disregard [selection](https://docs.getdbt.com/reference/node-selection/syntax#shorthand) / [selectors](https://docs.getdbt.com/reference/node-selection/yaml-selectors) altogether when running in inline mode during ["interactive compile"](https://docs.getdbt.com/reference/commands/compile#interactive-compile) and [dbt show](https://docs.getdbt.com/reference/commands/compile#interactive-compile).

i.e., whenever the `--inline` flag is passed to `dbt compile` or `dbt show`, dbt should render that Jinja regardless if:

- the dbt project has 0 nodes or many
- all nodes are disabled or not
- a `--select` or `--selector` flag is passed that selects 0 nodes
- the default selector selects 0 nodes
- etc.

In the case of `dbt show,` it should also run it as a SQL query and return the result set.




We've identified a handful of edge cases where `--inline` doesn't work as desired.

### Case 1

See https://github.com/dbt-labs/dbt-core/issues/9269#issuecomment-1854755317

### Case 2

```shell
dbt compile --select something_that_doesnt_match_any_nodes --inline "select 1 as id"
dbt show --select something_that_doesnt_match_any_nodes --inline "select 1 as id"
```

### Case 3

`selectors.yml`

```yaml
selectors:
  - name: no_nodes_for_you
    description: Something that matches no nodes
    default: false
    definition:
      union:
        - method: fqn
          value: "*"
        - exclude:
          - method: fqn
            value: "*"
```

```shell
dbt compile --selector no_nodes_for_you --inline "select 1 as id"
dbt show --selector no_nodes_for_you --inline "select 1 as id"
```

### Case 4

Same `selectors.yml` as Case 3, except make it the default instead:
```yaml
    default: true
```

```shell
dbt compile --inline "select 1 as id"
dbt show --inline "select 1 as id"
```

Resolving patch:
diff --git a/core/dbt/cli/flags.py b/core/dbt/cli/flags.py
--- a/core/dbt/cli/flags.py
+++ b/core/dbt/cli/flags.py
@@ -289,6 +289,10 @@ def _assign_params(
             params_assigned_from_default, ["WARN_ERROR", "WARN_ERROR_OPTIONS"]
         )
 
+        # Handle arguments mutually exclusive with INLINE
+        self._assert_mutually_exclusive(params_assigned_from_default, ["SELECT", "INLINE"])
+        self._assert_mutually_exclusive(params_assigned_from_default, ["SELECTOR", "INLINE"])
+
         # Support lower cased access for legacy code.
         params = set(
             x for x in dir(self) if not callable(getattr(self, x)) and not x.startswith("__")
@@ -315,7 +319,9 @@ def _assert_mutually_exclusive(
         """
         set_flag = None
         for flag in group:
-            flag_set_by_user = flag.lower() not in params_assigned_from_default
+            flag_set_by_user = (
+                hasattr(self, flag) and flag.lower() not in params_assigned_from_default
+            )
             if flag_set_by_user and set_flag:
                 raise DbtUsageException(
                     f"{flag.lower()}: not allowed with argument {set_flag.lower()}"
diff --git a/core/dbt/task/compile.py b/core/dbt/task/compile.py
--- a/core/dbt/task/compile.py
+++ b/core/dbt/task/compile.py
@@ -104,6 +104,12 @@ def _runtime_initialize(self):
                 )
                 sql_node = block_parser.parse_remote(self.args.inline, "inline_query")
                 process_node(self.config, self.manifest, sql_node)
+                # Special hack to remove disabled, if it's there. This would only happen
+                # if all models are disabled in dbt_project
+                if sql_node.config.enabled is False:
+                    sql_node.config.enabled = True
+                    self.manifest.disabled.pop(sql_node.unique_id)
+                    self.manifest.nodes[sql_node.unique_id] = sql_node
                 # keep track of the node added to the manifest
                 self._inline_node_id = sql_node.unique_id
             except CompilationError as exc:
diff --git a/core/dbt/task/runnable.py b/core/dbt/task/runnable.py
--- a/core/dbt/task/runnable.py
+++ b/core/dbt/task/runnable.py
@@ -5,7 +5,7 @@
 from datetime import datetime
 from multiprocessing.dummy import Pool as ThreadPool
 from pathlib import Path
-from typing import AbstractSet, Dict, Iterable, List, Optional, Set, Tuple
+from typing import AbstractSet, Dict, Iterable, List, Optional, Set, Tuple, Union
 
 import dbt.exceptions
 import dbt.tracking
@@ -108,7 +108,11 @@ def exclusion_arg(self):
 
     def get_selection_spec(self) -> SelectionSpec:
         default_selector_name = self.config.get_default_selector_name()
-        if self.args.selector:
+        spec: Union[SelectionSpec, bool]
+        if hasattr(self.args, "inline") and self.args.inline:
+            # We want an empty selection spec.
+            spec = parse_difference(None, None)
+        elif self.args.selector:
             # use pre-defined selector (--selector)
             spec = self.config.get_selector(self.args.selector)
         elif not (self.selection_arg or self.exclusion_arg) and default_selector_name:


Test patch:
diff --git a/tests/functional/dbt_runner/test_dbt_runner.py b/tests/functional/dbt_runner/test_dbt_runner.py
--- a/tests/functional/dbt_runner/test_dbt_runner.py
+++ b/tests/functional/dbt_runner/test_dbt_runner.py
@@ -36,6 +36,9 @@ def test_command_mutually_exclusive_option(self, dbt: dbtRunner) -> None:
         res = dbt.invoke(["deps", "--warn-error", "--warn-error-options", '{"include": "all"}'])
         assert type(res.exception) == DbtUsageException
 
+        res = dbt.invoke(["compile", "--select", "models", "--inline", "select 1 as id"])
+        assert type(res.exception) == DbtUsageException
+
     def test_invalid_command(self, dbt: dbtRunner) -> None:
         res = dbt.invoke(["invalid-command"])
         assert type(res.exception) == DbtUsageException
diff --git a/tests/functional/graph_selection/test_inline.py b/tests/functional/graph_selection/test_inline.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/graph_selection/test_inline.py
@@ -0,0 +1,64 @@
+import pytest
+
+from dbt.cli.exceptions import DbtUsageException
+from dbt.tests.util import run_dbt, run_dbt_and_capture, write_file
+
+selectors_yml = """
+    selectors:
+      - name: test_selector
+        description: Exclude everything
+        default: true
+        definition:
+           method: package
+           value: "foo"
+    """
+
+dbt_project_yml = """
+name: test
+profile: test
+flags:
+  send_anonymous_usage_stats: false
+"""
+
+dbt_project_yml_disabled_models = """
+name: test
+profile: test
+flags:
+  send_anonymous_usage_stats: false
+models:
+  +enabled: false
+"""
+
+
+class TestCompileInlineWithSelector:
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "first_model.sql": "select 1 as id",
+        }
+
+    @pytest.fixture(scope="class")
+    def selectors(self):
+        return selectors_yml
+
+    def test_inline_selectors(self, project):
+        (results, log_output) = run_dbt_and_capture(
+            ["compile", "--inline", "select * from {{ ref('first_model') }}"]
+        )
+        assert len(results) == 1
+        assert "Compiled inline node is:" in log_output
+
+        # Set all models to disabled, check that we still get inline result
+        write_file(dbt_project_yml_disabled_models, project.project_root, "dbt_project.yml")
+        (results, log_output) = run_dbt_and_capture(["compile", "--inline", "select 1 as id"])
+        assert len(results) == 1
+
+        # put back non-disabled dbt_project and check for mutually exclusive error message
+        # for --select and --inline
+        write_file(dbt_project_yml, project.project_root, "dbt_project.yml")
+        with pytest.raises(DbtUsageException):
+            run_dbt(["compile", "--select", "first_model", "--inline", "select 1 as id"])
+
+        # check for mutually exclusive --selector and --inline
+        with pytest.raises(DbtUsageException):
+            run_dbt(["compile", "--selector", "test_selector", "--inline", "select 1 as id"])