diff --git a/crates/sqlbuild-analysis/src/graph/tests.rs b/crates/sqlbuild-analysis/src/graph/tests.rs index a9d1498b0f..26c1d3f80a 100644 --- a/crates/sqlbuild-analysis/src/graph/tests.rs +++ b/crates/sqlbuild-analysis/src/graph/tests.rs @@ -2,5 +2,7 @@ mod graph; #[path = "tests/helpers.rs"] mod helpers; +#[path = "tests/test_selector_scenarios.rs"] +mod selector_scenarios; #[path = "tests/test_types.rs"] mod test_types; diff --git a/crates/sqlbuild-analysis/src/graph/tests/helpers.rs b/crates/sqlbuild-analysis/src/graph/tests/helpers.rs index 78a99a86be..ec596f7f1a 100644 --- a/crates/sqlbuild-analysis/src/graph/tests/helpers.rs +++ b/crates/sqlbuild-analysis/src/graph/tests/helpers.rs @@ -1,13 +1,15 @@ +use std::collections::BTreeSet; + use crate::assembly::project::types::ObjectKey; use crate::graph::errors::SelectorError; use crate::graph::main::build_project_graph::build_project_graph; use crate::graph::main::resolve_selectors::resolve_selectors; -use crate::graph::models::{GraphResource, ProjectGraph}; +use crate::graph::models::{GraphResource, ParsedSelector, ProjectGraph}; use crate::graph::tests::test_types::SelectorFailure; type OwnedFailure = (String, String, Option); -type ResourceSpec = ( +pub(super) type ResourceSpec = ( &'static str, &'static str, &'static [(&'static str, &'static str)], @@ -48,8 +50,101 @@ pub(super) fn key(pair: &(&str, &str)) -> ObjectKey { (pair.0.to_owned(), pair.1.to_owned()) } +/// Two sources feeding two models that a third joins, plus a seed nothing reads. +pub(super) const DIAMOND_PROJECT: &[ResourceSpec] = &[ + ( + "model", + "orders", + &[("source", "raw_orders")], + &["nightly", "staging"], + None, + ), + ( + "model", + "customers", + &[("source", "raw_customers")], + &[], + None, + ), + ( + "model", + "joined", + &[("model", "orders"), ("model", "customers")], + &["nightly"], + None, + ), + ("source", "raw_orders", &[], &[], None), + ("source", "raw_customers", &[], &[], None), + ("seed", "codes", &[], &[], None), +]; + +/// Staging, nested staging, intermediate and mart folders over two sources. +pub(super) const FOLDER_PROJECT: &[ResourceSpec] = &[ + ( + "model", + "stg_orders", + &[("source", "raw_orders")], + &[], + Some("staging"), + ), + ( + "model", + "stg_customers", + &[("source", "raw_customers")], + &[], + Some("staging"), + ), + ( + "model", + "stg_deep", + &[("source", "raw_orders")], + &[], + Some("staging/raw"), + ), + ( + "model", + "int_enriched", + &[("model", "stg_orders"), ("model", "stg_customers")], + &[], + Some("intermediate"), + ), + ( + "model", + "fact_orders", + &[("model", "int_enriched")], + &[], + Some("marts"), + ), + ("source", "raw_orders", &[], &[], None), + ("source", "raw_customers", &[], &[], None), +]; + +/// A model read through a table function by another model. +pub(super) const TABLE_FUNCTION_PROJECT: &[ResourceSpec] = &[ + ("model", "orders", &[], &[], None), + ( + "table_fn", + "customer_orders", + &[("model", "orders")], + &[], + None, + ), + ( + "model", + "customer_order_summary", + &[("table_fn", "customer_orders")], + &[], + None, + ), +]; + pub(super) fn orders_graph() -> ProjectGraph { - let resources: Vec = ORDERS_PROJECT + project_graph(ORDERS_PROJECT) +} + +/// The graph of a resource table. +pub(super) fn project_graph(project: &[ResourceSpec]) -> ProjectGraph { + let resources: Vec = project .iter() .map(|(kind, name, deps, tags, folder)| GraphResource { key: key(&(kind, name)), @@ -63,13 +158,31 @@ pub(super) fn orders_graph() -> ProjectGraph { /// The resolved keys, or the error's `(code, message, help)`. pub(super) fn resolved(select: &[&str], exclude: &[&str]) -> Result, OwnedFailure> { + resolved_in(&orders_graph(), select, exclude) +} + +/// The names `select` minus `exclude` resolves to in a project, sorted, or the error. +pub(super) fn resolved_names( + project: &[ResourceSpec], + select: &[&str], + exclude: &[&str], +) -> Result, OwnedFailure> { + resolved_in(&project_graph(project), select, exclude) + .map(|keys| keys.into_iter().map(|(_, name)| name).collect()) +} + +fn resolved_in( + graph: &ProjectGraph, + select: &[&str], + exclude: &[&str], +) -> Result, OwnedFailure> { let owned = |values: &[&str]| { values .iter() .map(|value| (*value).to_owned()) .collect::>() }; - resolve_selectors(&orders_graph(), &owned(select), &owned(exclude)).map_err( + resolve_selectors(graph, &owned(select), &owned(exclude)).map_err( |SelectorError { code, message, @@ -87,3 +200,28 @@ pub(super) fn keys(pairs: &[(&str, &str)]) -> Vec { pub(super) fn failure((code, message, help): SelectorFailure) -> OwnedFailure { (code.to_owned(), message.to_owned(), help.map(str::to_owned)) } + +/// A sorted name set. +pub(super) fn names(values: &[&str]) -> BTreeSet { + values.iter().map(|value| (*value).to_owned()).collect() +} + +/// A `kind:value` selector with its expansion flags. +pub(super) fn kind(kind: &str, value: &str, upstream: bool, downstream: bool) -> ParsedSelector { + ParsedSelector::Kind { + kind: kind.to_owned(), + value: value.to_owned(), + upstream, + downstream, + } +} + +/// A `start~end` selector with its expansion flags. +pub(super) fn path(start: &str, end: &str, upstream: bool, downstream: bool) -> ParsedSelector { + ParsedSelector::Path { + start: start.to_owned(), + end: end.to_owned(), + upstream, + downstream, + } +} diff --git a/crates/sqlbuild-analysis/src/graph/tests/test_selector_scenarios.rs b/crates/sqlbuild-analysis/src/graph/tests/test_selector_scenarios.rs new file mode 100644 index 0000000000..690b589d4a --- /dev/null +++ b/crates/sqlbuild-analysis/src/graph/tests/test_selector_scenarios.rs @@ -0,0 +1,561 @@ +use crate::graph::main::parse_selector::parse_selector; +use crate::graph::tests::helpers::{ + DIAMOND_PROJECT, FOLDER_PROJECT, TABLE_FUNCTION_PROJECT, failure, kind, names, path, + resolved_names, +}; +use crate::graph::tests::test_types::{ + ParseSelectorErrorTestCase, ParseSelectorTestCase, ProjectSelectorErrorTestCase, + SelectedNamesTestCase, +}; + +#[test] +fn given_raw_selector_when_parsing_then_returns_kind_value_and_expansion() { + let test_cases = [ + ParseSelectorTestCase { + description: "bare model name", + raw: "orders", + expected_selector: kind("name", "orders", false, false), + }, + ParseSelectorTestCase { + description: "upstream expansion", + raw: "+orders", + expected_selector: kind("name", "orders", true, false), + }, + ParseSelectorTestCase { + description: "downstream expansion", + raw: "orders+", + expected_selector: kind("name", "orders", false, true), + }, + ParseSelectorTestCase { + description: "bidirectional expansion", + raw: "+orders+", + expected_selector: kind("name", "orders", true, true), + }, + ParseSelectorTestCase { + description: "seed typed selector", + raw: "seed:country_codes", + expected_selector: kind("seed", "country_codes", false, false), + }, + ParseSelectorTestCase { + description: "source typed selector", + raw: "source:raw_orders", + expected_selector: kind("source", "raw_orders", false, false), + }, + ParseSelectorTestCase { + description: "tag typed selector", + raw: "tag:nightly", + expected_selector: kind("tag", "nightly", false, false), + }, + ParseSelectorTestCase { + description: "path typed selector", + raw: "path:models/staging", + expected_selector: kind("path", "models/staging", false, false), + }, + ParseSelectorTestCase { + description: "typed selector with upstream expansion", + raw: "+seed:country_codes", + expected_selector: kind("seed", "country_codes", true, false), + }, + ParseSelectorTestCase { + description: "task typed selector", + raw: "task:prepare_orders", + expected_selector: kind("task", "prepare_orders", false, false), + }, + ParseSelectorTestCase { + description: "asset typed selector", + raw: "asset:export_orders", + expected_selector: kind("asset", "export_orders", false, false), + }, + ParseSelectorTestCase { + description: "loader typed selector", + raw: "loader:load_events", + expected_selector: kind("loader", "load_events", false, false), + }, + ParseSelectorTestCase { + description: "check typed selector", + raw: "check:check_orders_export", + expected_selector: kind("check", "check_orders_export", false, false), + }, + ParseSelectorTestCase { + description: "path selector", + raw: "raw~orders", + expected_selector: path("raw", "orders", false, false), + }, + ParseSelectorTestCase { + description: "path selector with upstream expansion", + raw: "+raw~orders", + expected_selector: path("raw", "orders", true, false), + }, + ParseSelectorTestCase { + description: "path selector with downstream expansion", + raw: "raw~orders+", + expected_selector: path("raw", "orders", false, true), + }, + ParseSelectorTestCase { + description: "path selector with endpoint expansion on both sides", + raw: "+raw~orders+", + expected_selector: path("raw", "orders", true, true), + }, + ParseSelectorTestCase { + description: "trailing slash is a folder", + raw: "staging/", + expected_selector: kind("path", "staging", false, false), + }, + ParseSelectorTestCase { + description: "leading slash is a folder", + raw: "/staging", + expected_selector: kind("path", "staging", false, false), + }, + ParseSelectorTestCase { + description: "nested folder with trailing slash", + raw: "staging/orders/", + expected_selector: kind("path", "staging/orders", false, false), + }, + ParseSelectorTestCase { + description: "nested folder without trailing slash", + raw: "staging/orders", + expected_selector: kind("path", "staging/orders", false, false), + }, + ParseSelectorTestCase { + description: "folder with upstream expansion", + raw: "+staging/", + expected_selector: kind("path", "staging", true, false), + }, + ParseSelectorTestCase { + description: "folder with downstream expansion", + raw: "staging/+", + expected_selector: kind("path", "staging", false, true), + }, + ]; + + for test_case in test_cases { + assert_eq!( + parse_selector(test_case.raw), + Ok(test_case.expected_selector), + "{}", + test_case.description + ); + } +} + +#[test] +fn given_invalid_selector_when_parsing_then_fails_with_code() { + let test_cases = [ + ParseSelectorErrorTestCase { + description: "empty selector", + raw: "", + expected_code: "S001", + }, + ParseSelectorErrorTestCase { + description: "plus inside a path selector", + raw: "a~+b", + expected_code: "S002", + }, + ParseSelectorErrorTestCase { + description: "unknown typed prefix", + raw: "unknown:value", + expected_code: "S005", + }, + ParseSelectorErrorTestCase { + description: "empty value after colon", + raw: "seed:", + expected_code: "S006", + }, + ParseSelectorErrorTestCase { + description: "plus only", + raw: "+", + expected_code: "S004", + }, + ParseSelectorErrorTestCase { + description: "empty side of a path selector", + raw: "a~", + expected_code: "S003", + }, + ]; + + for test_case in test_cases { + assert_eq!( + parse_selector(test_case.raw).map_err(|error| error.code), + Err(test_case.expected_code), + "{}", + test_case.description + ); + } +} + +#[test] +fn given_selectors_when_resolving_then_selects_expected_names() { + let test_cases = [ + SelectedNamesTestCase { + description: "single model by name", + project: DIAMOND_PROJECT, + select: &["orders"], + exclude: &[], + expected_names: &["orders"], + }, + SelectedNamesTestCase { + description: "upstream expansion", + project: DIAMOND_PROJECT, + select: &["+joined"], + exclude: &[], + expected_names: &[ + "customers", + "joined", + "orders", + "raw_customers", + "raw_orders", + ], + }, + SelectedNamesTestCase { + description: "downstream expansion", + project: DIAMOND_PROJECT, + select: &["orders+"], + exclude: &[], + expected_names: &["joined", "orders"], + }, + SelectedNamesTestCase { + description: "name glob across resource types", + project: DIAMOND_PROJECT, + select: &["*orders"], + exclude: &[], + expected_names: &["orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "downstream expansion from every glob match", + project: DIAMOND_PROJECT, + select: &["*orders+"], + exclude: &[], + expected_names: &["joined", "orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "exclusion by name glob", + project: DIAMOND_PROJECT, + select: &[], + exclude: &["raw_*"], + expected_names: &["codes", "customers", "joined", "orders"], + }, + SelectedNamesTestCase { + description: "union of select arguments", + project: DIAMOND_PROJECT, + select: &["orders", "customers"], + exclude: &[], + expected_names: &["customers", "orders"], + }, + SelectedNamesTestCase { + description: "union of space-separated tokens", + project: DIAMOND_PROJECT, + select: &["orders customers"], + exclude: &[], + expected_names: &["customers", "orders"], + }, + SelectedNamesTestCase { + description: "excluded model subtracted from selection", + project: DIAMOND_PROJECT, + select: &["orders", "customers"], + exclude: &["customers"], + expected_names: &["orders"], + }, + SelectedNamesTestCase { + description: "empty select is every resource", + project: DIAMOND_PROJECT, + select: &[], + exclude: &[], + expected_names: &[ + "codes", + "customers", + "joined", + "orders", + "raw_customers", + "raw_orders", + ], + }, + SelectedNamesTestCase { + description: "exclusion from every resource", + project: DIAMOND_PROJECT, + select: &[], + exclude: &["customers"], + expected_names: &["codes", "joined", "orders", "raw_customers", "raw_orders"], + }, + SelectedNamesTestCase { + description: "seed by typed selector", + project: DIAMOND_PROJECT, + select: &["seed:codes"], + exclude: &[], + expected_names: &["codes"], + }, + SelectedNamesTestCase { + description: "source by typed selector", + project: DIAMOND_PROJECT, + select: &["source:raw_orders"], + exclude: &[], + expected_names: &["raw_orders"], + }, + SelectedNamesTestCase { + description: "sources by typed name glob", + project: DIAMOND_PROJECT, + select: &["source:raw_*"], + exclude: &[], + expected_names: &["raw_customers", "raw_orders"], + }, + SelectedNamesTestCase { + description: "intersection of comma-separated tokens", + project: DIAMOND_PROJECT, + select: &["orders+,+joined"], + exclude: &[], + expected_names: &["joined", "orders"], + }, + SelectedNamesTestCase { + description: "bidirectional expansion", + project: DIAMOND_PROJECT, + select: &["+orders+"], + exclude: &[], + expected_names: &["joined", "orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "path selector", + project: DIAMOND_PROJECT, + select: &["raw_orders~joined"], + exclude: &[], + expected_names: &["joined", "orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "path selector with start upstream expansion", + project: DIAMOND_PROJECT, + select: &["+orders~joined"], + exclude: &[], + expected_names: &["joined", "orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "path selector with end downstream expansion", + project: DIAMOND_PROJECT, + select: &["raw_orders~orders+"], + exclude: &[], + expected_names: &["joined", "orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "path selector with endpoint expansion on both sides", + project: DIAMOND_PROJECT, + select: &["+raw_orders~orders+"], + exclude: &[], + expected_names: &["joined", "orders", "raw_orders"], + }, + SelectedNamesTestCase { + description: "expanded exclusion subtracted from expanded selection", + project: DIAMOND_PROJECT, + select: &["+joined"], + exclude: &["orders+"], + expected_names: &["customers", "raw_customers", "raw_orders"], + }, + SelectedNamesTestCase { + description: "models by tag", + project: DIAMOND_PROJECT, + select: &["tag:nightly"], + exclude: &[], + expected_names: &["joined", "orders"], + }, + SelectedNamesTestCase { + description: "tag with downstream expansion", + project: DIAMOND_PROJECT, + select: &["tag:staging+"], + exclude: &[], + expected_names: &["joined", "orders"], + }, + SelectedNamesTestCase { + description: "tag intersected with a name", + project: DIAMOND_PROJECT, + select: &["tag:nightly,orders"], + exclude: &[], + expected_names: &["orders"], + }, + SelectedNamesTestCase { + description: "tag excluded from selection", + project: DIAMOND_PROJECT, + select: &["orders", "customers", "joined"], + exclude: &["tag:nightly"], + expected_names: &["customers"], + }, + SelectedNamesTestCase { + description: "consumer selection adds the table function it reads", + project: TABLE_FUNCTION_PROJECT, + select: &["customer_order_summary"], + exclude: &[], + expected_names: &["customer_order_summary", "customer_orders"], + }, + SelectedNamesTestCase { + description: "upstream expansion through a table function", + project: TABLE_FUNCTION_PROJECT, + select: &["+customer_order_summary"], + exclude: &[], + expected_names: &["customer_order_summary", "customer_orders", "orders"], + }, + SelectedNamesTestCase { + description: "table function downstream expansion reaches its consumer", + project: TABLE_FUNCTION_PROJECT, + select: &["customer_orders+"], + exclude: &[], + expected_names: &["customer_order_summary", "customer_orders"], + }, + SelectedNamesTestCase { + description: "folder including nested folders", + project: FOLDER_PROJECT, + select: &["path:models/staging"], + exclude: &[], + expected_names: &["stg_customers", "stg_deep", "stg_orders"], + }, + SelectedNamesTestCase { + description: "models root", + project: FOLDER_PROJECT, + select: &["path:models"], + exclude: &[], + expected_names: &[ + "fact_orders", + "int_enriched", + "stg_customers", + "stg_deep", + "stg_orders", + ], + }, + SelectedNamesTestCase { + description: "trailing slash with explicit root", + project: FOLDER_PROJECT, + select: &["models/staging/"], + exclude: &[], + expected_names: &["stg_customers", "stg_deep", "stg_orders"], + }, + SelectedNamesTestCase { + description: "leading slash with explicit root", + project: FOLDER_PROJECT, + select: &["/models/staging"], + exclude: &[], + expected_names: &["stg_customers", "stg_deep", "stg_orders"], + }, + SelectedNamesTestCase { + description: "folder with one model", + project: FOLDER_PROJECT, + select: &["path:models/marts"], + exclude: &[], + expected_names: &["fact_orders"], + }, + SelectedNamesTestCase { + description: "folder with downstream expansion", + project: FOLDER_PROJECT, + select: &["path:models/staging+"], + exclude: &[], + expected_names: &[ + "fact_orders", + "int_enriched", + "stg_customers", + "stg_deep", + "stg_orders", + ], + }, + SelectedNamesTestCase { + description: "folder with upstream expansion", + project: FOLDER_PROJECT, + select: &["+path:models/marts"], + exclude: &[], + expected_names: &[ + "fact_orders", + "int_enriched", + "raw_customers", + "raw_orders", + "stg_customers", + "stg_orders", + ], + }, + SelectedNamesTestCase { + description: "folder intersected with a name", + project: FOLDER_PROJECT, + select: &["path:models/staging,stg_orders"], + exclude: &[], + expected_names: &["stg_orders"], + }, + SelectedNamesTestCase { + description: "folder excluded from selection", + project: FOLDER_PROJECT, + select: &["+fact_orders"], + exclude: &["path:models/staging"], + expected_names: &["fact_orders", "int_enriched", "raw_customers", "raw_orders"], + }, + SelectedNamesTestCase { + description: "trailing slash selects a one-model folder", + project: FOLDER_PROJECT, + select: &["models/intermediate/"], + exclude: &[], + expected_names: &["int_enriched"], + }, + SelectedNamesTestCase { + description: "nested folder without its parent", + project: FOLDER_PROJECT, + select: &["path:models/staging/raw"], + exclude: &[], + expected_names: &["stg_deep"], + }, + ]; + + for test_case in test_cases { + assert_eq!( + resolved_names(test_case.project, test_case.select, test_case.exclude), + Ok(names(test_case.expected_names)), + "{}", + test_case.description + ); + } +} + +#[test] +fn given_unresolvable_selectors_when_resolving_then_fails_with_message() { + let test_cases = [ + ProjectSelectorErrorTestCase { + description: "unknown name", + project: DIAMOND_PROJECT, + select: &["nonexistent_model"], + expected_error: ("S007", "unknown selector name 'nonexistent_model'", None), + }, + ProjectSelectorErrorTestCase { + description: "name glob matching nothing", + project: DIAMOND_PROJECT, + select: &["missing_*"], + expected_error: ("S007", "unknown selector pattern 'missing_*'", None), + }, + ProjectSelectorErrorTestCase { + description: "folder without an explicit root", + project: FOLDER_PROJECT, + select: &["path:nonexistent"], + expected_error: ( + "S012", + "path selectors require an explicit root: use 'models/' or 'python/'", + None, + ), + }, + ProjectSelectorErrorTestCase { + description: "slash folder without an explicit root", + project: FOLDER_PROJECT, + select: &["nonexistent/"], + expected_error: ( + "S012", + "path selectors require an explicit root: use 'models/' or 'python/'", + None, + ), + }, + ProjectSelectorErrorTestCase { + description: "folder with no models", + project: FOLDER_PROJECT, + select: &["path:models/nonexistent"], + expected_error: ( + "S009", + "no models found under path 'models/nonexistent'.", + None, + ), + }, + ]; + + for test_case in test_cases { + assert_eq!( + resolved_names(test_case.project, test_case.select, &[]), + Err(failure(test_case.expected_error)), + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlbuild-analysis/src/graph/tests/test_types.rs b/crates/sqlbuild-analysis/src/graph/tests/test_types.rs index affff90bf4..2bb69273a2 100644 --- a/crates/sqlbuild-analysis/src/graph/tests/test_types.rs +++ b/crates/sqlbuild-analysis/src/graph/tests/test_types.rs @@ -1,3 +1,6 @@ +use crate::graph::models::ParsedSelector; +use crate::graph::tests::helpers::ResourceSpec; + pub(super) struct SelectorTestCase { pub(super) description: &'static str, pub(super) select: &'static [&'static str], @@ -22,3 +25,30 @@ pub(super) struct CloseMatchTestCase { pub(super) cutoff: f64, pub(super) expected_matches: &'static [&'static str], } + +pub(super) struct ParseSelectorTestCase { + pub(super) description: &'static str, + pub(super) raw: &'static str, + pub(super) expected_selector: ParsedSelector, +} + +pub(super) struct ParseSelectorErrorTestCase { + pub(super) description: &'static str, + pub(super) raw: &'static str, + pub(super) expected_code: &'static str, +} + +pub(super) struct SelectedNamesTestCase { + pub(super) description: &'static str, + pub(super) project: &'static [ResourceSpec], + pub(super) select: &'static [&'static str], + pub(super) exclude: &'static [&'static str], + pub(super) expected_names: &'static [&'static str], +} + +pub(super) struct ProjectSelectorErrorTestCase { + pub(super) description: &'static str, + pub(super) project: &'static [ResourceSpec], + pub(super) select: &'static [&'static str], + pub(super) expected_error: SelectorFailure, +} diff --git a/crates/sqlbuild-analysis/src/planner_selection/tests/helpers.rs b/crates/sqlbuild-analysis/src/planner_selection/tests/helpers.rs index 9a83291566..2bc9af1d3e 100644 --- a/crates/sqlbuild-analysis/src/planner_selection/tests/helpers.rs +++ b/crates/sqlbuild-analysis/src/planner_selection/tests/helpers.rs @@ -55,3 +55,26 @@ pub(super) fn models() -> Vec { model("report", &["daily"], "models/marts"), ] } + +/// Unit tests `order_status`, `order_totals_check` and `customer_status` beside four resources. +pub(super) fn order_selector_names() -> UnitTestSelectorNames { + UnitTestSelectorNames { + tests: name_set(&["customer_status", "order_status", "order_totals_check"]), + resources: name_set(&["customers", "order_totals", "orders", "raw_orders"]), + } +} + +/// A split with resource selectors, selected and excluded unit tests, and whether models run. +pub(super) fn full_split( + (select, exclude): (&[&str], &[&str]), + (tests, excluded_tests): (&[&str], &[&str]), + models_selected: bool, +) -> UnitTestSelectorSplit { + UnitTestSelectorSplit { + select: texts(select), + exclude: texts(exclude), + test_names: name_set(tests), + excluded_test_names: name_set(excluded_tests), + models_selected, + } +} diff --git a/crates/sqlbuild-analysis/src/planner_selection/tests/test_planner_selection.rs b/crates/sqlbuild-analysis/src/planner_selection/tests/test_planner_selection.rs index 80b6a0c0b2..e93a0bc9f7 100644 --- a/crates/sqlbuild-analysis/src/planner_selection/tests/test_planner_selection.rs +++ b/crates/sqlbuild-analysis/src/planner_selection/tests/test_planner_selection.rs @@ -2,7 +2,9 @@ use crate::planner_selection::main::split_selector_expansion::split_selector_exp use crate::planner_selection::main::split_unit_test_selectors::split_unit_test_selectors; use crate::planner_selection::main::sqlbuild_model_selector_names::sqlbuild_model_selector_names; use crate::planner_selection::models::{ModelSelectorNames, SelectorExpansion, SelectorGroups}; -use crate::planner_selection::tests::helpers::{failure, models, selector_names, split, texts}; +use crate::planner_selection::tests::helpers::{ + failure, full_split, models, order_selector_names, selector_names, split, texts, +}; use crate::planner_selection::tests::test_types::{ ExpansionTestCase, ModelSelectorTestCase, UnitTestSplitTestCase, }; @@ -157,6 +159,11 @@ fn given_model_selector_term_when_resolving_then_returns_model_names() { term: "path:models\\staging", expected_names: names(&["orders", "customers"], Some("path:models/staging")), }, + ModelSelectorTestCase { + description: "path selects the folder's models", + term: "path:models/marts", + expected_names: names(&["report"], None), + }, ModelSelectorTestCase { description: "other terms", term: "source:raw", @@ -172,3 +179,165 @@ fn given_model_selector_term_when_resolving_then_returns_model_names() { ); } } + +#[test] +fn given_order_selectors_when_splitting_unit_tests_then_tests_and_resources_separate() { + let test_cases = [ + UnitTestSplitTestCase { + description: "a bare test name selects only that test and no models", + select: texts(&["order_status"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Ok(full_split((&[], &[]), (&["order_status"], &[]), false)), + }, + UnitTestSplitTestCase { + description: "test: selectors union with model selectors", + select: texts(&["test:customer_status orders+", "tag:daily"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Ok(full_split( + (&["orders+", "tag:daily"], &[]), + (&["customer_status"], &[]), + true, + )), + }, + UnitTestSplitTestCase { + description: "test: patterns match unit tests with fnmatch", + select: texts(&["test:order_*"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Ok(full_split( + (&[], &[]), + (&["order_status", "order_totals_check"], &[]), + false, + )), + }, + UnitTestSplitTestCase { + description: "a bare pattern that matches resources keeps its resource meaning", + select: texts(&["order*"]), + exclude: texts(&["test:order_totals_check"]), + accepts_unit_tests: true, + expected_split: Ok(full_split( + (&["order*"], &[]), + (&[], &["order_totals_check"]), + true, + )), + }, + UnitTestSplitTestCase { + description: "empty and blank select values are kept and select nothing", + select: texts(&["", " "]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Ok(full_split((&["", " "], &[]), (&[], &[]), true)), + }, + UnitTestSplitTestCase { + description: "a test-only selector beside an empty value still selects no models", + select: texts(&["", "order_status"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Ok(full_split((&[""], &[]), (&["order_status"], &[]), true)), + }, + UnitTestSplitTestCase { + description: "selectors without unit tests pass through unchanged", + select: texts(&["orders", "customers,tag:daily", "models/marts"]), + exclude: texts(&["raw_orders"]), + accepts_unit_tests: true, + expected_split: Ok(full_split( + ( + &["orders", "customers,tag:daily", "models/marts"], + &["raw_orders"], + ), + (&[], &[]), + true, + )), + }, + UnitTestSplitTestCase { + description: "a command that runs no unit tests rejects a bare test name", + select: texts(&["order_status"]), + exclude: Vec::new(), + accepts_unit_tests: false, + expected_split: Err(failure( + "S013", + "selector 'order_status' selects a unit test; only `sqb test` and `sqb build` accept unit-test selectors", + None, + )), + }, + UnitTestSplitTestCase { + description: "a command that runs no unit tests rejects test: selectors", + select: texts(&["orders test:customer_status"]), + exclude: Vec::new(), + accepts_unit_tests: false, + expected_split: Err(failure( + "S013", + "selector 'test:customer_status' selects a unit test; only `sqb test` and `sqb build` accept unit-test selectors", + None, + )), + }, + UnitTestSplitTestCase { + description: "an unknown test: name suggests the nearest unit tests", + select: texts(&["test:order_stats"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Err(failure( + "S007", + "unknown unit test name 'order_stats'", + Some("did you mean 'order_status'?"), + )), + }, + UnitTestSplitTestCase { + description: "an unknown bare name suggests the closest unit test", + select: texts(&["customer_stat"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Err(failure( + "S007", + "unknown selector name 'customer_stat'", + Some("did you mean 'customer_status'?"), + )), + }, + UnitTestSplitTestCase { + description: "graph expansion of a unit test is refused", + select: texts(&["+order_status"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Err(failure( + "S013", + "selector '+order_status' expands a unit test with '+'; unit tests have no lineage, so select them by name without '+'", + None, + )), + }, + UnitTestSplitTestCase { + description: "intersecting a unit test is refused", + select: texts(&["order_status,tag:daily"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Err(failure( + "S013", + "selector 'order_status,tag:daily' intersects a unit test with ','; unit tests can only be selected whole, by name", + None, + )), + }, + UnitTestSplitTestCase { + description: "a stray trailing comma after a unit test reports the empty selector", + select: texts(&["test:order_status,"]), + exclude: Vec::new(), + accepts_unit_tests: true, + expected_split: Err(failure("S001", "empty selector", None)), + }, + ]; + for test_case in test_cases { + assert_eq!( + split_unit_test_selectors( + SelectorGroups { + select: &test_case.select, + exclude: &test_case.exclude, + }, + &order_selector_names(), + test_case.accepts_unit_tests, + ), + test_case.expected_split, + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs index 50d18b2b66..4c8bd38eec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs @@ -16,7 +16,7 @@ use crate::bindings::_helpers::records::ids::RecordId; /// The phase name of one target compile, before the target name. const COMPILE_PHASE_PREFIX: &str = "compile_"; -/// The `ClonePipelineResult` fields after the two projects, in `run_clone_planning` order. +/// The `ClonePipelineResult` fields after the two projects, in `timed_clone_planning` order. const PLANNED_FIELDS: [&str; 8] = [ "clone_plan", "destination_source_entries", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs index 8dda1953ed..f39fbfaa59 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs @@ -3,11 +3,8 @@ use std::collections::BTreeSet; use pyo3::exceptions::PyValueError; -use pyo3::prelude::{ - Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, -}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; -use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::cli_text::models::CliStyle; use sqlbuild_core::freshness_text::main::freshness_exit_code::freshness_exit_code; use sqlbuild_core::freshness_text::main::freshness_json::freshness_json; @@ -272,8 +269,6 @@ fn write_json_output( } /// Run one `sqb freshness` request. -#[pyfunction] -#[pyo3(signature = (*, request))] pub(crate) fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let fail_on_stale: bool = native_field(request, "fail_on_stale")?.extract()?; let fail_on_error: bool = native_field(request, "fail_on_error")?.extract()?; @@ -345,8 +340,3 @@ pub(crate) fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) (fail_on_stale, fail_on_error), )) } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(run_freshness_command, module)?)?; - Ok(()) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs index 86b94ed5e2..58007a4b2d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs @@ -589,20 +589,7 @@ fn observe_adapter_sources_freshness<'py>( observe_adapter_batch(&observer, &sources) } -/// Build the state record of one observation. -#[pyfunction] -#[pyo3(signature = (*, observation, source, run_id))] -fn source_freshness_record_from_observation<'py>( - observation: &Bound<'py, PyAny>, - source: &Bound<'py, PyAny>, - run_id: &Bound<'py, PyAny>, -) -> PyResult> { - observation_record(observation, source, run_id) -} - /// An aware freshness `datetime` in UTC; a naive value is an observation error. -#[pyfunction] -#[pyo3(signature = (*, value, field_name))] pub(crate) fn require_aware_utc_datetime<'py>( value: &Bound<'py, PyAny>, field_name: &str, @@ -634,15 +621,10 @@ pub(crate) fn normalize_observation( } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(require_aware_utc_datetime, module)?)?; module.add_function(wrap_pyfunction!( observe_configured_source_freshness, module )?)?; module.add_function(wrap_pyfunction!(observe_adapter_sources_freshness, module)?)?; - module.add_function(wrap_pyfunction!( - source_freshness_record_from_observation, - module - )?)?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/registration.rs index 4f10818a2a..d81d40f9e0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/registration.rs @@ -3,13 +3,11 @@ use pyo3::prelude::{Bound, PyModule, PyResult}; use crate::bindings::_helpers::freshness::{ - exception_types, freshness_command, freshness_observe, observation, planning, publication, - state_io, + exception_types, freshness_observe, observation, planning, publication, state_io, }; pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { exception_types::register(module)?; - freshness_command::register(module)?; freshness_observe::register(module)?; observation::register(module)?; planning::register(module)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs index a6c9d8abde..7f2f8e5f0c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs @@ -70,8 +70,6 @@ pub(crate) fn optional_text_check(value: &Bound<'_, PyAny>, field_name: &str) -> } /// Validate a timezone-aware UTC timestamp. -#[pyfunction(name = "validate_lifecycle_timestamp")] -#[pyo3(signature = (*, value))] pub(crate) fn timestamp_check(value: &Bound<'_, PyAny>) -> PyResult<()> { let py = value.py(); let datetime: Bound<'_, PyAny> = imported(py, "datetime", "datetime")?; @@ -90,8 +88,6 @@ pub(crate) fn timestamp_check(value: &Bound<'_, PyAny>) -> PyResult<()> { } /// Validate correlations and safe payload fields for a catalogued event. -#[pyfunction(name = "validate_known_lifecycle_event")] -#[pyo3(signature = (*, event))] pub(crate) fn known_event_check(event: &Bound<'_, PyAny>) -> PyResult<()> { let mut correlations: Vec<(&'static str, bool)> = Vec::new(); for field in CORRELATION_FIELDS { @@ -111,6 +107,5 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(frozen_json, module)?)?; module.add_function(wrap_pyfunction!(required_text_check, module)?)?; module.add_function(wrap_pyfunction!(optional_text_check, module)?)?; - module.add_function(wrap_pyfunction!(timestamp_check, module)?)?; - module.add_function(wrap_pyfunction!(known_event_check, module)?) + Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs index b237c93be5..f430eaa84d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs @@ -13,9 +13,7 @@ use sqlbuild_core::plan_display::main::cursor_bound_display::cursor_bound_displa use sqlbuild_core::plan_display::main::effective_microbatch_batch_size::effective_microbatch_batch_size; use sqlbuild_core::plan_display::main::format_no_input_rows::format_no_input_rows as no_input_rows; use sqlbuild_core::plan_display::main::format_waiting_on_empty_inputs::format_waiting_on_empty_inputs as waiting_on_empty_inputs; -use sqlbuild_core::plan_display::main::model_execution_annotation::model_execution_annotation as execution_annotation; use sqlbuild_core::plan_display::main::model_materialization_label::model_materialization_label as materialization_label; -use sqlbuild_core::plan_display::main::model_resource_type::model_resource_type as resource_type; use sqlbuild_core::plan_display::models::{ModelLabelFacts, ParsedBound}; use crate::bindings::_helpers::boundary::python_calls::imported; @@ -93,8 +91,6 @@ pub(crate) fn inclusive_cursor_end( } /// A fully bounded inclusive operator override as an executable interval. -#[pyfunction] -#[pyo3(signature = (*, start_cursor_override, end_cursor_override, cursor_type, cursor_grain))] pub(crate) fn resolve_bounded_cursor_override<'py>( start_cursor_override: &Bound<'py, PyAny>, end_cursor_override: &Bound<'py, PyAny>, @@ -167,23 +163,10 @@ pub(crate) fn cursor_bound_display( } /// The full materialization label used in plan summaries. -#[pyfunction] pub(crate) fn model_materialization_label(entry: &Bound<'_, PyAny>) -> PyResult { Ok(materialization_label(&label_facts(entry)?)) } -/// The left-column resource type used in execution progress rows. -#[pyfunction] -fn model_resource_type(entry: &Bound<'_, PyAny>) -> PyResult<&'static str> { - Ok(resource_type(optional_facts(entry)?.as_ref())) -} - -/// The model annotation used in execution progress rows. -#[pyfunction] -fn model_execution_annotation(entry: &Bound<'_, PyAny>) -> PyResult { - Ok(execution_annotation(optional_facts(entry)?.as_ref())) -} - fn names(input_names: &Bound<'_, PyAny>) -> PyResult> { input_names .try_iter()? @@ -192,7 +175,6 @@ fn names(input_names: &Bound<'_, PyAny>) -> PyResult> { } /// Explain that a cursor window is empty because its inputs have no rows. -#[pyfunction] pub(crate) fn format_no_input_rows(input_names: &Bound<'_, PyAny>) -> PyResult { Ok(no_input_rows(&names(input_names)?)) } @@ -263,12 +245,7 @@ pub(crate) fn aligned_timestamp_bounds<'py>( pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(inclusive_cursor_end, module)?)?; - module.add_function(wrap_pyfunction!(resolve_bounded_cursor_override, module)?)?; module.add_function(wrap_pyfunction!(cursor_bound_display, module)?)?; - module.add_function(wrap_pyfunction!(model_materialization_label, module)?)?; - module.add_function(wrap_pyfunction!(model_resource_type, module)?)?; - module.add_function(wrap_pyfunction!(model_execution_annotation, module)?)?; - module.add_function(wrap_pyfunction!(format_no_input_rows, module)?)?; module.add_function(wrap_pyfunction!(format_waiting_on_empty_inputs, module)?)?; module.add_function(wrap_pyfunction!( resolve_effective_microbatch_batch_size, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs index 8e89db211a..9e98797d20 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs @@ -13,8 +13,6 @@ const INCREMENTAL_MATERIALIZATION: &str = "incremental"; const CREATE_TABLE_ACTION: &str = "create_table"; /// Each selected model's change with the backfill its own `replay_on_change` decided. -#[pyfunction] -#[pyo3(signature = (*, scope, changes))] pub(crate) fn resolve_model_actions<'py>( scope: &Bound<'py, PyAny>, changes: &Bound<'py, PyAny>, @@ -60,6 +58,5 @@ pub(crate) fn replaces_incremental_relation(materialization_type: &str, action: } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(resolve_model_actions, module)?)?; module.add_function(wrap_pyfunction!(replaces_incremental_relation, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs index 3da15bed9c..6fb7570f0b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs @@ -159,40 +159,6 @@ pub(crate) fn detect_changes<'py>( ) } -/// Selected model changes, without inspecting functions. -#[pyfunction] -#[pyo3(signature = (**options))] -pub(crate) fn detect_model_changes_in_scope<'py>( - py: Python<'py>, - options: Option>, -) -> PyResult> { - let arguments: KeywordArguments<'py> = KeywordArguments::read( - py, - "detect_model_changes_in_scope", - options, - ( - &["project", "scope", "snapshot"], - &[ - "query_change_tracking", - "expected_version_hashes", - "expected_metadata_jsons", - ], - ), - )?; - scope_model_changes( - ( - &arguments.value("project")?, - &arguments.value("scope")?, - &arguments.value("snapshot")?, - ), - arguments.value("query_change_tracking")?.extract()?, - ( - arguments.value("expected_version_hashes")?, - arguments.value("expected_metadata_jsons")?, - ), - ) -} - /// One model's change and backfill. #[pyfunction] #[pyo3(signature = (**options))] @@ -784,7 +750,6 @@ impl<'py> ChangeResult<'py> { pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(detect_changes_of, module)?)?; - module.add_function(wrap_pyfunction!(detect_model_changes_in_scope, module)?)?; module.add_function(wrap_pyfunction!(detect_model_changes, module)?)?; module.add_function(wrap_pyfunction!(schema_changes, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs index d54dd25725..71faadc542 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs @@ -136,8 +136,6 @@ fn origin_reference_names_of<'py>( } /// This plan's renames plus recorded renames, read only when a selected query that references models changed. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn plan_reference_renames<'py>( py: Python<'py>, options: Option>, @@ -263,6 +261,5 @@ fn changed_referencing_query( } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(plan_reference_renames, module)?)?; module.add_function(wrap_pyfunction!(origin_reference_names_of, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs index d7daa964c3..160b531330 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs @@ -512,17 +512,7 @@ fn origin_source_entries<'py>( ) } -/// Prepare planner outputs needed for clone execution. -#[pyfunction] -#[pyo3(signature = (**kwargs))] -pub(crate) fn run_clone_planning<'py>( - py: Python<'py>, - kwargs: Option>, -) -> PyResult> { - Ok(timed_clone_planning(py, kwargs)?.0) -} - -/// `run_clone_planning`, with the seconds spent inspecting sources and planning. +/// Plan a clone, with the seconds spent inspecting sources and planning. pub(crate) fn timed_clone_planning<'py>( py: Python<'py>, kwargs: Option>, @@ -530,7 +520,7 @@ pub(crate) fn timed_clone_planning<'py>( let started = std::time::Instant::now(); let args = KeywordArguments::read( py, - "run_clone_planning", + "timed_clone_planning", kwargs, ( &[ @@ -728,7 +718,6 @@ pub(crate) fn resolve_skipped_view_chain<'py>( } pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(run_clone_planning, module)?)?; module.add_function(wrap_pyfunction!(resolve_clone_boundary, module)?)?; module.add_function(wrap_pyfunction!(resolve_skipped_view_chain, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs index bea6d82ebe..e6260170fb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs @@ -384,8 +384,6 @@ pub(crate) fn build_seed_command_plan<'py>( } /// Project static relation data for Python checks. -#[pyfunction] -#[pyo3(signature = (*, project, scope, relations))] pub(crate) fn build_relation_command_plan<'py>( project: &Bound<'py, PyAny>, scope: &Bound<'py, PyAny>, @@ -497,6 +495,5 @@ pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(build_audit_command_plan, module)?)?; module.add_function(wrap_pyfunction!(build_test_command_plan, module)?)?; module.add_function(wrap_pyfunction!(build_seed_command_plan, module)?)?; - module.add_function(wrap_pyfunction!(build_relation_command_plan, module)?)?; module.add_function(wrap_pyfunction!(read_selected_seed_fingerprints, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs index 8010b8aa82..9d15d3127d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs @@ -704,42 +704,7 @@ pub(crate) fn plan_output_fields<'py>( ]) } -/// Build one canonical function entry from resolved relation inputs. -#[pyfunction] -#[pyo3(signature = (**kwargs))] -fn plan_function<'py>( - py: Python<'py>, - kwargs: Option>, -) -> PyResult> { - let args = KeywordArguments::read( - py, - "plan_function", - kwargs, - ( - &["function", "adapter", "relations", "fingerprint_query_sql"], - &["previous_query_sql", "reason"], - ), - )?; - let reason = if args.passed("reason")? { - args.value("reason")? - } else { - planner_type(py, RecordId::PlanReason, "NO_CHANGE")? - }; - function_entry( - &args.value("function")?, - &args.value("adapter")?, - &args.value("relations")?, - ( - args.value("fingerprint_query_sql")?, - args.value("previous_query_sql")?, - reason, - ), - ) -} - /// Plan SQL tests selected by name or overlapping one selection into a reusable result. -#[pyfunction] -#[pyo3(signature = (*, project, adapter, selected_keys, sql_test_selection))] pub(crate) fn plan_selected_sql_tests<'py>( project: &Bound<'py, PyAny>, adapter: &Bound<'py, PyAny>, @@ -778,8 +743,6 @@ fn build_selected_test_entries<'py>( } pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(plan_function, module)?)?; - module.add_function(wrap_pyfunction!(plan_selected_sql_tests, module)?)?; module.add_function(wrap_pyfunction!(sql_test_model_chain_names_by_key, module)?)?; module.add_function(wrap_pyfunction!(build_selected_test_entries, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs index 2d6a09c871..9357c6f72e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs @@ -212,8 +212,6 @@ pub(crate) fn direct_model_version_identities<'py>( } /// Planned write hashes replaced by hashes based on upstreams available this run. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn with_honest_model_write_hashes<'py>( py: Python<'py>, options: Option>, @@ -397,6 +395,5 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { build_direct_model_version_identities, module )?)?; - module.add_function(wrap_pyfunction!(with_honest_model_write_hashes, module)?)?; module.add_function(wrap_pyfunction!(merge_recomputed_model_changes, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs index 14ced8eff4..00e4ec7788 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs @@ -1,6 +1,5 @@ //! Convert existing planner contracts for origin compatibility checks. -use crate::bindings::_helpers::boundary::python_calls::KeywordArguments; use crate::bindings::_helpers::boundary::python_calls::keywords; use crate::bindings::_helpers::compiled_views::model_reads; use crate::bindings::_helpers::compiled_views::model_reads::inferred_rows; @@ -12,10 +11,10 @@ use crate::bindings::_helpers::planner_migrations::models::{ use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; +use pyo3::IntoPyObject; use pyo3::exceptions::PyValueError; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyTupleMethods}; -use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_migrations::main::compatibility_findings::compatibility_findings; use sqlbuild_analysis::planner_migrations::models::CompatibilityFinding; @@ -71,45 +70,6 @@ fn blocking<'py>( .map_err(PyValueError::new_err)?, ) } - -#[pyfunction] -#[pyo3(signature = (**options))] -fn migration_incremental_blocking_findings<'py>( - py: Python<'py>, - options: Option>, -) -> PyResult> { - let arguments = KeywordArguments::read( - py, - "migration_incremental_blocking_findings", - options, - (&["model", "findings", "dialect"], &[]), - )?; - blocking( - (&arguments.value("model")?, &arguments.value("findings")?), - &arguments.value("dialect")?, - None, - ) -} - -#[pyfunction] -#[pyo3(signature = (**options))] -fn migration_snapshot_blocking_findings<'py>( - py: Python<'py>, - options: Option>, -) -> PyResult> { - let arguments = KeywordArguments::read( - py, - "migration_snapshot_blocking_findings", - options, - (&["model", "findings", "dialect", "snapshots_config"], &[]), - )?; - blocking( - (&arguments.value("model")?, &arguments.value("findings")?), - &arguments.value("dialect")?, - Some(&arguments.value("snapshots_config")?), - ) -} - /// Whether an origin relation's columns can carry the destination model's history. pub(super) fn check_migration_compatibility<'py>( check: &CompatibilityCheck<'py>, @@ -201,14 +161,3 @@ fn result<'py>( findings: findings.into_any(), }) } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!( - migration_incremental_blocking_findings, - module - )?)?; - module.add_function(wrap_pyfunction!( - migration_snapshot_blocking_findings, - module - )?) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs index 4a7a6d461c..e9fbd85573 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs @@ -64,8 +64,6 @@ pub(crate) fn fingerprint( } /// Every name whose rename identity can change the migration fingerprint. -#[pyfunction] -#[pyo3(signature = (*, query_sql, metadata_json))] pub(crate) fn migration_fingerprint_ref_names<'py>( py: Python<'py>, query_sql: &str, @@ -79,14 +77,11 @@ pub(crate) fn migration_fingerprint_ref_names<'py>( } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(with_migration_fingerprints, module)?)?; module.add_function(wrap_pyfunction!(build_migration_fingerprint, module)?)?; - module.add_function(wrap_pyfunction!(migration_fingerprint_ref_names, module)?) + Ok(()) } /// Attach persisted migration identities through the existing cache boundary. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn with_migration_fingerprints<'py>( py: Python<'py>, options: Option>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs index 516a9a1277..b589f5eb50 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs @@ -9,15 +9,14 @@ use crate::bindings::_helpers::planner_migrations::old_name_objects::relation; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::IntoPyObject; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PySet, PyTuple}; -use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_migrations::main::{ declared_column_aliases as aliases, old_name_claim_message as claim, old_name_schemas, old_name_skip_reason, }; -#[pyfunction] pub(crate) fn declared_column_aliases<'py>( py: Python<'py>, model: &Bound<'py, PyAny>, @@ -36,8 +35,6 @@ pub(crate) fn declared_column_aliases<'py>( PyTuple::new(py, aliases::declared_column_aliases(&columns)) } -#[pyfunction] -#[pyo3(signature = (*, snapshot, needed))] pub(crate) fn old_name_schemas_to_read<'py>( py: Python<'py>, snapshot: &Bound<'py, PyAny>, @@ -54,26 +51,6 @@ pub(crate) fn old_name_schemas_to_read<'py>( ), ) } - -#[pyfunction] -#[pyo3(signature = (**options))] -fn old_name_claim_message<'py>( - py: Python<'py>, - options: Option>, -) -> PyResult { - let arguments = KeywordArguments::read( - py, - "old_name_claim_message", - options, - (&["model_name", "view", "now"], &[]), - )?; - claim_message( - &arguments.value("model_name")?.extract::()?, - &arguments.value("view")?, - &arguments.value("now")?, - ) -} - fn claim_message(model: &str, view: &Bound<'_, PyAny>, now: &Bound<'_, PyAny>) -> PyResult { let old = native_field(view, "old")?; let qualified: Option = native_field(&old, "qualified_name")?.extract()?; @@ -97,8 +74,6 @@ fn claim_message(model: &str, view: &Bound<'_, PyAny>, now: &Bound<'_, PyAny>) - )) } -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn old_name_claim_errors<'py>( py: Python<'py>, options: Option>, @@ -156,8 +131,6 @@ pub(crate) fn old_name_claim_errors<'py>( PyTuple::new(py, errors) } -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn old_name_view_skip_reason<'py>( py: Python<'py>, options: Option>, @@ -208,11 +181,3 @@ pub(crate) fn old_name_view_skip_reason<'py>( arguments.value("origin_tracked")?.extract()?, )) } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(declared_column_aliases, module)?)?; - module.add_function(wrap_pyfunction!(old_name_schemas_to_read, module)?)?; - module.add_function(wrap_pyfunction!(old_name_claim_message, module)?)?; - module.add_function(wrap_pyfunction!(old_name_claim_errors, module)?)?; - module.add_function(wrap_pyfunction!(old_name_view_skip_reason, module)?) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/registration.rs index c0f4e37068..8e819c060c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/registration.rs @@ -3,8 +3,6 @@ use pyo3::prelude::{Bound, PyModule, PyModuleMethods, PyResult}; pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - super::compatibility::register(module)?; - super::old_names::register(module)?; super::columns::register(module)?; super::column_planning::register(module)?; super::projections::register(module)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs index 4cbc9d2036..8fc86e8ea3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs @@ -16,10 +16,7 @@ use crate::bindings::_helpers::planner_phases::runtime::warehouse_field; use crate::bindings::_helpers::planner_warehouse::source_tables::check_selected_source_tables_exist; use crate::bindings::_helpers::records::fields::field as native_field; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; -use pyo3::pyfunction; -#[pyfunction] -#[pyo3(signature = (*, runtime, scopes, warehouse, deferral))] pub(crate) fn check_selected_scope_buildability<'py>( runtime: &Bound<'py, PyAny>, scopes: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs index cef30a198f..8889654303 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs @@ -19,8 +19,6 @@ fn configured_full_refresh<'py>(model: &Bound<'py, PyAny>) -> PyResult, cli_full_refresh: bool, @@ -33,8 +31,6 @@ pub(crate) fn resolve_model_full_refresh( } /// Names of models effectively full refreshed for this invocation. -#[pyfunction] -#[pyo3(signature = (*, project, cli_full_refresh))] pub(crate) fn effectively_full_refreshed_model_names<'py>( project: &Bound<'py, PyAny>, cli_full_refresh: bool, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs index 2125419581..0d3ade77dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs @@ -60,8 +60,6 @@ fn version_identities<'py>( ) } -#[pyfunction] -#[pyo3(signature = (*, project, scopes, include_stale_warning_identities=true))] pub(crate) fn build_planner_identity_context<'py>( project: &Bound<'py, PyAny>, scopes: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs index 82db7755f3..f8405088ed 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs @@ -27,7 +27,6 @@ use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::fields::mapping_items; use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; -use pyo3::pyfunction; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; fn concat<'py>(py: Python<'py>, parts: &[Bound<'py, PyAny>]) -> PyResult> { @@ -128,8 +127,6 @@ pub(crate) fn base_plan_output<'py>( } /// Attach near-match column rename hints to the model entries they describe. -#[pyfunction] -#[pyo3(signature = (*, entries, hints))] pub(crate) fn with_column_rename_hints<'py>( entries: &Bound<'py, PyAny>, hints: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/registration.rs index 5c7f3e8641..fb4fc0679b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/registration.rs @@ -12,10 +12,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::scopes::resolve_planner_scopes, module )?)?; - module.add_function(wrap_pyfunction!( - super::identities::build_planner_identity_context, - module - )?)?; module.add_function(wrap_pyfunction!( super::identities::planner_identity_work_info, module @@ -24,18 +20,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::identities::detect_stale_warning_changes_of, module )?)?; - module.add_function(wrap_pyfunction!( - super::buildability::check_selected_scope_buildability, - module - )?)?; - module.add_function(wrap_pyfunction!( - super::full_refresh::resolve_model_full_refresh, - module - )?)?; - module.add_function(wrap_pyfunction!( - super::full_refresh::effectively_full_refreshed_model_names, - module - )?)?; module.add_function(wrap_pyfunction!( super::full_refresh::describe_full_rebuild_cause, module @@ -44,10 +28,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::full_refresh::check_full_rebuild_allowed, module )?)?; - module.add_function(wrap_pyfunction!( - super::retention::effective_target_config, - module - )?)?; module.add_function(wrap_pyfunction!( super::retention::table_type_copy_name, module @@ -57,12 +37,5 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module )?)?; module.add_function(wrap_pyfunction!(super::retention::plan_retention, module)?)?; - module.add_function(wrap_pyfunction!( - super::output_assembly::with_column_rename_hints, - module - )?)?; - module.add_function(wrap_pyfunction!( - super::warehouse_state::gather_planner_warehouse_state, - module - )?) + Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs index 680e3d1de2..17b2e0d419 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs @@ -33,8 +33,6 @@ use std::collections::{BTreeMap, BTreeSet}; const MATERIALIZED_KEY: &str = "materialized"; /// The resolved config of the planned target, or defaults without one. -#[pyfunction] -#[pyo3(signature = (*, runtime))] pub(crate) fn effective_target_config<'py>( runtime: &Bound<'py, PyAny>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs index ae15c80b06..ad98aa1104 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs @@ -19,7 +19,6 @@ use crate::bindings::_helpers::planner_warehouse::snapshot::{SnapshotRequest, wa use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult}; -use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use std::time::Instant; @@ -31,8 +30,6 @@ fn progress(runtime: &Bound<'_, PyAny>, message: &str) -> PyResult<()> { Ok(()) } -#[pyfunction] -#[pyo3(signature = (*, runtime, scopes, overrides, deferral))] pub(crate) fn gather_planner_warehouse_state<'py>( runtime: &Bound<'py, PyAny>, scopes: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs index 5cc45c0e3b..28ab3dbd03 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs @@ -5,9 +5,8 @@ use crate::bindings::_helpers::model_config::typed_config::{config_value, model_ use crate::bindings::_helpers::records::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::PyString; -use pyo3::{pyfunction, wrap_pyfunction}; const POLICY_DISABLED: &str = "disabled"; @@ -83,15 +82,9 @@ pub(crate) fn start_cursor_config<'py>( } /// Resolve automatic-start policy with model config taking precedence. -#[pyfunction] -#[pyo3(signature = (*, model, project_config))] pub(crate) fn resolve_start_cursor_config<'py>( model: &Bound<'py, PyAny>, project_config: &Bound<'py, PyAny>, ) -> PyResult> { start_cursor_config(model, project_config) } - -pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(resolve_start_cursor_config, module)?) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs index 74e9947bdf..534a6e508c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs @@ -645,32 +645,6 @@ fn compute_microbatch_range<'py>( microbatch_range(&args.value("model")?, &window_inputs(&args)?) } -/// Return empty inputs that leave a planner-owned window empty, and whether others have rows. -#[pyfunction] -#[pyo3(signature = (**kwargs))] -fn resolve_empty_cursor_inputs<'py>( - py: Python<'py>, - kwargs: Option>, -) -> PyResult<(Bound<'py, PyAny>, bool)> { - let args = KeywordArguments::read( - py, - "resolve_empty_cursor_inputs", - kwargs, - ( - &[ - "model", - "snapshot", - "full_refresh", - "runtime_owned_cursor_bounds", - "start_cursor_override", - "end_cursor_override", - ], - &[], - ), - )?; - empty_cursor_inputs(&args.value("model")?, &window_inputs(&args)?) -} - /// Resolve typed cursor overrides to generic start/end for one model. #[pyfunction] #[pyo3(signature = (*, model, cursor_overrides, start_cursor_override, end_cursor_override))] @@ -690,6 +664,5 @@ fn resolve_cursor_overrides<'py>( pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(compute_plan_cursor_bounds, module)?)?; module.add_function(wrap_pyfunction!(compute_microbatch_range, module)?)?; - module.add_function(wrap_pyfunction!(resolve_empty_cursor_inputs, module)?)?; module.add_function(wrap_pyfunction!(resolve_cursor_overrides, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs index 19d2d70f89..becf39a2b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs @@ -42,8 +42,6 @@ fn decide_empty_cursor_inputs<'py>( ) } -#[pyfunction] -#[pyo3(signature = (*, cursor_snapshot))] pub(crate) fn cursor_inputs_without_rows<'py>( cursor_snapshot: &Bound<'py, PyAny>, ) -> PyResult> { @@ -133,8 +131,6 @@ pub(crate) fn empty_input_rebuild_refusal( ) } -#[pyfunction] -#[pyo3(signature = (*, model_name, cursor_type, input_names))] pub(crate) fn unreadable_cursor_inputs_error<'py>( py: Python<'py>, model_name: &str, @@ -169,8 +165,6 @@ pub(crate) fn unreadable_cursor_inputs_error<'py>( ) } -#[pyfunction] -#[pyo3(signature = (*, cursor_snapshot))] pub(crate) fn without_destination_cursor<'py>( cursor_snapshot: &Bound<'py, PyAny>, ) -> PyResult> { @@ -268,12 +262,9 @@ pub(crate) fn normalize_cursor_snapshot_grain<'py>( pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(decide_empty_cursor_inputs, module)?)?; - module.add_function(wrap_pyfunction!(cursor_inputs_without_rows, module)?)?; module.add_function(wrap_pyfunction!(empty_cursor_window, module)?)?; module.add_function(wrap_pyfunction!(cursor_window_override_flags, module)?)?; module.add_function(wrap_pyfunction!(empty_input_rebuild_refusal, module)?)?; - module.add_function(wrap_pyfunction!(unreadable_cursor_inputs_error, module)?)?; - module.add_function(wrap_pyfunction!(without_destination_cursor, module)?)?; module.add_function(wrap_pyfunction!(resolve_effective_timestamp_grain, module)?)?; module.add_function(wrap_pyfunction!(normalize_cursor_snapshot_grain, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/registration.rs index 5433bfbe90..86ba08ef70 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/registration.rs @@ -5,7 +5,6 @@ use pyo3::prelude::{Bound, PyModule, PyResult}; pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::cursor_bounds::register(module)?; super::cursor_overrides::register(module)?; - super::cursor_policies::register(module)?; super::cursor_ranges::register(module)?; super::cursor_warnings::register(module)?; super::future_safety::register(module)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs index 6a34c76ea4..cd949bd6cc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs @@ -108,8 +108,6 @@ pub(crate) fn build_scenario_hash_index<'py>( Ok(result) } -#[pyfunction] -#[pyo3(signature = (*, logical_name, fixed_prefix, identifier_limit))] pub(crate) fn fit_scenario_artifact_logical_name( py: Python<'_>, logical_name: &str, @@ -124,8 +122,6 @@ pub(crate) fn fit_scenario_artifact_logical_name( ) } -#[pyfunction] -#[pyo3(signature = (*, hash_prefix, kind, logical_name, identifier_limit))] pub(crate) fn build_scenario_artifact_physical_name( hash_prefix: &str, kind: &str, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs index fc7a409b45..e931f4aca2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs @@ -1,6 +1,5 @@ //! Existing seed and function plan dataclass construction. -use crate::bindings::_helpers::boundary::python_calls::KeywordArguments; use crate::bindings::_helpers::boundary::python_calls::keywords; use crate::bindings::_helpers::compiled_views::project_reads; use crate::bindings::_helpers::compiled_views::seed_reads; @@ -15,9 +14,9 @@ use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::by_attribute; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::PyString; -use pyo3::types::{PyDict, PyDictMethods, PyTuple}; +use pyo3::types::{PyDictMethods, PyTuple}; pub(super) fn seed_entries<'py>( project: &Bound<'py, PyAny>, @@ -135,22 +134,3 @@ pub(super) fn function_entry<'py>( } new_record_with(py, RecordId::FunctionPlanEntry, &fields) } - -#[pyfunction] -#[pyo3(signature = (**kwargs))] -pub(crate) fn build_scenario_seed_entries<'py>( - py: Python<'py>, - kwargs: Option>, -) -> PyResult> { - let args = KeywordArguments::read( - py, - "build_scenario_seed_entries", - kwargs, - (&["project", "graph_plan", "relation_plan"], &[]), - )?; - seed_entries( - &args.value("project")?, - &args.value("graph_plan")?, - &args.value("relation_plan")?, - ) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/registration.rs index 9abe33e278..c2e450af9b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/registration.rs @@ -1,7 +1,7 @@ //! Register scenario planning bindings. use crate::bindings::_helpers::planner_scenario::{ - artifacts, cli, entries, execution, fixtures, graph, relations, sql, + artifacts, cli, execution, fixtures, graph, relations, sql, }; use pyo3::prelude::{Bound, PyModule, PyModuleMethods, PyResult}; use pyo3::wrap_pyfunction; @@ -15,10 +15,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { fixtures::build_scenario_fixture_plans, module )?)?; - module.add_function(wrap_pyfunction!( - entries::build_scenario_seed_entries, - module - )?)?; module.add_function(wrap_pyfunction!(sql::resolve_scenario_check_sql, module)?)?; module.add_function(wrap_pyfunction!( artifacts::compute_scenario_hash_prefix, @@ -32,14 +28,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { artifacts::build_scenario_artifact_name, module )?)?; - module.add_function(wrap_pyfunction!( - artifacts::build_scenario_artifact_physical_name, - module - )?)?; - module.add_function(wrap_pyfunction!( - artifacts::fit_scenario_artifact_logical_name, - module - )?)?; module.add_function(wrap_pyfunction!( artifacts::parse_scenario_artifact_physical_name, module diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs index 2fb987e094..e3dde684cf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs @@ -28,8 +28,6 @@ pub(crate) fn upstream_loader_dependency_names<'py>( PyTuple::new(loader_function.py(), graph.upstream(&deps)) } -#[pyfunction] -#[pyo3(signature = (*, project, loader))] pub(crate) fn loader_to_source_entry<'py>( project: &Bound<'py, PyAny>, loader: &Bound<'py, PyAny>, @@ -101,8 +99,6 @@ pub(crate) fn build_intermediate_source_map<'py>( intermediate_map(project, selected_keys, false) } -#[pyfunction] -#[pyo3(signature = (*, project, selected_keys))] pub(crate) fn build_upstream_intermediate_source_map<'py>( project: &Bound<'py, PyAny>, selected_keys: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/registration.rs index 517f9c5e66..6ba143a726 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/registration.rs @@ -1,28 +1,18 @@ //! Register planner scope and loader entry points. -use crate::bindings::_helpers::planner_scope::{loaders, scope, sources}; +use crate::bindings::_helpers::planner_scope::{loaders, sources}; use pyo3::prelude::{Bound, PyModule, PyModuleMethods, PyResult}; use pyo3::wrap_pyfunction; pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(scope::build_planner_scope, module)?)?; - module.add_function(wrap_pyfunction!( - scope::managed_source_upstream_keys, - module - )?)?; module.add_function(wrap_pyfunction!( loaders::upstream_loader_dependency_names, module )?)?; - module.add_function(wrap_pyfunction!(loaders::loader_to_source_entry, module)?)?; module.add_function(wrap_pyfunction!( loaders::build_intermediate_source_map, module )?)?; - module.add_function(wrap_pyfunction!( - loaders::build_upstream_intermediate_source_map, - module - )?)?; module.add_function(wrap_pyfunction!( loaders::expand_selected_loader_dependencies, module diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs index 5bf88db5fb..83438799cc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs @@ -19,7 +19,7 @@ use crate::bindings::_helpers::records::construct::{new_record, new_record_with} use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::fields::mapping_items; use crate::bindings::_helpers::records::ids::RecordId; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyDictMethods; use pyo3::types::{PyDict, PyFrozenSet, PyTuple}; use sqlbuild_analysis::planner_scope::constants::SOURCE; @@ -28,8 +28,6 @@ use sqlbuild_analysis::planner_scope::main::{ }; use std::collections::HashSet; -#[pyfunction] -#[pyo3(signature = (*, selected_keys, upstream_deps, project))] pub(crate) fn managed_source_upstream_keys<'py>( selected_keys: &Bound<'py, PyAny>, upstream_deps: &Bound<'py, PyAny>, @@ -49,8 +47,6 @@ pub(crate) fn managed_source_upstream_keys<'py>( ) } -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn build_planner_scope<'py>( py: Python<'py>, options: Option>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs index 78848545cd..2c1c8f45d8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs @@ -82,8 +82,6 @@ fn selectable_model(model: &Bound<'_, PyAny>) -> PyResult { } /// One SQLBuild-owned model selector core's model names, and its path selector when translated. -#[pyfunction] -#[pyo3(signature = (*, project, term))] pub(crate) fn resolve_sqlbuild_model_selector_names<'py>( project: &Bound<'py, PyAny>, term: &Bound<'py, PyString>, @@ -117,8 +115,5 @@ pub(crate) fn resolve_sqlbuild_model_selector_names<'py>( pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(split_selector_expansion, module)?)?; - module.add_function(wrap_pyfunction!( - resolve_sqlbuild_model_selector_names, - module - )?) + Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/registration.rs index 74289715bc..46c95951b4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/registration.rs @@ -2,10 +2,9 @@ use pyo3::prelude::{Bound, PyModule, PyResult}; -use crate::bindings::_helpers::planner_selection::{model_selectors, selectors, unit_tests}; +use crate::bindings::_helpers::planner_selection::model_selectors; pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { model_selectors::register(module)?; - selectors::register(module)?; - unit_tests::register(module) + Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs index 9aeb74249f..9fafbd54dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs @@ -1,10 +1,7 @@ //! Planner selector parsing and resolution over native project graphs, raising `PlannerInputError`. -use pyo3::prelude::{ - Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRef, PyResult, Python, -}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyRef, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyFrozenSet, PySetMethods, PyString}; -use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::assembly::project::types::ObjectKey; use sqlbuild_analysis::graph::errors::SelectorError; use sqlbuild_analysis::graph::main::match_selector::match_selector; @@ -117,7 +114,6 @@ fn view_graph(mappings: GraphViewMappings<'_, '_>) -> PyResult( raw: &Bound<'py, PyString>, ) -> PyResult> { @@ -128,8 +124,6 @@ pub(crate) fn parse_project_selector<'py>( } /// `--select` minus `--exclude` on a native graph, adding the functions selected models need. -#[pyfunction] -#[pyo3(signature = (*, graph, select, exclude))] pub(crate) fn resolve_graph_selection<'py>( graph: PyRef<'py, NativeProjectGraph>, select: &Bound<'py, PyAny>, @@ -139,8 +133,6 @@ pub(crate) fn resolve_graph_selection<'py>( } /// `--select` minus `--exclude` on a project graph, adding the functions selected models need. -#[pyfunction] -#[pyo3(signature = (*, graph, select, exclude))] pub(crate) fn select_project_graph<'py>( graph: &Bound<'py, PyAny>, select: &Bound<'py, PyAny>, @@ -154,8 +146,6 @@ pub(crate) fn select_project_graph<'py>( } /// Model, source, seed and function selectors resolved against compiled graph mappings. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn resolve_project_selectors<'py>( py: Python<'py>, options: Option>, @@ -174,8 +164,6 @@ pub(crate) fn resolve_project_selectors<'py>( } /// The keys whitespace-separated selector tokens match, without required build resources. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn resolve_project_selector_tokens<'py>( py: Python<'py>, options: Option>, @@ -206,8 +194,6 @@ fn arguments_graph(arguments: &KeywordArguments<'_>) -> PyResult( parsed: &Bound<'py, PyAny>, all_keys: &Bound<'py, PyAny>, @@ -236,8 +222,6 @@ pub(crate) fn match_project_selector<'py>( } /// The upstream key closure of one compiled graph key. -#[pyfunction] -#[pyo3(signature = (*, key, upstream_deps))] pub(crate) fn expand_project_upstream_keys<'py>( key: Bound<'py, PyAny>, upstream_deps: &Bound<'py, PyAny>, @@ -245,14 +229,3 @@ pub(crate) fn expand_project_upstream_keys<'py>( let visited = closure(vec![key], upstream_deps, (false, None))?; PyFrozenSet::new(upstream_deps.py(), visited.iter()) } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(parse_project_selector, module)?)?; - module.add_function(wrap_pyfunction!(resolve_graph_selection, module)?)?; - module.add_function(wrap_pyfunction!(select_project_graph, module)?)?; - module.add_function(wrap_pyfunction!(resolve_project_selectors, module)?)?; - module.add_function(wrap_pyfunction!(resolve_project_selector_tokens, module)?)?; - module.add_function(wrap_pyfunction!(match_project_selector, module)?)?; - module.add_function(wrap_pyfunction!(expand_project_upstream_keys, module)?)?; - Ok(()) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs index 926ac60583..30e188ca01 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs @@ -2,9 +2,9 @@ use std::collections::BTreeSet; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::IntoPyObject; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyFrozenSet, PyString, PyTuple}; -use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_selection::main::split_unit_test_selectors::split_unit_test_selectors as split; use sqlbuild_analysis::planner_selection::models::{ SelectorGroups, UnitTestSelectorNames, UnitTestSelectorSplit, @@ -146,8 +146,6 @@ fn split_python<'py>( } /// Move unit-test selectors out of resource selectors, or reject them for this command. -#[pyfunction] -#[pyo3(signature = (*, select, exclude, discovered_inputs, accepts_unit_tests))] pub(crate) fn split_project_unit_test_selectors<'py>( select: &Bound<'py, PyAny>, exclude: &Bound<'py, PyAny>, @@ -158,8 +156,6 @@ pub(crate) fn split_project_unit_test_selectors<'py>( } /// Split unit-test selectors from resource selectors for a command that runs unit tests. -#[pyfunction] -#[pyo3(signature = (*, select, exclude, discovered_inputs))] pub(crate) fn split_unit_test_selectors<'py>( select: &Bound<'py, PyAny>, exclude: &Bound<'py, PyAny>, @@ -169,8 +165,6 @@ pub(crate) fn split_unit_test_selectors<'py>( } /// Raise when a selector names a unit test; return the unchanged resource selectors. -#[pyfunction] -#[pyo3(signature = (*, select, exclude, discovered_inputs))] pub(crate) fn reject_unit_test_selectors<'py>( select: &Bound<'py, PyAny>, exclude: &Bound<'py, PyAny>, @@ -178,9 +172,3 @@ pub(crate) fn reject_unit_test_selectors<'py>( ) -> PyResult> { split_python((select, exclude), discovered_inputs, false) } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(split_project_unit_test_selectors, module)?)?; - module.add_function(wrap_pyfunction!(split_unit_test_selectors, module)?)?; - module.add_function(wrap_pyfunction!(reject_unit_test_selectors, module)?) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs index cab7a9818a..8876606089 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs @@ -193,8 +193,6 @@ fn plan_audit<'py>( } /// Select attached audits by their logical target and singular audits by dependency overlap. -#[pyfunction] -#[pyo3(signature = (*, audit, selected_keys))] pub(crate) fn audit_is_selected( audit: &Bound<'_, PyAny>, selected_keys: &Bound<'_, PyAny>, @@ -223,8 +221,6 @@ pub(crate) fn audit_is_selected( } /// Assemble selected audits in compiled-project order without querying lineage for an empty selection. -#[pyfunction] -#[pyo3(signature = (**request))] pub(crate) fn build_selected_audit_entries<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs index f0a28a6bc5..48f55a3095 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs @@ -41,8 +41,6 @@ fn log_parse_failure(py: Python<'_>, failure: &TypeParseFailure) -> PyResult<()> } /// The `CursorType` a supported cross-adapter SQL type holds, or `None`. -#[pyfunction] -#[pyo3(signature = (sql_type))] pub(crate) fn classify_cursor_sql_type<'py>( py: Python<'py>, sql_type: &str, @@ -126,6 +124,5 @@ pub(crate) fn cursor_type_warning_record<'py>( } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(classify_cursor_sql_type, module)?)?; module.add_function(wrap_pyfunction!(check_cursor_type_consistency, module)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_sql.rs index a791ee7954..b9b2e18f39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_sql.rs @@ -1,8 +1,7 @@ //! Adapter-bound rendering of fixture completion SQL. -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods}; -use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_fixtures::main::{ empty_fixture_error::empty_fixture_error, empty_fixture_marker::empty_fixture_marker, render_completed::render_completed as native_render_completed, render_empty::render_empty, @@ -15,7 +14,6 @@ use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::planner_strategy::models::FixtureRelation; use crate::bindings::_helpers::records::fields::field as native_field; -#[pyfunction(name = "is_empty_fixture_query")] pub(crate) fn fixture_marker(sql: &str) -> PyResult { empty_fixture_marker(sql).map_err(value_error) } @@ -128,7 +126,3 @@ pub(crate) fn complete_typed_null_columns( adapter, ) } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(fixture_marker, module)?) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs index c7193aa6ac..07127e9bb1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs @@ -1,10 +1,7 @@ //! Watermark inputs resolved through SQL reference lineage, and capped producers behind them. -use pyo3::prelude::{ - Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, -}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::PyDict; -use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_strategy::errors::LineageFailure; use sqlbuild_analysis::planner_strategy::main::capped_watermark_input::capped_watermark_input; @@ -117,8 +114,6 @@ fn lineage_error(py: Python<'_>, failure: LineageFailure) -> PyErr { } /// The reference one named input resolves to through model and SQL function references. -#[pyfunction] -#[pyo3(signature = (*, model, input_name, models_by_name, functions_by_name))] pub(crate) fn resolve_lineage_reference<'py>( model: &Bound<'py, PyAny>, input_name: &str, @@ -136,8 +131,6 @@ pub(crate) fn resolve_lineage_reference<'py>( } /// Reject capped producers feeding a microbatch model's watermark inputs. -#[pyfunction(name = "validate_capped_watermark_inputs")] -#[pyo3(signature = (*, model, models_by_name, functions_by_name = None))] pub(crate) fn reject_capped_watermark_inputs<'py>( model: &Bound<'py, PyAny>, models_by_name: Bound<'py, PyAny>, @@ -168,8 +161,3 @@ pub(crate) fn reject_capped_watermark_inputs<'py>( Err(failure) => Err(lineage_error(py, failure)), } } - -pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(resolve_lineage_reference, module)?)?; - module.add_function(wrap_pyfunction!(reject_capped_watermark_inputs, module)?) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs index 7eb33c6dc5..45faefc0a7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs @@ -240,8 +240,6 @@ pub(crate) fn resolve_microbatch_lookback( } /// Declared seed columns with types, as `ColumnInfo`. -#[pyfunction] -#[pyo3(signature = (seed))] pub(crate) fn extract_seed_columns<'py>(seed: &Bound<'py, PyAny>) -> PyResult> { schema_columns(seed, true) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/registration.rs index 26e4c400b9..fdbc656dbd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/registration.rs @@ -4,7 +4,7 @@ use pyo3::prelude::{Bound, PyModule, PyResult}; use pyo3::{prelude::PyModuleMethods, wrap_pyfunction}; use crate::bindings::_helpers::planner_strategy::{ - audits, cursor_types, fixture_completion, fixture_sql, lineage, microbatches, strategy, + audits, cursor_types, fixture_completion, microbatches, strategy, }; pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { @@ -12,14 +12,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::audit_entries::plan_audit_of, module )?)?; - module.add_function(wrap_pyfunction!( - super::audit_entries::audit_is_selected, - module - )?)?; - module.add_function(wrap_pyfunction!( - super::audit_entries::build_selected_audit_entries, - module - )?)?; module.add_function(wrap_pyfunction!( super::cursor_inputs::cursor_input_columns_of, module @@ -36,10 +28,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::model_entries::resolve_microbatch_lookback, module )?)?; - module.add_function(wrap_pyfunction!( - super::model_entries::extract_seed_columns, - module - )?)?; module.add_function(wrap_pyfunction!( super::microbatch_limits::apply_microbatch_limit, module @@ -51,10 +39,8 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { audits::register(module)?; cursor_types::register(module)?; fixture_completion::register(module)?; - fixture_sql::register(module)?; super::fixture_context::register(module)?; super::fixture_reads::register(module)?; - lineage::register(module)?; microbatches::register(module)?; strategy::register(module) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/registration.rs index 939d91e174..5b7ca3db79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/registration.rs @@ -13,10 +13,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::snapshot::gather_warehouse_snapshot, module )?)?; - module.add_function(wrap_pyfunction!( - super::snapshot::gather_redirected_cursor_snapshots, - module - )?)?; module.add_function(wrap_pyfunction!( super::watermarks::watermark_type_is_compatible, module @@ -33,10 +29,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::semantic_sources::list_source_candidates, module )?)?; - module.add_function(wrap_pyfunction!( - super::source_columns::gather_source_inspection, - module - )?)?; module.add_function(wrap_pyfunction!( super::semantic_sources::semantic_source_columns, module @@ -45,18 +37,6 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { super::source_tables::check_selected_source_tables_exist_binding, module )?)?; - module.add_function(wrap_pyfunction!( - super::source_deferral::build_source_read_map, - module - )?)?; - module.add_function(wrap_pyfunction!( - super::source_deferral::with_declared_source_reads, - module - )?)?; - module.add_function(wrap_pyfunction!( - super::source_freshness::source_freshness_unknown_warnings, - module - )?)?; module.add_function(wrap_pyfunction!( super::source_freshness::build_planner_source_freshness_result_of, module diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs index 8d1d95fb7a..e72cafdd90 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs @@ -772,8 +772,6 @@ pub(crate) fn gather_warehouse_snapshot<'py>( } /// Cursor snapshots of models whose history lives in another relation or column. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn gather_redirected_cursor_snapshots<'py>( py: pyo3::Python<'py>, options: Option>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs index 81f04ff66b..b99fd5cebe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs @@ -199,8 +199,6 @@ pub(crate) fn source_inspection<'py>( Ok((result, frozenset(py, &listed)?)) } -#[pyfunction] -#[pyo3(signature = (*, project, adapter, connection, source_entries))] pub(crate) fn gather_source_inspection<'py>( project: &Bound<'py, PyAny>, adapter: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs index 603769e400..d419ec541b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs @@ -22,7 +22,6 @@ use crate::bindings::_helpers::planner_warehouse::constants::{ use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; -use pyo3::pyfunction; use pyo3::types::PyDict as KeywordDict; use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; use sqlbuild_analysis::planner_warehouse::main::source_call_names::source_call_names; @@ -244,8 +243,6 @@ fn entry_for_environment<'py>( } /// Resolve source reads for selected SQL, or for `read_source_names` when given. -#[pyfunction] -#[pyo3(signature = (**options))] pub(crate) fn build_source_read_map<'py>( py: pyo3::Python<'py>, options: Option>, @@ -325,8 +322,6 @@ pub(crate) fn build_source_read_map<'py>( } /// Add declared project sources that no selected SQL resource reads to a source map. -#[pyfunction] -#[pyo3(signature = (*, project, source_map, source_names))] pub(crate) fn with_declared_source_reads<'py>( project: &Bound<'py, PyAny>, source_map: &Bound<'py, PyDict>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs index cafffee3cc..d2a86ae501 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs @@ -25,9 +25,6 @@ use sqlbuild_analysis::planner_warehouse::main::freshness_unknown_message::fresh use std::collections::BTreeSet; /// One warning per reason that left configured source freshness unknown. -#[pyfunction] -#[pyo3(name = "build_source_freshness_unknown_warnings")] -#[pyo3(signature = (*, source_freshness))] pub(crate) fn source_freshness_unknown_warnings<'py>( source_freshness: &Bound<'py, PyAny>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs index 5a02286c8a..ac5e886fd0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs @@ -693,7 +693,6 @@ native_ids! { BuildModelWarnings => "build_model_warnings", BuildPhaseTimingTracker => "BuildPhaseTimingTracker", BuildPlannerRelationsContext => "build_planner_relations_context", - BuildPlannerScope => "build_planner_scope", BuildPlannerSourceFreshnessResult => "build_planner_source_freshness_result", BuildProgressCallbacks => "BuildProgressCallbacks", BuildProjectColumnLineage => "build_project_column_lineage", @@ -709,8 +708,6 @@ native_ids! { BuildScenarioHashIndex => "build_scenario_hash_index", BuildScenarioRelationMap => "build_scenario_relation_map", BuildSeedCommandPlan => "build_seed_command_plan", - BuildSelectedAuditEntries => "build_selected_audit_entries", - BuildSourceReadMap => "build_source_read_map", BuildSqlbuildPlanOutput => "build_sqlbuild_plan_output", BuildStaleOutOfSelectionWarnings => "build_stale_out_of_selection_warnings", BuildStaticPipelineResult => "build_static_pipeline_result", @@ -815,7 +812,6 @@ native_ids! { FormatSkippedTypeProofRules => "format_skipped_type_proof_rules", FormatTestExecutionJson => "format_test_execution_json", GatherDeferredRelations => "gather_deferred_relations", - GatherRedirectedCursorSnapshots => "gather_redirected_cursor_snapshots", GetConfigCursorBound => "get_config_cursor_bound", GetConfigStr => "get_config_str", GetMaterializationType => "get_materialization_type", @@ -840,7 +836,6 @@ native_ids! { MachineOutputMode => "machine_output_mode", MacroStoreToken => "macro_store_token", MaintainSqlbuildSkills => "maintain_sqlbuild_skills", - MatchProjectSelector => "match_project_selector", MatchingDynamicFamilies => "matching_dynamic_families", MaximumAllowedStart => "maximum_allowed_start", ModelPythonHookNames => "model_python_hook_names", @@ -852,18 +847,14 @@ native_ids! { NestedCommandProgressCallbacks => "NestedCommandProgressCallbacks", NormalizeAnalysisSql => "normalize_analysis_sql", NormalizeDbtProfileConnection => "normalize_dbt_profile_connection", - OldNameClaimErrors => "old_name_claim_errors", - OldNameViewSkipReason => "old_name_view_skip_reason", ParseDbtLsJsonLines => "parse_dbt_ls_json_lines", ParseHeaderValues => "parse_header_values", - ParseProjectSelector => "parse_project_selector", PlanColumnMigrations => "plan_column_migrations", PlanCompiledSqlTestArtifacts => "plan_compiled_sql_test_artifacts", PlanDbtInteropCommand => "plan_dbt_interop_command", PlanDbtInteropFromProject => "plan_dbt_interop_from_project", PlanHasExecutableWork => "plan_has_executable_work", PlanModel => "plan_model", - PlanReferenceRenames => "plan_reference_renames", PlanRuleFixes => "plan_rule_fixes", PlanRuleRun => "plan_rule_run", PlanSqlTests => "plan_sql_tests", @@ -880,7 +871,6 @@ native_ids! { ReadSelectedSeedFingerprints => "read_selected_seed_fingerprints", ReadsOutsideTargetLineage => "reads_outside_target_lineage", RecordTemplateReads => "record_template_reads", - RejectUnitTestSelectors => "reject_unit_test_selectors", RejectUnparseableHeaderRewrites => "reject_unparseable_header_rewrites", RelationLineageFingerprint => "relation_lineage_fingerprint", RemainingContractFindings => "remaining_contract_findings", @@ -912,15 +902,12 @@ native_ids! { ResolveModelDeferralInputs => "resolve_model_deferral_inputs", ResolveModelSql => "resolve_model_sql", ResolveProjectConnectionConfig => "resolve_project_connection_config", - ResolveProjectSelectorTokens => "resolve_project_selector_tokens", - ResolveProjectSelectors => "resolve_project_selectors", ResolveRawDbtProfileConnection => "resolve_raw_dbt_profile_connection", ResolveRefReferences => "resolve_ref_references", ResolveRuleIgnoreSelectors => "resolve_rule_ignore_selectors", ResolveSkippedViewChain => "resolve_skipped_view_chain", ResolveSourceReferences => "resolve_source_references", ResolveSqlbuildExecutionPlanOutput => "resolve_sqlbuild_execution_plan_output", - ResolveSqlbuildModelSelectorNames => "resolve_sqlbuild_model_selector_names", ResolveStaticRelationContext => "resolve_static_relation_context", ResolveTargetConfig => "resolve_target_config", ResolveTargetConnectionConfig => "resolve_target_connection_config", @@ -933,7 +920,6 @@ native_ids! { RunCheckCommand => "run_check_command", RunCloneCommand => "run_clone_command", RunClonePipelineWithOptions => "run_clone_pipeline_with_options", - RunClonePlanning => "run_clone_planning", RunCompile => "run_compile", RunCompileCommand => "run_compile_command", RunCompileOnlyPipeline => "run_compile_only_pipeline", @@ -948,7 +934,6 @@ native_ids! { RunDiffCommand => "run_diff_command", RunDiffPipeline => "run_diff_pipeline", RunFormatCommand => "run_format_command", - RunFreshnessCommand => "run_freshness_command", RunInitCommand => "run_init_command", RunJanitorCommand => "run_janitor_command", RunLineageCommand => "run_lineage_command", @@ -971,7 +956,6 @@ native_ids! { SchemaValidations => "schema_validations", ScopeMetadataProjection => "scope_metadata_projection", SelectCatalogueRules => "select_catalogue_rules", - SelectProjectGraph => "select_project_graph", SelectedRuleCodes => "selected_rule_codes", SessionOutcomeAnalysis => "session_outcome_analysis", SettingHelp => "setting_help", @@ -980,7 +964,6 @@ native_ids! { SourceFreshnessInputError => "SourceFreshnessInputError", SourceFreshnessObservationError => "SourceFreshnessObservationError", SplitSelectorExpansion => "split_selector_expansion", - SplitUnitTestSelectors => "split_unit_test_selectors", SqlTestModelChainNamesByKey => "sql_test_model_chain_names_by_key", SqlTestScanCache => "SqlTestScanCache", SqlValueValidationError => "SqlValueValidationError", @@ -993,7 +976,6 @@ native_ids! { TypeCursorOverrides => "type_cursor_overrides", UpdateSqlbuildSkills => "update_sqlbuild_skills", ValidateAuthoredSqlTestCtes => "validate_authored_sql_test_ctes", - ValidateCappedWatermarkInputs => "validate_capped_watermark_inputs", ValidateCliWarehouse => "validate_cli_warehouse", ValidateCompileDbtModelNames => "validate_compile_dbt_model_names", ValidateCompileDbtModelReference => "validate_compile_dbt_model_reference", @@ -1001,8 +983,6 @@ native_ids! { ValidatePreviewTarget => "validate_preview_target", ValidateSourceCursorInputColumns => "validate_source_cursor_input_columns", WithBindingDiagnostics => "with_binding_diagnostics", - WithHonestModelWriteHashes => "with_honest_model_write_hashes", - WithMigrationFingerprints => "with_migration_fingerprints", WithTypeRecovery => "with_type_recovery", WriteAtomically => "write_atomically", WriteCheckResults => "write_check_results", diff --git a/src/sqlbuild/_native.pyi b/src/sqlbuild/_native.pyi index fb3786b731..81c6d8ccd7 100644 --- a/src/sqlbuild/_native.pyi +++ b/src/sqlbuild/_native.pyi @@ -2103,28 +2103,7 @@ class NativeProjectGraph: self, selected: list[_GraphKey], include: tuple[bool, bool, bool] ) -> list[_GraphKey]: ... -def parse_project_selector(raw: str) -> ParsedSelector | PathSelector: ... -def resolve_project_selectors( - *, - select: Sequence[str], - exclude: Sequence[str], - all_keys: Mapping[str, CompiledObjectKey], - upstream_deps: Mapping[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - downstream_deps: Mapping[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - tag_index: Mapping[str, Iterable[CompiledObjectKey]], - path_index: Mapping[CompiledObjectKey, str], -) -> frozenset[CompiledObjectKey]: ... -def split_project_unit_test_selectors( - *, - select: Sequence[str], - exclude: Sequence[str], - discovered_inputs: DiscoveredProjectInputs, - accepts_unit_tests: bool, -) -> UnitTestSelectorSplit: ... def split_selector_expansion(raw: str) -> SelectorExpansion: ... -def resolve_sqlbuild_model_selector_names( - *, project: CompiledProjectView, term: str -) -> tuple[tuple[str, ...], str | None]: ... def run_compile(request: CompileCommandRequest) -> int: ... COMPILE_REUSE_RACY_WINDOW_NS: int diff --git a/tests/integration/src/sqlbuild/integrations/dbt/test_plan.py b/tests/integration/src/sqlbuild/integrations/dbt/test_plan.py index 9e671055ec..380f6eafb4 100644 --- a/tests/integration/src/sqlbuild/integrations/dbt/test_plan.py +++ b/tests/integration/src/sqlbuild/integrations/dbt/test_plan.py @@ -258,6 +258,25 @@ expected_sqlbuild_skip_reason=None, expected_path_translations=(), ), + RealDbtInteropPlanTestCase( + description="translates backslash model path selector for real SQLBuild project", + args=("--select", "path:models\\marts"), + sqlbuild_model_contents_by_relative_path={ + "marts/mart_orders.sql": "MODEL (description 'Test model mart_orders.');\n\nselect 1 as order_id\n", + "staging/stg_local.sql": "MODEL (description 'Test model stg_local.');\n\nselect 1 as order_id\n", + }, + expected_sqlbuild_model_names=("mart_orders",), + expected_sqlbuild_command_argvs=(("sqb", "plan", "--select", "mart_orders"),), + expected_dbt_selected_unique_ids=(), + expected_dbt_required_unique_ids=(), + expected_dbt_required_selector_terms=(), + expected_supplemental_dbt_command_argvs=(), + expected_dbt_anchor_terms=(), + expected_dbt_anchor_unique_ids_by_term={}, + expected_dbt_skip_reason=DbtInteropSkipReason.NO_DBT_WORK, + expected_sqlbuild_skip_reason=None, + expected_path_translations=(("path:models\\marts", "path:models/marts"),), + ), RealDbtInteropPlanTestCase( description="preserves routed SQLBuild cursor args in real plan argv", args=("--select", "local_only", "--start-cursor-int", "10"), diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/_test_types.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/_test_types.py index be95ea7350..248ce38c5d 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/_test_types.py +++ b/tests/unit/src/sqlbuild/compiler/planner/_helpers/_test_types.py @@ -13,8 +13,6 @@ MaterializationType, MissingUpstream, OnSchemaChange, - ParsedSelector, - PathSelector, PlanAction, PlanReason, PlanWarning, @@ -298,37 +296,6 @@ class ExpandDownstreamTestCase: expected_keys: frozenset[CompiledObjectKey] -@dataclass(frozen=True) -class ParseSelectorTestCase: - description: str - raw: str - expected_result: ParsedSelector | PathSelector - - -@dataclass(frozen=True) -class ParseSelectorErrorTestCase: - description: str - raw: str - expected_error_type: type[Exception] - - -@dataclass(frozen=True) -class ResolveSelectorTestCase: - description: str - select: tuple[str, ...] - exclude: tuple[str, ...] - expected_names: frozenset[str] - - -@dataclass(frozen=True) -class ResolveSelectorErrorTestCase: - description: str - select: tuple[str, ...] - exclude: tuple[str, ...] - expected_error_type: type[Exception] - expected_error_fragment: str | None = None - - @dataclass(frozen=True) class CheckBuildabilityTestCase: description: str diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/conftest.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/conftest.py index 0a6994b40e..9d48db4f9f 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/conftest.py +++ b/tests/unit/src/sqlbuild/compiler/planner/_helpers/conftest.py @@ -1,83 +1 @@ from __future__ import annotations - -import pytest - -from sqlbuild._native import ( - CompiledObjectKey, - build_downstream_deps, - build_execution_upstream_deps, - project_lineage_views, -) -from sqlbuild.compiler.compile.types import CompiledProjectView -from tests.unit.src.sqlbuild.compiler.planner._helpers.helpers import ( - build_test_project, - model_key, -) - - -@pytest.fixture -def diamond_graph() -> tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], -]: - project: CompiledProjectView = build_test_project( - model_deps={ - "orders": ("raw_orders",), - "customers": ("raw_customers",), - "joined": ("orders", "customers"), - }, - source_names=("raw_orders", "raw_customers"), - seed_names=("codes",), - ) - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = ( - build_execution_upstream_deps(project) - ) - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = build_downstream_deps( - upstream - ) - all_keys: dict[str, CompiledObjectKey] = {key.name: key for key in upstream} - return all_keys, upstream, downstream - - -@pytest.fixture -def diamond_tag_index() -> dict[str, frozenset[CompiledObjectKey]]: - return { - "nightly": frozenset({model_key("orders"), model_key("joined")}), - "staging": frozenset({model_key("orders")}), - } - - -@pytest.fixture -def path_graph() -> tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, str], -]: - project: CompiledProjectView = build_test_project( - model_deps={ - "stg_orders": ("raw_orders",), - "stg_customers": ("raw_customers",), - "stg_deep": ("raw_orders",), - "int_enriched": ("stg_orders", "stg_customers"), - "fact_orders": ("int_enriched",), - }, - model_paths={ - "stg_orders": "models/staging/stg_orders.sql", - "stg_customers": "models/staging/stg_customers.sql", - "stg_deep": "models/staging/raw/stg_deep.sql", - "int_enriched": "models/intermediate/int_enriched.sql", - "fact_orders": "models/marts/fact_orders.sql", - }, - source_names=("raw_orders", "raw_customers"), - ) - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = ( - build_execution_upstream_deps(project) - ) - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = build_downstream_deps( - upstream - ) - all_keys: dict[str, CompiledObjectKey] = {key.name: key for key in upstream} - path_idx: dict[CompiledObjectKey, str] = project_lineage_views(project).path_index - return all_keys, upstream, downstream, path_idx diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/__init__.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/_test_types.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/_test_types.py deleted file mode 100644 index 1e0b74d81a..0000000000 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/_test_types.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass - -from sqlbuild._native import SqlTestSelection - - -@dataclass(frozen=True) -class UnitTestSelectorSplitTestCase: - description: str - select: tuple[str, ...] - exclude: tuple[str, ...] - expected_select: tuple[str, ...] - expected_exclude: tuple[str, ...] - expected_selection: SqlTestSelection - - -@dataclass(frozen=True) -class UnitTestSelectorErrorTestCase: - description: str - select: tuple[str, ...] - accepts_unit_tests: bool - expected_code: str - expected_message: str - expected_help: str | None diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/helpers.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/helpers.py deleted file mode 100644 index e7f3904331..0000000000 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/helpers.py +++ /dev/null @@ -1,33 +0,0 @@ -from pathlib import Path -from types import SimpleNamespace -from typing import cast - -from sqlbuild._native import DiscoveredProjectInputs - - -def build_selector_discovered_inputs( - *, tests: frozenset[str], resources: frozenset[str] -) -> DiscoveredProjectInputs: - """Discovered inputs naming these unit tests and, as model files, these resources.""" - - inputs: SimpleNamespace = SimpleNamespace( - test_files=( - SimpleNamespace( - relative_path=Path("tests/unit_tests.sql"), - blocks=tuple(SimpleNamespace(name=name) for name in sorted(tests)), - ), - ), - model_files=tuple( - SimpleNamespace(file_path=Path(f"models/{name}.sql")) for name in sorted(resources) - ), - schema_files=(), - source_files=(), - seed_files=(), - sql_function_files=(), - python_function_files=(), - loader_functions=(), - task_functions=(), - asset_functions=(), - check_functions=(), - ) - return cast(DiscoveredProjectInputs, inputs) diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/test_unit_test_selectors.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/test_unit_test_selectors.py deleted file mode 100644 index 5fc6d33ba1..0000000000 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/selection/test_unit_test_selectors.py +++ /dev/null @@ -1,200 +0,0 @@ -"""Splitting unit-test selectors from resource selectors, and rejecting them elsewhere.""" - -from __future__ import annotations - -import pytest - -from sqlbuild._native import ( - DiscoveredProjectInputs, - PlannerInputError, - SqlTestSelection, - UnitTestSelectorSplit, - split_project_unit_test_selectors, -) -from tests.unit.src.sqlbuild.compiler.planner._helpers.selection._test_types import ( - UnitTestSelectorErrorTestCase, - UnitTestSelectorSplitTestCase, -) -from tests.unit.src.sqlbuild.compiler.planner._helpers.selection.helpers import ( - build_selector_discovered_inputs, -) - -_DISCOVERED: DiscoveredProjectInputs = build_selector_discovered_inputs( - tests=frozenset({"order_status", "order_totals_check", "customer_status"}), - resources=frozenset({"orders", "order_totals", "customers", "raw_orders"}), -) - - -@pytest.mark.parametrize( - "test_case", - [ - UnitTestSelectorSplitTestCase( - description="a bare test name selects only that test and no models", - select=("order_status",), - exclude=(), - expected_select=(), - expected_exclude=(), - expected_selection=SqlTestSelection( - names=frozenset({"order_status"}), models_selected=False - ), - ), - UnitTestSelectorSplitTestCase( - description="test: selectors union with model selectors", - select=("test:customer_status orders+", "tag:daily"), - exclude=(), - expected_select=("orders+", "tag:daily"), - expected_exclude=(), - expected_selection=SqlTestSelection(names=frozenset({"customer_status"})), - ), - UnitTestSelectorSplitTestCase( - description="test: patterns match unit tests with fnmatch", - select=("test:order_*",), - exclude=(), - expected_select=(), - expected_exclude=(), - expected_selection=SqlTestSelection( - names=frozenset({"order_status", "order_totals_check"}), models_selected=False - ), - ), - UnitTestSelectorSplitTestCase( - description="a bare pattern that matches resources keeps its resource meaning", - select=("order*",), - exclude=("test:order_totals_check",), - expected_select=("order*",), - expected_exclude=(), - expected_selection=SqlTestSelection(excluded_names=frozenset({"order_totals_check"})), - ), - UnitTestSelectorSplitTestCase( - description="empty and blank select values are kept and select nothing", - select=("", " "), - exclude=(), - expected_select=("", " "), - expected_exclude=(), - expected_selection=SqlTestSelection(), - ), - UnitTestSelectorSplitTestCase( - description="a test-only selector beside an empty value still selects no models", - select=("", "order_status"), - exclude=(), - expected_select=("",), - expected_exclude=(), - expected_selection=SqlTestSelection(names=frozenset({"order_status"})), - ), - UnitTestSelectorSplitTestCase( - description="selectors without unit tests pass through unchanged", - select=("orders", "customers,tag:daily", "models/marts"), - exclude=("raw_orders",), - expected_select=("orders", "customers,tag:daily", "models/marts"), - expected_exclude=("raw_orders",), - expected_selection=SqlTestSelection(), - ), - ], - ids=lambda case: case.description, -) -def test_given_selectors_when_splitting_unit_tests_then_tests_and_resources_separate( - test_case: UnitTestSelectorSplitTestCase, -) -> None: - split: UnitTestSelectorSplit = split_project_unit_test_selectors( - select=test_case.select, - exclude=test_case.exclude, - discovered_inputs=_DISCOVERED, - accepts_unit_tests=True, - ) - - assert split.select == test_case.expected_select - assert split.exclude == test_case.expected_exclude - assert split.sql_test_selection == test_case.expected_selection - - -@pytest.mark.parametrize( - "test_case", - [ - UnitTestSelectorErrorTestCase( - description="a command that runs no unit tests rejects a bare test name", - select=("order_status",), - accepts_unit_tests=False, - expected_code="S013", - expected_message=( - "selector 'order_status' selects a unit test; only `sqb test` and `sqb build` " - "accept unit-test selectors" - ), - expected_help=None, - ), - UnitTestSelectorErrorTestCase( - description="a command that runs no unit tests rejects test: selectors", - select=("orders test:customer_status",), - accepts_unit_tests=False, - expected_code="S013", - expected_message=( - "selector 'test:customer_status' selects a unit test; only `sqb test` and " - "`sqb build` accept unit-test selectors" - ), - expected_help=None, - ), - UnitTestSelectorErrorTestCase( - description="an unknown test: name suggests the nearest unit tests", - select=("test:order_stats",), - accepts_unit_tests=True, - expected_code="S007", - expected_message="unknown unit test name 'order_stats'", - expected_help="did you mean 'order_status'?", - ), - UnitTestSelectorErrorTestCase( - description="an unknown bare name suggests the closest unit test", - select=("customer_stat",), - accepts_unit_tests=True, - expected_code="S007", - expected_message="unknown selector name 'customer_stat'", - expected_help="did you mean 'customer_status'?", - ), - UnitTestSelectorErrorTestCase( - description="graph expansion of a unit test is refused", - select=("+order_status",), - accepts_unit_tests=True, - expected_code="S013", - expected_message=( - "selector '+order_status' expands a unit test with '+'; unit tests have no " - "lineage, so select them by name without '+'" - ), - expected_help=None, - ), - UnitTestSelectorErrorTestCase( - description="intersecting a unit test is refused", - select=("order_status,tag:daily",), - accepts_unit_tests=True, - expected_code="S013", - expected_message=( - "selector 'order_status,tag:daily' intersects a unit test with ','; unit tests " - "can only be selected whole, by name" - ), - expected_help=None, - ), - UnitTestSelectorErrorTestCase( - description="a stray trailing comma after a unit test reports the empty selector", - select=("test:order_status,",), - accepts_unit_tests=True, - expected_code="S001", - expected_message="empty selector", - expected_help=None, - ), - ], - ids=lambda case: case.description, -) -def test_given_invalid_unit_test_selectors_when_splitting_then_it_raises_clear_errors( - test_case: UnitTestSelectorErrorTestCase, -) -> None: - with pytest.raises(PlannerInputError) as raised: - split_project_unit_test_selectors( - select=test_case.select, - exclude=(), - discovered_inputs=_DISCOVERED, - accepts_unit_tests=test_case.accepts_unit_tests, - ) - - assert raised.value.code == test_case.expected_code - assert raised.value.message == test_case.expected_message - assert raised.value.help == test_case.expected_help - - -if __name__ == "__main__": - pytest.main([__file__, "-vv"]) diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_selectors.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_selectors.py index aee3733314..ebb507e2dd 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_selectors.py +++ b/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_selectors.py @@ -4,679 +4,15 @@ from sqlbuild._native import ( CompiledObjectKey, - ParsedSelector, - PathSelector, - SelectorKind, - build_downstream_deps, - parse_project_selector, project_lineage_views, - resolve_project_selectors, ) from sqlbuild.compiler.compile.types import CompiledProjectView from tests.unit.src.sqlbuild.compiler.planner._helpers._test_types import ( BuildPathIndexTestCase, - ParseSelectorErrorTestCase, - ParseSelectorTestCase, - ResolveSelectorErrorTestCase, - ResolveSelectorTestCase, ) from tests.unit.src.sqlbuild.compiler.planner._helpers.helpers import build_test_project -@pytest.mark.parametrize( - "test_case", - [ - ParseSelectorTestCase( - description="parses bare model name", - raw="orders", - expected_result=ParsedSelector(kind=SelectorKind.NAME, value="orders"), - ), - ParseSelectorTestCase( - description="parses upstream expansion", - raw="+orders", - expected_result=ParsedSelector(kind=SelectorKind.NAME, value="orders", upstream=True), - ), - ParseSelectorTestCase( - description="parses downstream expansion", - raw="orders+", - expected_result=ParsedSelector(kind=SelectorKind.NAME, value="orders", downstream=True), - ), - ParseSelectorTestCase( - description="parses bidirectional expansion", - raw="+orders+", - expected_result=ParsedSelector( - kind=SelectorKind.NAME, value="orders", upstream=True, downstream=True - ), - ), - ParseSelectorTestCase( - description="parses seed typed selector", - raw="seed:country_codes", - expected_result=ParsedSelector(kind=SelectorKind.SEED, value="country_codes"), - ), - ParseSelectorTestCase( - description="parses source typed selector", - raw="source:raw_orders", - expected_result=ParsedSelector(kind=SelectorKind.SOURCE, value="raw_orders"), - ), - ParseSelectorTestCase( - description="parses tag typed selector", - raw="tag:nightly", - expected_result=ParsedSelector(kind=SelectorKind.TAG, value="nightly"), - ), - ParseSelectorTestCase( - description="parses path typed selector", - raw="path:models/staging", - expected_result=ParsedSelector(kind=SelectorKind.PATH, value="models/staging"), - ), - ParseSelectorTestCase( - description="parses typed selector with upstream expansion", - raw="+seed:country_codes", - expected_result=ParsedSelector( - kind=SelectorKind.SEED, value="country_codes", upstream=True - ), - ), - ParseSelectorTestCase( - description="parses path selector as a~b tuple", - raw="raw~orders", - expected_result=PathSelector(start_name="raw", end_name="orders"), - ), - ParseSelectorTestCase( - description="parses path selector with upstream expansion", - raw="+raw~orders", - expected_result=PathSelector(start_name="raw", end_name="orders", upstream=True), - ), - ParseSelectorTestCase( - description="parses path selector with downstream expansion", - raw="raw~orders+", - expected_result=PathSelector(start_name="raw", end_name="orders", downstream=True), - ), - ParseSelectorTestCase( - description="parses path selector with endpoint expansion on both sides", - raw="+raw~orders+", - expected_result=PathSelector( - start_name="raw", end_name="orders", upstream=True, downstream=True - ), - ), - ParseSelectorTestCase( - description="parses bare slash as path selector", - raw="staging/", - expected_result=ParsedSelector(kind=SelectorKind.PATH, value="staging"), - ), - ParseSelectorTestCase( - description="parses leading bare slash as path selector", - raw="/staging", - expected_result=ParsedSelector(kind=SelectorKind.PATH, value="staging"), - ), - ParseSelectorTestCase( - description="parses nested bare slash as path selector", - raw="staging/orders/", - expected_result=ParsedSelector(kind=SelectorKind.PATH, value="staging/orders"), - ), - ParseSelectorTestCase( - description="parses bare slash without trailing slash as path selector", - raw="staging/orders", - expected_result=ParsedSelector(kind=SelectorKind.PATH, value="staging/orders"), - ), - ParseSelectorTestCase( - description="parses bare slash with upstream expansion", - raw="+staging/", - expected_result=ParsedSelector(kind=SelectorKind.PATH, value="staging", upstream=True), - ), - ParseSelectorTestCase( - description="parses bare slash with downstream expansion", - raw="staging/+", - expected_result=ParsedSelector( - kind=SelectorKind.PATH, value="staging", downstream=True - ), - ), - ], - ids=lambda case: case.description, -) -def test_given_raw_selector_when_parsing_then_returns_expected_result( - test_case: ParseSelectorTestCase, -) -> None: - result: ParsedSelector | PathSelector = parse_project_selector(test_case.raw) - - assert result == test_case.expected_result - - -@pytest.mark.parametrize( - "test_case", - [ - ParseSelectorErrorTestCase( - description="raises on empty selector", - raw="", - expected_error_type=ValueError, - ), - ParseSelectorErrorTestCase( - description="raises when plus appears inside path selector", - raw="a~+b", - expected_error_type=ValueError, - ), - ParseSelectorErrorTestCase( - description="raises on unknown typed prefix", - raw="unknown:value", - expected_error_type=ValueError, - ), - ParseSelectorErrorTestCase( - description="raises on empty value after colon", - raw="seed:", - expected_error_type=ValueError, - ), - ParseSelectorErrorTestCase( - description="raises on plus-only selector", - raw="+", - expected_error_type=ValueError, - ), - ParseSelectorErrorTestCase( - description="raises on empty tilde side", - raw="a~", - expected_error_type=ValueError, - ), - ], - ids=lambda case: case.description, -) -def test_given_invalid_selector_when_parsing_then_raises( - test_case: ParseSelectorErrorTestCase, -) -> None: - with pytest.raises(test_case.expected_error_type): - parse_project_selector(test_case.raw) - - -@pytest.mark.parametrize( - "test_case", - [ - ResolveSelectorTestCase( - description="selects single model by name", - select=("orders",), - exclude=(), - expected_names=frozenset({"orders"}), - ), - ResolveSelectorTestCase( - description="selects model with upstream expansion", - select=("+joined",), - exclude=(), - expected_names=frozenset( - {"joined", "orders", "customers", "raw_orders", "raw_customers"} - ), - ), - ResolveSelectorTestCase( - description="selects model with downstream expansion", - select=("orders+",), - exclude=(), - expected_names=frozenset({"orders", "joined"}), - ), - ResolveSelectorTestCase( - description="selects resources by name glob", - select=("*orders",), - exclude=(), - expected_names=frozenset({"orders", "raw_orders"}), - ), - ResolveSelectorTestCase( - description="expands downstream from every name glob match", - select=("*orders+",), - exclude=(), - expected_names=frozenset({"raw_orders", "orders", "joined"}), - ), - ResolveSelectorTestCase( - description="excludes resources by name glob", - select=(), - exclude=("raw_*",), - expected_names=frozenset({"orders", "customers", "joined", "codes"}), - ), - ResolveSelectorTestCase( - description="unions multiple select tokens", - select=("orders", "customers"), - exclude=(), - expected_names=frozenset({"orders", "customers"}), - ), - ResolveSelectorTestCase( - description="unions space-separated tokens in one select", - select=("orders customers",), - exclude=(), - expected_names=frozenset({"orders", "customers"}), - ), - ResolveSelectorTestCase( - description="subtracts excluded models from selected", - select=("orders", "customers"), - exclude=("customers",), - expected_names=frozenset({"orders"}), - ), - ResolveSelectorTestCase( - description="returns all keys when select is empty", - select=(), - exclude=(), - expected_names=frozenset( - {"orders", "customers", "joined", "raw_orders", "raw_customers", "codes"} - ), - ), - ResolveSelectorTestCase( - description="subtracts exclusions from all keys when select is empty", - select=(), - exclude=("customers",), - expected_names=frozenset({"orders", "joined", "raw_orders", "raw_customers", "codes"}), - ), - ResolveSelectorTestCase( - description="selects seed by typed selector", - select=("seed:codes",), - exclude=(), - expected_names=frozenset({"codes"}), - ), - ResolveSelectorTestCase( - description="selects source by typed selector", - select=("source:raw_orders",), - exclude=(), - expected_names=frozenset({"raw_orders"}), - ), - ResolveSelectorTestCase( - description="selects sources by typed name glob", - select=("source:raw_*",), - exclude=(), - expected_names=frozenset({"raw_orders", "raw_customers"}), - ), - ResolveSelectorTestCase( - description="intersects comma-separated tokens", - select=("orders+,+joined",), - exclude=(), - expected_names=frozenset({"orders", "joined"}), - ), - ResolveSelectorTestCase( - description="selects bidirectional expansion", - select=("+orders+",), - exclude=(), - expected_names=frozenset({"raw_orders", "orders", "joined"}), - ), - ResolveSelectorTestCase( - description="resolves path selector through resolve flow", - select=("raw_orders~joined",), - exclude=(), - expected_names=frozenset({"raw_orders", "orders", "joined"}), - ), - ResolveSelectorTestCase( - description="path selector with upstream expansion includes start upstreams", - select=("+orders~joined",), - exclude=(), - expected_names=frozenset({"raw_orders", "orders", "joined"}), - ), - ResolveSelectorTestCase( - description="path selector with downstream expansion includes end downstreams", - select=("raw_orders~orders+",), - exclude=(), - expected_names=frozenset({"raw_orders", "orders", "joined"}), - ), - ResolveSelectorTestCase( - description="path selector with endpoint expansion on both sides includes both expansions", - select=("+raw_orders~orders+",), - exclude=(), - expected_names=frozenset({"raw_orders", "orders", "joined"}), - ), - ResolveSelectorTestCase( - description="subtracts expanded exclude from selected", - select=("+joined",), - exclude=("orders+",), - expected_names=frozenset({"customers", "raw_customers", "raw_orders"}), - ), - ], - ids=lambda case: case.description, -) -def test_given_selectors_when_resolving_then_returns_expected_names( - test_case: ResolveSelectorTestCase, - diamond_graph: tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - ], -) -> None: - all_keys: dict[str, CompiledObjectKey] = diamond_graph[0] - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = diamond_graph[1] - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = diamond_graph[2] - - result: frozenset[CompiledObjectKey] = resolve_project_selectors( - select=test_case.select, - exclude=test_case.exclude, - all_keys=all_keys, - upstream_deps=upstream, - downstream_deps=downstream, - tag_index={}, - path_index={}, - ) - result_names: frozenset[str] = frozenset(key.name for key in result) - - assert result_names == test_case.expected_names - - -@pytest.mark.parametrize( - "test_case", - [ - ResolveSelectorTestCase( - description="consumer selection includes required table function", - select=("customer_order_summary",), - exclude=(), - expected_names=frozenset({"customer_orders", "customer_order_summary"}), - ), - ResolveSelectorTestCase( - description="upstream expansion traverses through table function", - select=("+customer_order_summary",), - exclude=(), - expected_names=frozenset({"orders", "customer_orders", "customer_order_summary"}), - ), - ResolveSelectorTestCase( - description="table function downstream expansion reaches consumer", - select=("customer_orders+",), - exclude=(), - expected_names=frozenset({"customer_orders", "customer_order_summary"}), - ), - ], - ids=lambda case: case.description, -) -def test_given_model_table_function_dependency_when_selecting_then_graph_expands( - test_case: ResolveSelectorTestCase, -) -> None: - base_key: CompiledObjectKey = CompiledObjectKey(resource_type="model", name="orders") - function_key: CompiledObjectKey = CompiledObjectKey( - resource_type="table_fn", name="customer_orders" - ) - consumer_key: CompiledObjectKey = CompiledObjectKey( - resource_type="model", name="customer_order_summary" - ) - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = { - base_key: (), - function_key: (base_key,), - consumer_key: (function_key,), - } - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = build_downstream_deps( - upstream - ) - all_keys: dict[str, CompiledObjectKey] = { - key.name: key for key in (base_key, function_key, consumer_key) - } - - selected: frozenset[CompiledObjectKey] = resolve_project_selectors( - select=test_case.select, - exclude=test_case.exclude, - all_keys=all_keys, - upstream_deps=upstream, - downstream_deps=downstream, - tag_index={}, - path_index={}, - ) - - assert frozenset(key.name for key in selected) == test_case.expected_names - - -@pytest.mark.parametrize( - "test_case", - [ - ResolveSelectorErrorTestCase( - description="raises when selector references unknown name", - select=("nonexistent_model",), - exclude=(), - expected_error_type=ValueError, - ), - ResolveSelectorErrorTestCase( - description="raises when selector name glob matches nothing", - select=("missing_*",), - exclude=(), - expected_error_type=ValueError, - ), - ], - ids=lambda case: case.description, -) -def test_given_unknown_name_when_resolving_then_raises( - test_case: ResolveSelectorErrorTestCase, - diamond_graph: tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - ], -) -> None: - all_keys: dict[str, CompiledObjectKey] = diamond_graph[0] - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = diamond_graph[1] - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = diamond_graph[2] - - with pytest.raises(test_case.expected_error_type): - resolve_project_selectors( - select=test_case.select, - exclude=test_case.exclude, - all_keys=all_keys, - upstream_deps=upstream, - downstream_deps=downstream, - tag_index={}, - path_index={}, - ) - - -@pytest.mark.parametrize( - "test_case", - [ - ResolveSelectorTestCase( - description="selects models by tag", - select=("tag:nightly",), - exclude=(), - expected_names=frozenset({"orders", "joined"}), - ), - ResolveSelectorTestCase( - description="selects tag with downstream expansion", - select=("tag:staging+",), - exclude=(), - expected_names=frozenset({"orders", "joined"}), - ), - ResolveSelectorTestCase( - description="intersects tag with name selector", - select=("tag:nightly,orders",), - exclude=(), - expected_names=frozenset({"orders"}), - ), - ResolveSelectorTestCase( - description="excludes tag from selected", - select=("orders", "customers", "joined"), - exclude=("tag:nightly",), - expected_names=frozenset({"customers"}), - ), - ], - ids=lambda case: case.description, -) -def test_given_tag_selectors_when_resolving_then_returns_expected_names( - test_case: ResolveSelectorTestCase, - diamond_graph: tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - ], - diamond_tag_index: dict[str, frozenset[CompiledObjectKey]], -) -> None: - all_keys: dict[str, CompiledObjectKey] = diamond_graph[0] - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = diamond_graph[1] - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = diamond_graph[2] - - result: frozenset[CompiledObjectKey] = resolve_project_selectors( - select=test_case.select, - exclude=test_case.exclude, - all_keys=all_keys, - upstream_deps=upstream, - downstream_deps=downstream, - tag_index=diamond_tag_index, - path_index={}, - ) - result_names: frozenset[str] = frozenset(key.name for key in result) - - assert result_names == test_case.expected_names - - -@pytest.mark.parametrize( - "test_case", - [ - ResolveSelectorTestCase( - description="selects models by path folder including nested subdirectories", - select=("path:models/staging",), - exclude=(), - expected_names=frozenset({"stg_orders", "stg_customers", "stg_deep"}), - ), - ResolveSelectorTestCase( - description="selects models root path", - select=("path:models",), - exclude=(), - expected_names=frozenset( - {"stg_orders", "stg_customers", "stg_deep", "int_enriched", "fact_orders"} - ), - ), - ResolveSelectorTestCase( - description="selects models by slash convention with explicit root", - select=("models/staging/",), - exclude=(), - expected_names=frozenset({"stg_orders", "stg_customers", "stg_deep"}), - ), - ResolveSelectorTestCase( - description="selects models by leading slash convention with explicit root", - select=("/models/staging",), - exclude=(), - expected_names=frozenset({"stg_orders", "stg_customers", "stg_deep"}), - ), - ResolveSelectorTestCase( - description="selects single folder with one model", - select=("path:models/marts",), - exclude=(), - expected_names=frozenset({"fact_orders"}), - ), - ResolveSelectorTestCase( - description="selects path with downstream expansion", - select=("path:models/staging+",), - exclude=(), - expected_names=frozenset( - { - "stg_orders", - "stg_customers", - "stg_deep", - "int_enriched", - "fact_orders", - } - ), - ), - ResolveSelectorTestCase( - description="selects path with upstream expansion", - select=("+path:models/marts",), - exclude=(), - expected_names=frozenset( - { - "fact_orders", - "int_enriched", - "stg_orders", - "stg_customers", - "raw_orders", - "raw_customers", - } - ), - ), - ResolveSelectorTestCase( - description="intersects path with name selector", - select=("path:models/staging,stg_orders",), - exclude=(), - expected_names=frozenset({"stg_orders"}), - ), - ResolveSelectorTestCase( - description="excludes path from selected", - select=("+fact_orders",), - exclude=("path:models/staging",), - expected_names=frozenset( - {"fact_orders", "int_enriched", "raw_orders", "raw_customers"} - ), - ), - ResolveSelectorTestCase( - description="slash path with explicit root selects single-model folder", - select=("models/intermediate/",), - exclude=(), - expected_names=frozenset({"int_enriched"}), - ), - ResolveSelectorTestCase( - description="selects only nested subdirectory not parent", - select=("path:models/staging/raw",), - exclude=(), - expected_names=frozenset({"stg_deep"}), - ), - ], - ids=lambda case: case.description, -) -def test_given_path_selectors_when_resolving_then_returns_expected_names( - test_case: ResolveSelectorTestCase, - path_graph: tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, str], - ], -) -> None: - all_keys: dict[str, CompiledObjectKey] = path_graph[0] - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = path_graph[1] - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = path_graph[2] - path_idx: dict[CompiledObjectKey, str] = path_graph[3] - - result: frozenset[CompiledObjectKey] = resolve_project_selectors( - select=test_case.select, - exclude=test_case.exclude, - all_keys=all_keys, - upstream_deps=upstream, - downstream_deps=downstream, - tag_index={}, - path_index=path_idx, - ) - result_names: frozenset[str] = frozenset(key.name for key in result) - - assert result_names == test_case.expected_names - - -@pytest.mark.parametrize( - "test_case", - [ - ResolveSelectorErrorTestCase( - description="raises when path omits explicit root", - select=("path:nonexistent",), - exclude=(), - expected_error_type=ValueError, - expected_error_fragment="path selectors require an explicit root", - ), - ResolveSelectorErrorTestCase( - description="raises when slash path omits explicit root", - select=("nonexistent/",), - exclude=(), - expected_error_type=ValueError, - expected_error_fragment="path selectors require an explicit root", - ), - ResolveSelectorErrorTestCase( - description="raises with folder name when explicit path matches no models", - select=("path:models/nonexistent",), - exclude=(), - expected_error_type=ValueError, - expected_error_fragment="no models found under path 'models/nonexistent'", - ), - ], - ids=lambda case: case.description, -) -def test_given_invalid_path_selector_when_resolving_then_raises_with_message( - test_case: ResolveSelectorErrorTestCase, - path_graph: tuple[ - dict[str, CompiledObjectKey], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]], - dict[CompiledObjectKey, str], - ], -) -> None: - all_keys: dict[str, CompiledObjectKey] = path_graph[0] - upstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = path_graph[1] - downstream: dict[CompiledObjectKey, tuple[CompiledObjectKey, ...]] = path_graph[2] - path_idx: dict[CompiledObjectKey, str] = path_graph[3] - - with pytest.raises(test_case.expected_error_type, match=test_case.expected_error_fragment): - resolve_project_selectors( - select=test_case.select, - exclude=test_case.exclude, - all_keys=all_keys, - upstream_deps=upstream, - downstream_deps=downstream, - tag_index={}, - path_index=path_idx, - ) - - @pytest.mark.parametrize( "test_case", [ diff --git a/tests/unit/src/sqlbuild/compiler/planner/main/_test_types.py b/tests/unit/src/sqlbuild/compiler/planner/main/_test_types.py index 502519ebf2..fad25ca9a9 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/main/_test_types.py +++ b/tests/unit/src/sqlbuild/compiler/planner/main/_test_types.py @@ -110,14 +110,6 @@ class SelectionStalenessClassifierTestCase: expected_warnings: tuple[SelectionStalenessWarning, ...] -@dataclass(frozen=True) -class SqlbuildModelSelectorNamesTestCase: - description: str - term: str - expected_model_names: tuple[str, ...] - expected_translation: str | None - - @dataclass(frozen=True) class InclusiveCursorEndTestCase: description: str diff --git a/tests/unit/src/sqlbuild/compiler/planner/main/helpers.py b/tests/unit/src/sqlbuild/compiler/planner/main/helpers.py index e68966d93b..672fc0c2b4 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/main/helpers.py +++ b/tests/unit/src/sqlbuild/compiler/planner/main/helpers.py @@ -40,51 +40,6 @@ def model_definition_hash(project: CompiledProjectView, name: str) -> str: return compute_query_hash(query_sql=model.query_sql, dialect=project.sql_analysis_dialect) -def build_sqlbuild_model_selector_project() -> CompiledProjectView: - return CompiledProject( - run_id="selector-test-run", - effective_target_name=None, - effective_connection={}, - effective_vars={}, - models=( - build_sqlbuild_model_selector_model( - name="fact_orders", - relative_path=Path("models/marts/fact_orders.sql"), - tags=("nightly",), - ), - build_sqlbuild_model_selector_model( - name="dim_customers", - relative_path=Path("models/marts/dim_customers.sql"), - tags=("nightly", "customer"), - ), - build_sqlbuild_model_selector_model( - name="stg_orders", - relative_path=Path("models/staging/stg_orders.sql"), - tags=("staging",), - ), - ), - ) - - -def build_sqlbuild_model_selector_model( - *, name: str, relative_path: Path, tags: tuple[str, ...] -) -> CompiledModelView: - return CompiledModel( - key=CompiledObjectKey(resource_type=CompiledResourceType.MODEL, name=name), - deps=(), - name=name, - relative_path=relative_path, - query_sql=f"SELECT 1 AS {name}", - config=CompileModelConfig(values={"tags": tags}), - destination=CompiledRelationLocation( - database=None, - schema=None, - name=name, - qualified_name=None, - ), - ) - - def build_execution_plan_from_kwargs(**kwargs: Any) -> PlanOutput: """Adapt flat planner kwargs to the grouped build_execution_plan inputs.""" diff --git a/tests/unit/src/sqlbuild/compiler/planner/main/test_sqlbuild_model_selectors.py b/tests/unit/src/sqlbuild/compiler/planner/main/test_sqlbuild_model_selectors.py deleted file mode 100644 index e46555e333..0000000000 --- a/tests/unit/src/sqlbuild/compiler/planner/main/test_sqlbuild_model_selectors.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import pytest - -from sqlbuild._native import resolve_sqlbuild_model_selector_names -from tests.unit.src.sqlbuild.compiler.planner.main._test_types import ( - SqlbuildModelSelectorNamesTestCase, -) -from tests.unit.src.sqlbuild.compiler.planner.main.helpers import ( - build_sqlbuild_model_selector_project, -) - - -@pytest.mark.parametrize( - "test_case", - [ - SqlbuildModelSelectorNamesTestCase( - description="resolves model name selector", - term="fact_orders", - expected_model_names=("fact_orders",), - expected_translation=None, - ), - SqlbuildModelSelectorNamesTestCase( - description="resolves tag selector", - term="tag:nightly", - expected_model_names=("fact_orders", "dim_customers"), - expected_translation=None, - ), - SqlbuildModelSelectorNamesTestCase( - description="resolves path selector", - term="path:models/marts", - expected_model_names=("fact_orders", "dim_customers"), - expected_translation=None, - ), - SqlbuildModelSelectorNamesTestCase( - description="translates backslash path selector", - term="path:models\\marts", - expected_model_names=("fact_orders", "dim_customers"), - expected_translation="path:models/marts", - ), - SqlbuildModelSelectorNamesTestCase( - description="unknown selector resolves empty", - term="state:modified", - expected_model_names=(), - expected_translation=None, - ), - ], - ids=lambda case: case.description, -) -def test_given_sqlbuild_model_selector_when_resolving_names_then_returns_expected_names( - test_case: SqlbuildModelSelectorNamesTestCase, -) -> None: - model_names, translation = resolve_sqlbuild_model_selector_names( - project=build_sqlbuild_model_selector_project(), - term=test_case.term, - ) - - assert model_names == test_case.expected_model_names - assert translation == test_case.expected_translation diff --git a/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/_test_types.py b/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/_test_types.py index 64cefbdae3..938cf864fa 100644 --- a/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/_test_types.py +++ b/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/_test_types.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from sqlbuild._native import ParsedSelector, PythonSqlRunSelection +from sqlbuild._native import PythonSqlRunSelection from sqlbuild.compiler.python_nodes.types import PythonNodeKind from sqlbuild.python_nodes.models import SqlResourceRef @@ -29,13 +29,6 @@ class PythonNodeSelectorTestCase: expected_names: frozenset[str] -@dataclass(frozen=True) -class PythonNodeParseSelectorTestCase: - description: str - raw: str - expected_result: ParsedSelector - - @dataclass(frozen=True) class PythonNodeSelectorErrorTestCase: description: str diff --git a/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_selectors.py b/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_selectors.py index 6d3c8e1827..bbebfe503c 100644 --- a/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_selectors.py +++ b/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_selectors.py @@ -5,14 +5,10 @@ import pytest from sqlbuild._native import ( - ParsedSelector, PythonNodeGraph, - SelectorKind, - parse_project_selector, resolve_python_node_selection, ) from tests.unit.src.sqlbuild.compiler.python_nodes._helpers._test_types import ( - PythonNodeParseSelectorTestCase, PythonNodeSelectorErrorTestCase, PythonNodeSelectorTestCase, ) @@ -21,43 +17,6 @@ ) -@pytest.mark.parametrize( - "test_case", - [ - PythonNodeParseSelectorTestCase( - description="parses task typed selector", - raw="task:prepare_orders", - expected_result=ParsedSelector(kind=SelectorKind.TASK, value="prepare_orders"), - ), - PythonNodeParseSelectorTestCase( - description="parses asset typed selector", - raw="asset:export_orders", - expected_result=ParsedSelector(kind=SelectorKind.ASSET, value="export_orders"), - ), - PythonNodeParseSelectorTestCase( - description="parses loader typed selector", - raw="loader:load_events", - expected_result=ParsedSelector(kind=SelectorKind.LOADER, value="load_events"), - ), - PythonNodeParseSelectorTestCase( - description="parses check typed selector", - raw="check:check_orders_export", - expected_result=ParsedSelector( - kind=SelectorKind.CHECK, - value="check_orders_export", - ), - ), - ], - ids=lambda case: case.description, -) -def test_given_python_node_typed_selector_when_parsing_then_returns_expected_kind( - test_case: PythonNodeParseSelectorTestCase, -) -> None: - result: object = parse_project_selector(test_case.raw) - - assert result == test_case.expected_result - - @pytest.mark.parametrize( "test_case", [ diff --git a/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_unified_selectors.py b/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_unified_selectors.py index 8dd3cc597c..9f7fb862bd 100644 --- a/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_unified_selectors.py +++ b/tests/unit/src/sqlbuild/compiler/python_nodes/_helpers/test_unified_selectors.py @@ -5,11 +5,9 @@ import pytest from sqlbuild._native import ( - CompiledObjectKey, ProjectGraph, PythonNodeGraph, PythonSqlRunSelection, - resolve_project_selectors, ) from sqlbuild.refs import seed from tests.unit.src.sqlbuild.compiler.python_nodes._helpers._test_types import ( @@ -357,18 +355,8 @@ def test_given_sql_only_selector_when_resolving_unified_then_sql_keys_match_sql_ project_graph=project_graph, python_graph=build_sibling_sql_ref_python_node_graph(), ) - sql_only: frozenset[CompiledObjectKey] = resolve_project_selectors( - select=(test_case.selector,), - exclude=(), - all_keys=project_graph.all_keys, - upstream_deps=project_graph.upstream_deps, - downstream_deps=project_graph.downstream_deps, - tag_index=project_graph.tag_index, - path_index=project_graph.path_index, - ) assert frozenset(key.name for key in result.sql_keys) == test_case.expected_sql_names - assert frozenset(key.name for key in sql_only) == test_case.expected_sql_names @pytest.mark.parametrize(