Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
c45473d
fix: render SQL Server table functions as inline TVFs and list wareho…
kvlonge Nov 7, 2026
bc86f06
feat(lint): warn when SQL lint is unavailable for a dialect
kvlonge Nov 7, 2026
15b863e
fix(sqlserver): quote schema names in EXEC, end MERGE with a semicolo…
kvlonge Nov 7, 2026
57b0c20
refactor(analysis): read B101 spellings from a recorded table instead…
kvlonge Nov 7, 2026
7edf70d
feat(lint): keep L007 out of every exit decision and show it in compi…
kvlonge Nov 7, 2026
2fb4547
fix(lint): keep L007 out of rule exceptions and the plan rules gate
kvlonge Nov 7, 2026
3572408
docs(rules): name plan and build, not format, in the L007 exit note
kvlonge Nov 7, 2026
c03cec2
Merge remote-tracking branch 'origin/main' into wip/b101-function-cat…
kvlonge Nov 7, 2026
bc89e18
Merge branch 'main' into feat/lint-unavailable-warning
kvlonge Nov 7, 2026
ff7e1b9
Merge branch 'main' into fix/clickhouse-recall-sqlserver-tvf
kvlonge Nov 7, 2026
47c7a6b
test(orchestration): count the store statements a wide catalog listin…
kvlonge Nov 7, 2026
c00a9da
refactor(analysis): share diagnostic location and the catalog's optio…
kvlonge Nov 7, 2026
5aea666
Merge branch 'main' into feat/lint-unavailable-warning
kvlonge Nov 7, 2026
b620a2f
Merge branch 'main' into fix/clickhouse-recall-sqlserver-tvf
kvlonge Nov 7, 2026
355219b
Merge branch 'main' into test/catalog-statement-count
kvlonge Nov 7, 2026
2436dc7
Merge branch 'main' into refactor/b101-recorded-spellings
kvlonge Nov 7, 2026
3cd6dff
feat(contracts): prove dynamic pivots from SQLQuack parse trees
kvlonge Nov 8, 2026
db2387f
fix(contracts): refuse pivot SQL nested deeper than 512 levels before…
kvlonge Nov 8, 2026
630b2d8
wip: read rule facts with SQLQuack
kvlonge Nov 8, 2026
d9e86d0
wip: locate cast types and Snowflake date parts without a generator
kvlonge Nov 8, 2026
27e4cb4
Merge remote-tracking branch 'origin/feat/lint-unavailable-warning' i…
kvlonge Nov 8, 2026
a2909a3
wip: report built-in rules unavailable for unparsed dialects
kvlonge Nov 8, 2026
7a85582
wip: e2e for built-in rules on unparsed dialects
kvlonge Nov 8, 2026
4fad955
chore(deps): bump sqlquack to a9dde6a0
kvlonge Nov 8, 2026
adc7984
wip: docs and tests for built-in rules on unparsed dialects
kvlonge Nov 8, 2026
088bd97
wip: format tests
kvlonge Nov 8, 2026
8b2a9d3
fix(rules): refuse rule facts for statements deeper than 512 levels
kvlonge Nov 8, 2026
d9fa004
fix(rules): anchor nested comparisons and Snowflake LAST_DAY date parts
kvlonge Nov 8, 2026
449de8d
feat(scenario): map scenario column types with SQLQuack's type parsers
kvlonge Nov 8, 2026
7655ef3
fix(scenario): map SQL Server date, time, uuid and money types locally
kvlonge Nov 8, 2026
5ad2d42
Merge branch 'main' into feat/sqlquack-type-and-local-sql
kvlonge Nov 8, 2026
4637fb4
chore(fensu): allow the cast-target walk next to the other JSON tree …
kvlonge Nov 8, 2026
0352b44
refactor(rules): name the date-part and unavailable-rule loops
kvlonge Nov 8, 2026
e9221bc
Merge remote-tracking branch 'origin/main' into feat/rule-facts-sqlquack
kvlonge Nov 8, 2026
5f253c1
docs(analysis): keep the warehouse type binding's doc comment to one …
kvlonge Nov 8, 2026
28a28c5
Merge remote-tracking branch 'origin/main' into feat/sqlquack-type-an…
kvlonge Nov 8, 2026
2d146f5
feat(types): normalize adapter types without Polyglot
kvlonge Nov 8, 2026
d2e1fb8
fix(ui): stop a burst of control API requests on every live refresh
kvlonge Nov 8, 2026
9c09e54
fix(types): read only shipped dialects and ClickHouse nested parameters
kvlonge Nov 8, 2026
e0638cd
refactor(rules): read the shared-parser dialect from one lookup
kvlonge Nov 8, 2026
cb84e52
Merge main into the sqlquack bump
kvlonge Nov 8, 2026
fea889c
chore(deps): bump sqlquack to 19c51fa2
kvlonge Nov 8, 2026
183b210
build(deps): pin SQLQuack before its Snowflake grammar checks
kvlonge Nov 8, 2026
36342ee
Merge branch 'feat/sqlquack-type-and-local-sql' into feat/sqlquack-ty…
kvlonge Nov 8, 2026
7d1ef9a
fix(rules): read relation markers as tables in custom-rule syntax trees
kvlonge Nov 8, 2026
fd5faf5
Merge branch 'main' into fix/ui-refresh-burst
kvlonge Nov 8, 2026
de3cdc8
Merge fix/sql-tree-placeholder-relations (#1370)
kvlonge Nov 8, 2026
2d2eaa5
Merge feat/rule-facts-sqlquack (#1361)
kvlonge Nov 8, 2026
2a8cfa2
Merge feat/sqlquack-type-normalization (#1366)
kvlonge Nov 8, 2026
cff088f
Merge chore/sqlquack-rev-bump (#1368)
kvlonge Nov 8, 2026
a1e6b0f
build(deps): keep Windows windows-sys edges at main's resolution
kvlonge Nov 8, 2026
9d01621
docs(types): keep the ClickHouse type parameter doc comment to one line
kvlonge Nov 8, 2026
1e4aa20
feat(ui): wire the lineage and changes pages to the live control API
kvlonge Nov 8, 2026
0793704
feat(server): page the security audit newest first with actor names
kvlonge Nov 8, 2026
eb7c880
feat(ui): wire the audit log to the security audit read
kvlonge Nov 8, 2026
96ad1ba
feat(server): list people and role holdings for administrators
kvlonge Nov 8, 2026
5fa1089
feat(ui): wire the people page to the people and role reads
kvlonge Nov 8, 2026
118d6d3
fix(server): count no active sessions for a disabled person
kvlonge Nov 8, 2026
19ba7be
Merge the SQLQuack parser train into the pivot proof
kvlonge Nov 8, 2026
d27158a
fix(contracts): read relation markers as relations in dynamic pivot p…
kvlonge Nov 8, 2026
9b3282c
refactor(analysis): share the statement depth guard between pivot pro…
kvlonge Nov 8, 2026
87b8bd9
fix(contracts): keep non-dependency markers out of pivot relations
kvlonge Nov 8, 2026
42579ef
fix(types): spell ClickHouse Nullable and BINARY as the engine does
kvlonge Nov 8, 2026
6a32d49
Merge main into the SQLQuack parser train
kvlonge Nov 8, 2026
c9fa2ad
Merge main into test/catalog-statement-count
kvlonge Nov 8, 2026
1b1d0b3
Merge branch 'main' into fix/ui-refresh-burst
kvlonge Nov 8, 2026
7c64f85
Merge the SQLQuack parser train into the pivot proof
kvlonge Nov 8, 2026
5494e24
feat(server): let people read a target's location health
kvlonge Nov 8, 2026
fd39408
feat(ui): show live location health on the Locations page
kvlonge Nov 8, 2026
8dc27f1
fix(server): limit a target's health read to its own observations
kvlonge Nov 8, 2026
1f04a86
feat(server): let people read live platform problems
kvlonge Nov 8, 2026
8929fae
feat(ui): show live platform problems in the alert banner
kvlonge Nov 8, 2026
58de3d6
docs(ui): keep the locations doc comment on listLocations
kvlonge Nov 8, 2026
35f7a3a
feat(server): let role administrators read a target's role definitions
kvlonge Nov 8, 2026
a49d346
feat(ui): show role definitions on the Roles pages
kvlonge Nov 8, 2026
d220b21
feat(ui): show warehouse sync status in the top bar
kvlonge Nov 8, 2026
b12327f
fix(ui): stop rereading warehouse sync after a refused read
kvlonge Nov 8, 2026
03b49ca
fix(rules): wrap Snowflake table functions in TABLE( ) before parsing
kvlonge Nov 8, 2026
a84c6ea
Merge origin/main into the platform and admin slices
kvlonge Nov 8, 2026
689deba
fix(rules): read Snowflake table function markers inside TABLE( ) in …
kvlonge Nov 8, 2026
5974f71
feat(ui): show catalog node detail from the control API and finish li…
kvlonge Nov 8, 2026
a1d030b
Merge remote-tracking branch 'origin/main' into fix/ui-refresh-burst
kvlonge Nov 8, 2026
f729c4c
fix(ui): ask for refused node SQL once until reload
kvlonge Nov 8, 2026
5b2b272
fix(rules): keep columns after a wrapped table function in rule trees
kvlonge Nov 8, 2026
001d78c
fix(ui): wrap SQL previews and drop the kind prefix from SQL file labels
kvlonge Nov 8, 2026
2994b2a
feat: publish column lineage and show it on catalog node pages
kvlonge Nov 8, 2026
2c59909
fix(ui): show full names and transform kinds in column lineage
kvlonge Nov 8, 2026
2ff5a9e
refactor: move column lineage to its own change
kvlonge Nov 8, 2026
c8e186b
Merge branch 'main' into test/catalog-statement-count
kvlonge Nov 8, 2026
2cd8757
feat: show per-node run history and selecting jobs on catalog node pages
kvlonge Nov 8, 2026
b5f41d4
feat(orchestration): gzip location snapshot publishes
kvlonge Nov 8, 2026
e9a8dde
feat: show run events, statements, SQL and output to people
kvlonge Nov 8, 2026
bff5f8c
feat: require run.prioritise for runs above priority 0
kvlonge Nov 8, 2026
d10bd28
fix(orchestration): report the server's refusal of a snapshot too lar…
kvlonge Nov 8, 2026
19a4005
fix(orchestration): bound decompressed publish bodies at 320 MiB
kvlonge Nov 8, 2026
32abf51
fix: keep run cancellation independent of run.prioritise
kvlonge Nov 8, 2026
58abe6f
test: wait for every trigger to reach the latest due time after sched…
kvlonge Nov 8, 2026
6abe6a0
test: count ticks scheduled after their own evaluation as off schedule
kvlonge Nov 8, 2026
e8df6e4
ci: run the full Verify matrix only on pull requests labelled full-ve…
kvlonge Nov 8, 2026
deed388
Merge feat/gzip-location-publish into the combined delivery
kvlonge Nov 8, 2026
0ba6b92
Merge fix/scheduler-fault-catch-up into the combined delivery
kvlonge Nov 8, 2026
b072d05
Merge test/catalog-statement-count into the combined delivery
kvlonge Nov 8, 2026
6f4ec9c
Merge fix/clickhouse-recall-sqlserver-tvf into the combined delivery
kvlonge Nov 8, 2026
6639e64
Merge chore/sqlquack-parser-train into the combined delivery
kvlonge Nov 8, 2026
7fc70b1
Merge refactor/b101-recorded-spellings into the combined delivery
kvlonge Nov 8, 2026
bfd5cfc
Merge feat/pivot-sqlquack into the combined delivery
kvlonge Nov 8, 2026
30914da
Merge feat/run-prioritise into the combined delivery
kvlonge Nov 8, 2026
ffe352d
Merge feat/ui-warehouse-sync into the combined delivery
kvlonge Nov 8, 2026
24aba79
Merge fix/ui-refresh-burst into the combined delivery
kvlonge Nov 8, 2026
ef60acc
feat: run ad-hoc sqb commands from the web UI
kvlonge Nov 8, 2026
05cd1ab
ci: keep the Verify aggregate on always()
kvlonge Nov 8, 2026
096dbdb
Merge feat/adhoc-commands into the combined delivery
kvlonge Nov 8, 2026
5f82f19
refactor(orchestration): page security audit facts beside the audit w…
kvlonge Nov 8, 2026
aeed1ce
test(ci): prove a fast-core or label-only Verify run cannot satisfy t…
kvlonge Nov 8, 2026
6c4c7f1
feat: publish column lineage and show it on catalog node pages
kvlonge Nov 8, 2026
ac8c917
Merge origin/main into the combined delivery
kvlonge Nov 8, 2026
304af88
Merge feat/ui-run-reads into the combined delivery
kvlonge Nov 8, 2026
1826b8c
fix(lineage): publish snapshot column lineage from native edge records
kvlonge Nov 8, 2026
f757fa2
fix(ui): find an ad-hoc run launched while a runs listing is in flight
kvlonge Nov 8, 2026
13fc786
Merge wip/column-lineage-native into the combined delivery
kvlonge Nov 8, 2026
0aaf702
feat: run test, seed, audit and freshness ad-hoc commands on workers
kvlonge Nov 8, 2026
2d16d24
fix: run every ad-hoc verb from the worker's verified project
kvlonge Nov 9, 2026
5f37d98
fix(ui): show the approved logos and favicon from the website
kvlonge Nov 9, 2026
3828720
Merge remote-tracking branch 'origin/wip/ui-branding' into wip/combin…
kvlonge Nov 9, 2026
e57245a
ci: run the UI checks when the website logos or favicon change
kvlonge Nov 9, 2026
fb73e67
Merge remote-tracking branch 'origin/main' into wip/combined-next-local
kvlonge Nov 9, 2026
334c6b3
Merge remote-tracking branch 'origin/feat/adhoc-verbs' into wip/combi…
kvlonge Nov 9, 2026
333c627
style: move the checks-only outcome note into its doc comment
kvlonge Nov 9, 2026
7d80e87
feat(ui): show the Overview from live runs, checks, models and deploys
kvlonge Nov 9, 2026
0bd8914
Merge remote-tracking branch 'origin/wip/ui-overview' into wip/combin…
kvlonge Nov 9, 2026
69e9de5
feat(ui): tell a partial viewer why the Overview is empty
kvlonge Nov 9, 2026
5d846c1
fix(ui): show Overview errors and drop a stale partial-view message
kvlonge Nov 9, 2026
fafaae1
Merge remote-tracking branch 'origin/wip/ui-overview' into wip/combin…
kvlonge Nov 9, 2026
fbcf13c
feat(ui): show renames, old names and clean-ups on the Maintenance page
kvlonge Nov 9, 2026
728345e
Merge remote-tracking branch 'origin/wip/ui-maintenance' into wip/com…
kvlonge Nov 9, 2026
620cbe6
fix: regenerate the TypeScript control client for ad-hoc run commands
kvlonge Nov 9, 2026
28ba06f
ci: check the generated control client in make check-ci
kvlonge Nov 9, 2026
787a0e6
feat(ui): give and remove roles and disable people from the admin pages
kvlonge Nov 9, 2026
3d5eb48
Merge remote-tracking branch 'origin/wip/ui-admin-actions' into wip/c…
kvlonge Nov 9, 2026
0a69cf7
feat(ui): test a location's warehouse connection from the Locations page
kvlonge Nov 9, 2026
3ed5eed
Merge commit '0a69cf70c44839c47cccd834c443c4d946042d79' into wip/comb…
kvlonge Nov 9, 2026
6739902
Merge remote-tracking branch 'origin/main' into wip/combined-next-local
kvlonge Nov 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,10 @@ jobs:
;;
esac
case "${path}" in
ui/*|clients/typescript/*|.github/workflows/verify.yml)
ui/*|clients/typescript/*|.github/workflows/verify.yml|website/src/assets/logo-light.png|website/src/assets/logo-dark.png|website/public/favicon.png)
# The web UI's own checks and unit tests; the wheels embed its build. It compiles
# against the generated control client, so client changes run them too.
# against the generated control client, so client changes run them too, and it
# imports the website's logos and favicon, so moving them fails its build.
ui=true
;;
esac
Expand Down Expand Up @@ -357,11 +358,6 @@ jobs:
node-version: "22"
cache: npm
cache-dependency-path: clients/typescript/package-lock.json
- name: Check generated control client against the built native API
run: |
npm --prefix clients/typescript ci
uv run --no-sync npm --prefix clients/typescript run check
uv run --no-sync npm --prefix clients/typescript test
- name: Fetch the base for the new-duplicate check
run: git fetch --no-tags --depth=1 origin "$BASE_REF"
env:
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := /bin/bash

.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering check-dupes-new check-native-stub check-build-features check-python-boundary check-release-gate check-docs-skill \
.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering check-dupes-new check-native-stub check-build-features check-python-boundary check-release-gate check-docs-skill check-control-client \
check-e2e-shards test-e2e-duckdb test-e2e-duckdb-build-core \
test-e2e-duckdb-build-incremental \
test-e2e-duckdb-cli-data test-e2e-duckdb-cli test-e2e-duckdb-inspection \
Expand Down Expand Up @@ -706,14 +706,21 @@ verify-quick:
exit $$status


check-ci: check-dupes-new check-e2e-shards check-native-layering check-native-stub check-build-features check-python-boundary check-release-gate check-docs-skill rust-lint
check-ci: check-dupes-new check-e2e-shards check-native-layering check-native-stub check-build-features check-python-boundary check-release-gate check-docs-skill check-control-client rust-lint
uv run ruff format --check .
uv run ruff check .
uv run ty check src tests
uv run pytest tests/unit/src/sqlbuild/adapter/contract/classes/strict_adapter/test_strict_adapter.py -q
uv run fensu check


# The generated TypeScript control client must match the OpenAPI document of the installed sqb.
check-control-client:
npm --prefix clients/typescript ci
uv run --no-sync npm --prefix clients/typescript run check
uv run --no-sync npm --prefix clients/typescript test


check-native-layering:
uv run python -m scripts.check_native_layering

Expand Down
87 changes: 87 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/column-lineage": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["readPersonColumnLineage"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/connection-tests": {
parameters: {
query?: never;
Expand Down Expand Up @@ -1334,6 +1350,21 @@ export interface components {
CliLoginStartRequest: {
client_hash: string;
};
/** @description One published column lineage edge; `transform` is the compiler's transform kind, such as `direct` or `cast`. */
ColumnLineageEdge: {
source_column: string;
source_key: string;
target_column: string;
target_key: string;
transform: string;
};
/** @description The column lineage reaching or leaving one node, from the current snapshot; empty before one publishes lineage. */
ColumnLineageRead: {
edges: components["schemas"]["ColumnLineageEdge"][];
nodes: components["schemas"]["LineageNodeColumns"][];
/** Format: uuid */
snapshot_id?: string | null;
};
/** @description A row whose key was already stored with different content; the first row is kept. */
ConflictEvidence: {
existing_sha256: string;
Expand Down Expand Up @@ -1472,6 +1503,16 @@ export interface components {
target: string;
window?: null | components["schemas"]["RunWindow"];
};
LineageColumn: {
data_type?: string | null;
name: string;
};
/** @description A node's columns as its compiled definition infers them; `data_type` is absent when inference could not tell. */
LineageNodeColumns: {
columns: components["schemas"]["LineageColumn"][];
kind: string;
node_key: string;
};
/** @description One location target the person can view, with the actions allowed on at least one of its nodes. */
LocationAccess: {
actions: components["schemas"]["Action"][];
Expand Down Expand Up @@ -1989,6 +2030,11 @@ export interface components {
claim_id: string;
request: components["schemas"]["RunRequest"];
};
/**
* @description The `sqb` command a run executes; job and group runs always build.
* @enum {string}
*/
RunCommand: "build" | "test" | "seed" | "audit" | "freshness";
RunErrorText: {
error_type?: string | null;
invocation_id: string;
Expand Down Expand Up @@ -2121,6 +2167,7 @@ export interface components {
};
/** @description The authorized selection and pinned execution inputs are immutable once requested. */
RunRequest: {
command?: components["schemas"]["RunCommand"];
concurrency_groups: string[];
image_id?: string | null;
job_key?: string | null;
Expand Down Expand Up @@ -3379,6 +3426,46 @@ export interface operations {
};
};
};
readPersonColumnLineage: {
parameters: {
query: {
/** @description The node whose upstream and downstream column lineage to return */
node_key: string;
};
header?: never;
path: {
location_id: string;
target: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ColumnLineageRead"];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
};
};
requestConnectionTest: {
parameters: {
query?: never;
Expand Down
82 changes: 75 additions & 7 deletions crates/sqlbuild-cli/src/adhoc/_helpers/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use sqlbuild_orchestration::runs::models::{CommandPlan, PlannerResource};
use sqlbuild_orchestration::runs::types::CommandPlanner;

use crate::adhoc::constants::{
ADHOC_VERBS, ALLOWED_DESTS, BUILD_VERB, COMMAND_DEST, INTEGER_CONVERTERS, LOAD_SOURCES_DEST,
POSITIVE_INTEGER_CONVERTER, REFUSED_DESTS, SELECTOR_DESTS,
ADHOC_VERBS, ALLOWED_DESTS, BUILD_VERB, COMMAND_DEST, FRESHNESS_VERB, INTEGER_CONVERTERS,
LOAD_SOURCES_DEST, POSITIVE_INTEGER_CONVERTER, REFUSED_DESTS, SEED_VERB, SELECTOR_DESTS,
};
use crate::argv::main::command_settings_arguments::command_settings_arguments;
use crate::argv::main::job_settings_arguments::job_settings_arguments;
use crate::argv::main::parse_cli_arguments::parse_cli_arguments;
use crate::argv::models::{ArgvSettings, Namespace, TypeFailure, Value};
Expand Down Expand Up @@ -52,13 +53,29 @@ impl CommandPlanner for CliPlanner {
}
if verb == BUILD_VERB {
job_settings_arguments(&settings)?;
} else {
command_settings_arguments(verb, &settings)?;
}
let mut select: Vec<String> = selectors.remove("select").unwrap_or_default();
select.extend(selectors.remove("positional_select").unwrap_or_default());
let exclude: Vec<String> = selectors.remove("exclude").unwrap_or_default();
let selection: BTreeSet<String> = match verb.as_str() {
SEED_VERB => {
let seeds: BTreeSet<String> = resolve(resources, &select, &exclude)?
.into_iter()
.filter(|key| key.starts_with("seed:"))
.collect();
if seeds.is_empty() {
return Err("the selectors match no seeds in the current publication".into());
}
seeds
}
FRESHNESS_VERB => freshness_sources(resources, &select, &exclude)?,
_ => resolve(resources, &select, &exclude)?,
};
Ok(CommandPlan {
verb: verb.clone(),
selection: resolve(resources, &select, &exclude)?,
selection,
settings,
})
}
Expand Down Expand Up @@ -163,18 +180,69 @@ fn resolve(
resources: &[PlannerResource],
select: &[String],
exclude: &[String],
) -> Result<BTreeSet<String>, String> {
let selected: BTreeSet<String> = resolved(resources, select, exclude)?;
if selected.is_empty() {
return Err("the selectors match no nodes in the current publication".into());
}
Ok(selected)
}

fn resolved(
resources: &[PlannerResource],
select: &[String],
exclude: &[String],
) -> Result<BTreeSet<String>, String> {
let graph_resources: Vec<GraphResource> = resources.iter().filter_map(graph_resource).collect();
let graph = build_project_graph(&graph_resources);
let selected: BTreeSet<String> = resolve_selectors(&graph, select, exclude)
Ok(resolve_selectors(&graph, select, exclude)
.map_err(|error| format!("{}: {}", error.code, error.message))?
.into_iter()
.map(|(kind, name)| format!("{kind}:{name}"))
.collect())
}

/// Sources `sqb freshness` observes: selected or upstream sources, less those the exclusions reach.
fn freshness_sources(
resources: &[PlannerResource],
select: &[String],
exclude: &[String],
) -> Result<BTreeSet<String>, String> {
let selected: BTreeSet<String> =
upstream_sources(resources, &resolved(resources, select, &[])?);
let excluded: BTreeSet<String> = if exclude.is_empty() {
BTreeSet::new()
} else {
upstream_sources(resources, &resolved(resources, exclude, &[])?)
};
let sources: BTreeSet<String> = selected.difference(&excluded).cloned().collect();
if sources.is_empty() {
return Err("no sources are selected or upstream of the selection".into());
}
Ok(sources)
}

fn upstream_sources(resources: &[PlannerResource], keys: &BTreeSet<String>) -> BTreeSet<String> {
let deps: BTreeMap<&str, &[String]> = resources
.iter()
.map(|resource| (resource.key.as_str(), resource.deps.as_slice()))
.collect();
if selected.is_empty() {
return Err("the selectors match no nodes in the current publication".into());
let mut reached: BTreeSet<&str> = BTreeSet::new();
let mut pending: Vec<&str> = keys.iter().map(String::as_str).collect();
while let Some(key) = pending.pop() {
if reached.insert(key) {
pending.extend(
deps.get(key)
.into_iter()
.flat_map(|deps| deps.iter().map(String::as_str)),
);
}
}
Ok(selected)
reached
.into_iter()
.filter(|key| key.starts_with("source:"))
.map(str::to_owned)
.collect()
}

/// A published node as the selector graph sees it; keys without a kind are not selectable.
Expand Down
6 changes: 6 additions & 0 deletions crates/sqlbuild-cli/src/adhoc/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ pub(crate) const ADHOC_VERBS: [&str; 5] = ["build", "test", "seed", "audit", "fr
/// The verb whose settings must also form valid job settings.
pub(crate) const BUILD_VERB: &str = "build";

/// The verb that runs only the seeds of its selection.
pub(crate) const SEED_VERB: &str = "seed";

/// The verb that observes sources, including those upstream of selected models.
pub(crate) const FRESHNESS_VERB: &str = "freshness";

/// The parser destination holding the verb itself, which is not a setting.
pub(crate) const COMMAND_DEST: &str = "command";

Expand Down
28 changes: 28 additions & 0 deletions crates/sqlbuild-cli/src/adhoc/tests/test_adhoc_planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,34 @@ fn given_ad_hoc_commands_when_planned_then_flags_follow_the_allowlist_and_select
command: &["test", "-s", "orders", "--case", "happy_path"],
expected_plan: Ok((&["model:orders"], json!({"case": "happy_path"}))),
},
AdhocPlanTestCase {
description: "audit settings come from the audit parser",
command: &["audit", "-s", "orders", "--concurrency", "2"],
expected_plan: Ok((&["model:orders"], json!({"concurrency": 2}))),
},
AdhocPlanTestCase {
description: "seed runs only the seeds its selectors reach",
command: &["seed", "+orders"],
expected_plan: Ok((&["seed:currencies"], json!({}))),
},
AdhocPlanTestCase {
description: "seed refuses a selection without seeds, which would run nothing",
command: &["seed", "orders"],
expected_plan: Err("the selectors match no seeds"),
},
AdhocPlanTestCase {
description: "freshness observes every source upstream of a selected model",
command: &["freshness", "order_totals", "--state", "--fail-on-stale"],
expected_plan: Ok((
&["source:shop.raw_orders"],
json!({"fail_on_stale": true, "state": true}),
)),
},
AdhocPlanTestCase {
description: "freshness excludes the sources an excluded model reads",
command: &["freshness", "--exclude", "orders"],
expected_plan: Err("no sources are selected or upstream of the selection"),
},
];
let planner = adhoc_planner();
for test_case in test_cases {
Expand Down
30 changes: 30 additions & 0 deletions crates/sqlbuild-cli/src/argv/main/command_settings_arguments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Turn frozen settings back into one command's flags, against the same table as the command-line parser.

use std::collections::BTreeMap;

use serde_json::Value as Json;

use crate::argv::_helpers::job_settings::{setting_argument, validate_mutex};
use crate::argv::_helpers::table::root_parser;

/// Produce argument tokens for `command`, never shell text; a setting the command lacks is refused.
pub fn command_settings_arguments(
command: &str,
settings: &BTreeMap<String, Json>,
) -> Result<Vec<String>, String> {
let parser = root_parser()
.actions
.iter()
.flat_map(|action| &action.parsers)
.find(|(name, _)| name == command)
.map(|(_, parser)| parser)
.ok_or_else(|| format!("{command} command is unavailable"))?;
let mut arguments: Vec<String> = Vec::new();
for (name, value) in settings {
if let Some(argument) = setting_argument(parser, name, value)? {
arguments.push(argument);
}
}
validate_mutex(parser, &arguments)?;
Ok(arguments)
}
Loading
Loading