Skip to content

feat(cli): allow --json to be used without --dry-run in run - #2418

Open
Ford544 wants to merge 9 commits into
mainfrom
run-json-option
Open

Ford544 wants to merge 9 commits into
mainfrom
run-json-option

Conversation

@Ford544

@Ford544 Ford544 commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The behavior of --json option in the run command is inconsistent and does not match documentation.

Today --json can only be passed together with --dry-run, but (unless jit actions are present), this doesn't actually trigger a dry run, and only prints out the execution graph. The docs do not make it clear that --json effectively overrides --dry-run.

If jit actions are present, however, the dry run will be conducted, and the json representation of the run result will be printed (which is probably the more intutive behavior for --dry-run combined with --json). This is also not made clear in the docs.

Effectively, this means that the --json flag, instead of the expected "print the result of the command in json format" semantics (which would match the description in the docs: "Outputs the run result as JSON."), actually means "If no jit actions are present, print out the execution graph without running. Otherwise, run and print out the run results. Works only for dry runs".

Solution

Make the behavior of the --json option consistent with itself and the docs description by making it simply print out the run result as json, suppressing all other outputs, and allow it to be used with or without --dry-run. In order to still allow the user to obtain the execution graph if needed, introduce the --execution-graph option, which, when combined with --json will simply print out the execution graph and terminate (regardless of --dry-run settings and the presence of jit actions)

…tent regardless of compilation mode. Add --execution-graph option to preserve old behavior
@Ford544
Ford544 marked this pull request as ready for review November 3, 2026 15:12
@Ford544
Ford544 requested a review from a team as a code owner November 3, 2026 15:12
@Ford544
Ford544 requested review from apilaskowski, mbiernacik and rafal-hawrylak and removed request for a team and rafal-hawrylak November 3, 2026 15:12
Comment thread cli/README.md Outdated
| `--full-refresh` | boolean | `false` | Forces incremental tables (unless marked `protected`) to be rebuilt from scratch instead of updated incrementally (regular tables and views are always rebuilt). |
| `--credentials` | string | `.df-credentials.json` | Path to the credentials JSON file to use. |
| `--json` | boolean | `false` | Outputs the run result as JSON. Only supported together with `--dry-run` — plain `run --json` (without `--dry-run`) is rejected. |
| `--json` | boolean | `false` | Outputs the run result as JSON instead of a human-readable summary |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add a trailing period to the --json description, wrap false in backticks (\false`) and add the missing closing

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

!executionGraph.actions.some((action) => !!action.jitCode)
) {
printExecutionGraph(executionGraph, isJsonOutput);
if (argv.executionGraph) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets make sure we properly document this breaking change.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you suggest documenting it aside from the changes to cli/README.md?

) {
printExecutionGraph(executionGraph, isJsonOutput);
if (argv.executionGraph) {
printExecutionGraph(executionGraph, argv.json);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed comment explained that the early graph print was skipped for JiT actions because their SQL is only produced once the Runner triggers JiT compilation. With --execution-graph that limitation comes back: JiT actions are emitted with raw jitCode and no compiled SQL. Worth documenting in the flag description/README (or warning on stderr), and adding a test for --execution-graph --json with JiT actions.

Also, this early return silently ignores --run-tests and --dry-run — should those combinations be rejected or documented?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rafal-hawrylak PTAL please as you implemented JIT support

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--dry-run --json now always runs Runner and populates compiledSql for both AoT and JiT. For --execution-graph, build() only sets jitCode with empty tasks since JiT compiles inside Runner, so documenting that and rejecting --dry-run or --run-tests with --execution-graph makes sense.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with skipping for JiT was that it led to the command having completely different behavior and outputs depending on whether JiT actions are present (make a dry run and print run result if they are, print the exec graph if they aren't). Execution graph is the intermediate stage after AoT compilation but before running (and thus before JiT compilation), so IMO it makes sense to leave JiT as is here. Warning on stderr might be a good idea though.

Rejecting when pairing --execution-graph with --run-tests and --dry-run might be a good idea as well.

@Ford544 Ford544 Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning if --run-tests or --dry-run also set + a warning if jit actions are present that they will not be compiled

Comment thread cli/commands/run_command.ts Outdated
const logger = new Logger(!argv.json);

if (isJsonOutput && !argv.dryRun) {
if (argv.executionGraph && !argv.json) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why require --json here? printExecutionGraph(executionGraph, json) already supports a non-JSON mode, so allowing plain --execution-graph would avoid this extra validation path. If the restriction is intentional, maybe a shorter message: --execution-graph requires --json.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I required it mostly because my intention in adding the option was to preserve the old behavior in case any users need it, and in the old version there was no way to obtain the non-json summary. But I suppose we can as well use that logic and allow --execution-graph without --json.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the requirement, and extended the non-JSON path of printExecutionGraph to also display the current warehouse state (since otherwise it would be essenitally identical to compile output).

I think the name --execution-graph is somewhat confusing now though, since it does not actually print the graph without --json - do you think maybe something like --build-only would be a better name?

Comment thread cli/commands/run_command.ts Outdated
});

if (actionsByName.size === 0) {
if (!argv.json && actionsByName.size === 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With --json and no actions we now fall through and create a lineage emitter + runner just to get an empty result. Consider short-circuiting here instead: print an empty RunResult (status SUCCESSFUL, actions: []) and return 0.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread cli/commands/run_command.ts Outdated
const defaultLocation = compiledGraph.projectConfig?.defaultLocation ?? undefined;
if (
!isJsonOutput &&
!argv.json &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In JSON mode this location-mismatch warning (and the --timeout hint above, and the timed-out/cancelled messages at the end) are dropped entirely. Since stdout carries the JSON, these could go to stderr instead — same approach you used for failed unit tests.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - those warnings already wrote to stderr, so not sure why they were skipped before.

Comment thread cli/README.md Outdated
| `--job-prefix` | string | — | Adds an additional prefix to BigQuery job IDs, in the form `dataform-${jobPrefix}-`. |
| `--job-labels` | string | — | Comma-separated `key=value` labels to attach to BigQuery jobs, e.g. `'key1=val1,key2=val2'`. Also applied to unit tests with `--run-tests`. Overrides `defaultJobLabels`, action `jobLabels` and table `labels` that use the same key. |
| `--emit-lineage` | boolean | — | Emits OpenLineage `RunEvent`s to Knowledge Catalog Lineage for each executed action. Overrides the `lineage.enabled` setting in `workflow_settings.yaml` when specified. See the [Data Lineage guide](../docs/lineage.md). |
| `--execution-graph` | boolean | false | Only builds and outputs the execution graph, without submitting the jobs to BigQuery. Only supported together with `--json` — plain `run --execution-graph` (without `--json`) is rejected.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits: missing trailing | on this row; default should be `false` for consistency; and it'd read better placed next to --json/--dry-run rather than after --emit-lineage. Also the --json row above lost its trailing period.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread cli/index_run_e2e_test.ts
},
);

test("run --json executes actions and outputs RunResult", { timeout: 120000 }, async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This performs a real (non-dry) run that creates example_table. Please double-check uniqueDataset is cleaned up after the suite so we don't leak datasets in the integration project.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It deletes the datasets after running tests AND removes all the stale ones that were left undeleted for whatever reason before running tests, just like the other suites, so there should be no long term leak possible

const aotAction = executedGraph.actions.find((a: any) => a.target.name === "aot_table");
expect(!!aotAction).to.equal(true);
expect(aotAction.tasks[0].statement).to.include("SELECT 2 as id");
expect(aotAction.tasks[0].compiledSql).to.include("SELECT 2 as id");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion change (statement -> compiledSql) illustrates the output schema change for run --dry-run --json (ExecutionGraph -> RunResult) — see the breaking-change comment in run_command.ts.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, passing --actions=aot_table pruned jit_table out of executionGraph.actions, which caused --dry-run --json to hit the early ExecutionGraph exit (statement) instead of returning RunResult (compiledSql). Also now it is consistent with other assertions in this suite (mixed JiT with AoT).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants