feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(cli): node selection for dataform compile - #2212

Merged
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection
Aug 2, 2026
Merged

feat(cli): node selection for dataform compile#2212
kolina merged 4 commits into
dataform-co:mainfrom
andrebmallmann:feat/compile-node-selection

Conversation

@andrebmallmann

Copy link
Copy Markdown
Contributor

Summary

Adds node selection to dataform compile, mirroring dbt compile --select. compile now accepts the same selection flags as run/build--actions, --tags, --include-deps, --include-dependents — and filters the printed graph to just the selected action(s):

dataform compile --actions my_model --json
dataform compile --tags daily --include-deps --json

Resolves#2203.

How it works

This is output filtering, not partial compilation. The whole project must
still compile (ref() resolution and the dependency graph require every action
registered), so we compile fully and then prune the result before printing —
reusing the existing prune() that run/build already use, plus the shared
yargs option definitions. No proto changes.

  • A clean graph is pruned only when a selector (--actions/--tags) is present.
  • On compile errors, the full graph + errors print unchanged (graph-level errors kept as-is).
  • A selector matching nothing emits an empty graph and exits 0, matching prune's run-side behavior.
  • --include-deps / --include-dependents keep their existing validation (only valid alongside --actions/--tags).

Tests

New compile node selection suite in cli/index_compile_test.ts over an
upstream -> midstream -> downstream project, covering: full graph (no selector),
--actions, --include-deps, --include-dependents, --tags, empty-match
exits 0, and the no-selector validation error. //cli:index_compile_test passes.

Credit

Implementation by @ihistand (Ivan Histand), proposed in SQLAnvil/sqlanvil#27 in
response to this issue. Thank you!

@andrebmallmann
andrebmallmann requested a review from a team as a code ownerJune 21, 2026 02:51
@andrebmallmann
andrebmallmann requested review from andrzej-grudzien and removed request for a teamJune 21, 2026 02:51
@google-cla

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch 2 times, most recently from 9fa4e64 to 2d1b912CompareJune 21, 2026 03:02
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

@kolina
kolina removed the request for review from andrzej-grudzienJune 25, 2026 23:55
@andrebmallmann

andrebmallmann commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

I think it makes sense to add some prefix to these options for compile to make it more clear that these options are different from not compiling them. I'd suggest output-{...} format, feel free to suggest something else if you want.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

@kolina

Copy link
Copy Markdown
Contributor

Sorry for the late reply from my side.

Thanks for the review! Just to check I understood correctly: the concern is that for compile these flags work as filters rather than changing what actually gets compiled, so the unprefixed names could lead to confusion, is that it?

Yes, it may lead to confusion. Also if we decide to implement actual filtering of compiled nodes, it may be hard to pick filters for them in the future.

This version of the solution leans towards keeping parity with commands like run and build, and from a DX perspective I personally like that more. Do you think a clear distinction between filtering vs. compiling outweighs that consistency?

In my opinion, yes.

If we go the prefix way, which do you think is better --output-* or --filter-*? "Filter" reads a bit closer to the actual behavior for me, but I'm happy with either.

output- may be more clear to me, but I don't have a strong opinion here.

And with a prefix, would --include-deps / --include-dependents become --output-include-deps / --output-include-dependents?

Yeah, I think so.

andrebmallmann added a commit to andrebmallmann/dataform that referenced this pull request Jul 19, 2026
Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

Hello @kolina

Sorry for late reply on my side, rough weeks.

I've pushed the rename we agreed on the compile selection flags now carry the output- prefix to make it explicit that they filter the printed output rather than change what gets compiled.

run/build keep the unprefixed flags, so there's no change there. Tests updated accordingly, and I verified it end-to-end against examples/stackoverflow_reporter

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

  1. Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
  2. Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.

I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Compiled example using my version

$CLI compile examples/stackoverflow_reporter --output-actions user_stats --json
{
"tables": [
{
"type": "table",
"target": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"query": "\n\nselect\n stg_users.user_id,\n stg_users.age,\n stg_users.creation_date,\n stg_users.user_tenure,\n count(distinct stg_badges.badge_id) as badge_count,\n count(distinct posts_all.post_id) as questions_and_answer_count,\n count(distinct if(type=\"question\", posts_all.post_id, null)) as question_count,\n count(distinct if(type=\"answer\", posts_all.post_id, null)) as answer_count,\n max(stg_badges.award_timestamp) as last_badge_received_at,\n max(posts_all.created_at) as last_posted_at,\n max(if(type=\"question\", posts_all.created_at, null)) as last_question_posted_at,\n max(if(type=\"answer\", posts_all.created_at, null)) as last_answer_posted_at\nfrom\n `dataform-open-source.staging.stg_users` as stg_users\n left join `dataform-open-source.staging.stg_badges` as stg_badges\n on stg_users.user_id = stg_badges.user_id\n left join `dataform-open-source.reporting.posts_combined` as posts_all\n on stg_users.user_id = posts_all.owner_user_id\ngroup by\n 1,2,3,4",
"disabled": false,
"fileName": "definitions/reporting/user_stats.sqlx",
"tags": [
"daily"
],
"actionDescriptor": {
"description": "Create a summary table for all users including statistics on questions, answeers and badges received."
},
"dependencyTargets": [
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
}
],
"hermeticity": "NON_HERMETIC",
"canonicalTarget": {
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
"enumType": "TABLE"
}
],
"operations": [],
"assertions": [],
"declarations": [
{
"target": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw badges table"
},
"fileName": "definitions/sources/badges.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_answers table"
},
"fileName": "definitions/sources/posts_answers.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw posts_questions table"
},
"fileName": "definitions/sources/posts_questions.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
}
},
{
"target": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
"actionDescriptor": {
"description": "raw users table"
},
"fileName": "definitions/sources/users.sqlx",
"canonicalTarget": {
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
}
}
],
"tests": [],
"notebooks": [],
"dataPreparations": [],
"targets": [
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_posts_combined_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "posts_combined",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_uniqueKey_0",
"database": "dataform-open-source"
},
{
"schema": "dataform_stackoverflow_reporter_example_assertions",
"name": "reporting_user_stats_assertions_rowConditions",
"database": "dataform-open-source"
},
{
"schema": "reporting",
"name": "user_stats",
"database": "dataform-open-source"
},
{
"schema": "stackoverflow",
"name": "badges",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_answers",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "posts_questions",
"database": "bigquery-public-data"
},
{
"schema": "stackoverflow",
"name": "users",
"database": "bigquery-public-data"
},
{
"schema": "staging",
"name": "stg_badges",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_answers",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_posts_questions",
"database": "dataform-open-source"
},
{
"schema": "staging",
"name": "stg_users",
"database": "dataform-open-source"
}
],
"projectConfig": {
"warehouse": "bigquery",
"defaultSchema": "dataform_stackoverflow_reporter_example",
"assertionSchema": "dataform_stackoverflow_reporter_example_assertions",
"defaultDatabase": "dataform-open-source",
"defaultLocation": "us"
},
"graphErrors": {},
"dataformCoreVersion": "3.0.59",
"jitData": {}
}

Renames the compile-only selection flags so it is explicit that they
filter the printed output rather than change what gets compiled:
--actions -> --output-actions
--tags -> --output-tags
--include-deps -> --output-include-deps
--include-dependents -> --output-include-dependents
run/build keep the unprefixed flags. Addresses review feedback on dataform-co#2212.
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from 1dc512b to ba44749CompareJuly 19, 2026 04:41
@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolinakolina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One thing I ran into while testing that I'd like your take on. Since this reuses prune(), the filtering only applies to tables/operations/assertions — prune() leaves declarations and the flat targets manifest untouched. So compile --output-actions user_stats --json correctly returns just the user_stats table, but the output still lists all declarations and all targets from the full graph:

Here we might have two options:

Two options:

Leave as-is to keep consistent with run/build, less code; the actual SQL is correctly isolated in tables.
Trim declarations/targets in the compile output path only (not touching shared prune()) for a tighter single-model output.
I think for this PR we could close with (1) and if the option 2 is good for you i can start work in another PR.

Yeah, option 1 is fine with me!

Comment threadcli/index.ts
Extract `splitCommas` for the comma/repeat-splitting coerce used by
`actions`, `tags`, `output-actions` and `output-tags`, and
`requiresSelection` for the include-deps/include-dependents checks, so
the run/build and compile flag families no longer duplicate the same
logic in two places.
Claude-Session: https://claude.ai/code/session_01SDrEz3ahcaMu9Zc2pwwpUu
…ctions
Follow-up to the shared coerce/check refactor:
* `requiresSelection` now takes the selector option objects rather than
their names as string literals, so renaming `actions`/`output-actions`
can't silently desync the check from the flag it guards.
* `prune()` left `targets` untouched, so `compile --output-actions x`
printed a single-table graph next to a `targets` list covering the
whole project. Nothing downstream reads `targets`, but `compile` prints
it, so it now gets filtered with the same included-action set.
* `matchPatterns()` matches exactly on the action name or its
fully-qualified `database.schema.name`; there is no glob support, so
`--output-actions` no longer advertises `'*'` wildcards.
Claude-Session: https://claude.ai/code/session_01PnfbDkrf1KoYR2hjp7DEyQ
@andrebmallmann
andrebmallmannforce-pushed the feat/compile-node-selection branch from d0d3e7c to 52c2021CompareJuly 30, 2026 00:48
@andrebmallmann

Copy link
Copy Markdown
ContributorAuthor

@kolina

One thing i hit during the review of coerce and i want to make it clear.

The JSON I posted earlier is misleading. With --output-actions user_stats the tables list is
correctly just the one table, but targets still listed all 13 actions. prune() spreads the rest
of the graph through untouched, so targets was never filtered. Harmless for run and build since
nothing reads it, but compile prints it. Fixed in 52c2021, tables and targets now agree.

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun

@kolina

Copy link
Copy Markdown
Contributor

/gcbrun(52c2021)

@kolina
kolina merged commit 2b2e43e into dataform-co:mainAug 2, 2026
14 checks passed
Sign up for freeto 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.

Node selection for dataform compile (compile/output a single model)

2 participants

@andrebmallmann@kolina