Skip to content

Commit ecc31b7

Browse files
authored
refactor: hold planner contexts natively and call native helpers directly (#1121)
* refactor(planner): hold planner scope, identity, relation and entry contexts in Rust PlannerScopeResolution, PlannerIdentityContext, PlannerRelationsContext, ModelPlanContext and PlanEntryBuildInputs are frozen native classes with typed fields instead of generic native records. Plan entry construction, SQL resolution and cursor bound computation read their fields directly. Constructors take the same keywords; the classes no longer support dataclasses.replace. * refactor(migrations): plan model migrations over Rust requests Model migration requests, declarations, discovery results and compatibility results are Rust structs. Discovery, fingerprint matching, origin resolution, compatibility checks, decisions, execution choices, redirected cursor inspection and the live-state inspection are called directly instead of through the native module. Removes the corresponding native records and pyfunctions, none of which had a Python caller. * refactor(planner): call native planner helpers directly Planner resolution, entry, migration, scenario, warehouse, audit and command-plan code calls the native helpers it needs as Rust functions instead of looking them up on the native module and calling them with keyword dictionaries. Keyword-only helpers receive their arguments directly; signature helpers receive typed values. SQL code scanning in reference resolution uses the scanner directly, and the migration fingerprint cache default is constructed natively. Calls that tests deliberately replace on the native module stay module lookups: SQL test planning, migration fingerprint computation, table type planning, and the compile pipeline's execution plan and Python run outputs. The stale-identity unit test now supplies the project fields change detection reads instead of replacing it. * refactor(commands): call native helpers directly in freshness, janitor, check and diff The freshness, janitor, check, diff and deferred-target code calls the native discovery, graph, selection, connection, target, progress, state and output helpers it uses as Rust functions instead of looking them up on the native module. SQL test chain resolution is called directly; SQL test planning stays a module lookup because tests observe it there. Native classes the commands construct (progress reporters, the event writer, the cost context and freshness errors) are still created through the module.
1 parent 1ec45de commit ecc31b7

147 files changed

Lines changed: 2632 additions & 2808 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ fn python_literal_sql_calls(
386386
/// How an owner of `owner_kind` should read `target` instead of naming its relation in SQL.
387387
#[pyfunction]
388388
#[pyo3(signature = (*, owner_kind, r#ref, upstream_loader_by_source = None))]
389-
fn hard_coded_relation_remedy(
389+
pub(crate) fn hard_coded_relation_remedy(
390390
owner_kind: &str,
391391
r#ref: &Bound<'_, PyAny>,
392392
upstream_loader_by_source: Option<&Bound<'_, PyAny>>,
@@ -407,7 +407,7 @@ fn hard_coded_relation_remedy(
407407
/// The P008 message for SQL naming a relation kept only as a compatibility view.
408408
#[pyfunction]
409409
#[pyo3(signature = (*, owner_label, written, method, model_name))]
410-
fn old_name_reference_message(
410+
pub(crate) fn old_name_reference_message(
411411
owner_label: &str,
412412
written: &str,
413413
method: &str,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mod identity;
22
mod measurement;
33
pub(crate) mod registration;
4-
mod render;
4+
pub(crate) mod render;
55
mod schema_audits;

‎crates/sqlbuild-python/src/bindings/_helpers/auditing/render.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fn qualified<'py>(mapping: &Bound<'py, PyAny>, name: &str) -> PyResult<Option<Bo
3030
/// Render audit SQL from unresolved markers, applying relation overrides first.
3131
#[pyfunction]
3232
#[pyo3(signature = (**request))]
33-
fn render_audit_sql(request: Option<&Bound<'_, PyDict>>) -> PyResult<String> {
33+
pub(crate) fn render_audit_sql(request: Option<&Bound<'_, PyDict>>) -> PyResult<String> {
3434
let request = Request::new("render_audit_sql", request)?;
3535
let sql: String = request.get("unresolved_sql")?.extract()?;
3636
let models: Bound<'_, PyAny> = request.get("model_locations")?;

‎crates/sqlbuild-python/src/bindings/_helpers/authored_values/project_vars.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn preview_json(value: &Bound<'_, PyAny>) -> PyResult<String> {
4545
/// One project var value as interpolated text; structured values are rejected.
4646
#[pyfunction]
4747
#[pyo3(signature = (*, value, label))]
48-
fn render_project_var_text(value: &Bound<'_, PyAny>, label: &str) -> PyResult<String> {
48+
pub(crate) fn render_project_var_text(value: &Bound<'_, PyAny>, label: &str) -> PyResult<String> {
4949
project_var_text(value, label)
5050
}
5151

‎crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs‎

Lines changed: 83 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
//! `sqb check`: run the selected Python checks after their Python dependencies.
22
3+
use crate::bindings::_helpers::cli::progress::current_native_progress_projector;
4+
use crate::bindings::_helpers::cli::progress_text::write_execution_header;
5+
use crate::bindings::_helpers::compile_driver::pipeline_entries::build_static_pipeline_result;
6+
use crate::bindings::_helpers::compile_driver::python_calls::keywords;
7+
use crate::bindings::_helpers::compile_driver::relation_targets::build_project_relation_targets;
8+
use crate::bindings::_helpers::compile_driver::relation_targets::build_python_relation_targets;
9+
use crate::bindings::_helpers::compile_driver::selection_phases::compile_static_command_context;
10+
use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver;
11+
use crate::bindings::_helpers::graph::algorithms::transitive_closure_many;
12+
use crate::bindings::_helpers::graph::execution_gates::sql_ref_key;
13+
use crate::bindings::_helpers::graph::python_lifecycle::build_python_sql_run_lifecycle;
14+
use crate::bindings::_helpers::planner_entries::command_plans::build_relation_command_plan;
15+
use crate::bindings::_helpers::planner_phases::objects::object;
16+
use crate::bindings::_helpers::planner_selection::selectors::parse_project_selector;
17+
use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors;
18+
use crate::bindings::_helpers::result_output::documents::write_execution_json_output;
319
use pyo3::prelude::{
420
Bound, Py, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python,
521
};
@@ -70,12 +86,12 @@ fn empty_tuple(py: Python<'_>) -> Bound<'_, PyAny> {
7086

7187
/// Refuse `+`, `~` and path selectors, splitting selectors as Python's `split` does.
7288
fn refuse_graph_selectors(py: Python<'_>, selectors: &[Bound<'_, PyAny>]) -> PyResult<()> {
73-
let parse: Bound<'_, PyAny> = registry::native(py, NativeAttr::ParseProjectSelector)?;
7489
let path_selector: Bound<'_, PyAny> = registry::class(py, RecordId::PathSelector)?;
7590
for selector in selectors {
7691
for token in selector.call_method0("split")?.try_iter()? {
7792
for part in token?.call_method1("split", (",",))?.try_iter()? {
78-
let parsed: Bound<'_, PyAny> = parse.call1((part?,))?;
93+
let parsed: Bound<'_, PyAny> =
94+
parse_project_selector(part?.downcast::<PyString>()?)?;
7995
if parsed.is_instance(&path_selector)?
8096
|| parsed.getattr("upstream")?.is_truthy()?
8197
|| parsed.getattr("downstream")?.is_truthy()?
@@ -163,16 +179,14 @@ fn compile_pipeline<'py>(
163179
&graph.getattr("selected_sql_refs")?,
164180
&[("selected_names", check_names.clone().into_any())],
165181
)?;
166-
let closure: Bound<'py, PyAny> = call_with(
167-
&registry::native(py, NativeAttr::TransitiveClosureMany)?,
168-
&[
169-
("starts", check_names.clone().into_any()),
170-
("edges", graph.getattr("upstream_deps")?),
171-
(
172-
"include_starts",
173-
PyBool::new(py, false).to_owned().into_any(),
174-
),
175-
],
182+
let closure: Bound<'py, PyAny> = object(
183+
py,
184+
transitive_closure_many(
185+
check_names.as_any(),
186+
&graph.getattr("upstream_deps")?,
187+
false,
188+
None,
189+
)?,
176190
)?;
177191
let loader_kind: Bound<'py, PyAny> =
178192
host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("LOADER")?;
@@ -187,10 +201,9 @@ fn compile_pipeline<'py>(
187201
let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?;
188202
let source_type: Bound<'py, PyAny> =
189203
imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?;
190-
let ref_key: Bound<'py, PyAny> = registry::native(py, NativeAttr::SqlRefKey)?;
191204
let mut keys: Vec<Bound<'py, PyAny>> = Vec::new();
192205
for reference in required_refs.try_iter()? {
193-
keys.push(ref_key.call1((reference?,))?);
206+
keys.push(sql_ref_key(py, &reference?)?);
194207
}
195208
for source_file in discovered.getattr("source_files")?.try_iter()? {
196209
for source in source_file?.getattr("source_entries")?.try_iter()? {
@@ -207,12 +220,9 @@ fn compile_pipeline<'py>(
207220
}
208221
}
209222
let keys: Bound<'py, PyAny> = PyFrozenSet::new(py, keys)?.into_any();
210-
let resolver: Bound<'py, PyAny> = call_with(
211-
&registry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?,
212-
&[
213-
("project_dir", invocation.getattr("effective_project_dir")?),
214-
("discovered_inputs", discovered.clone()),
215-
],
223+
let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver(
224+
invocation.getattr("effective_project_dir")?,
225+
discovered.clone(),
216226
)?;
217227
let options: Bound<'py, PyAny> = call_with(
218228
&registry::class(py, RecordId::CompilePipelineOptions)?,
@@ -227,8 +237,8 @@ fn compile_pipeline<'py>(
227237
("external_sql_reference_resolver", resolver),
228238
],
229239
)?;
230-
let context: Bound<'py, PyAny> = call_with(
231-
&registry::native(py, NativeAttr::CompileStaticCommandContext)?,
240+
let context: Bound<'py, PyAny> = compile_static_command_context(Some(&keywords(
241+
py,
232242
&[
233243
("discovered_inputs", discovered),
234244
("adapter", invocation.getattr("adapter")?),
@@ -242,18 +252,18 @@ fn compile_pipeline<'py>(
242252
.getattr("on_progress")?,
243253
),
244254
],
255+
)?))?;
256+
let plan_output: Bound<'py, PyAny> = build_relation_command_plan(
257+
&context.getattr("project")?,
258+
&context.getattr("scope")?,
259+
&context.getattr("relations")?,
245260
)?;
246-
let plan_output: Bound<'py, PyAny> = call_with(
247-
&registry::native(py, NativeAttr::BuildRelationCommandPlan)?,
248-
&[
249-
("project", context.getattr("project")?),
250-
("scope", context.getattr("scope")?),
251-
("relations", context.getattr("relations")?),
252-
],
253-
)?;
254-
call_with(
255-
&registry::native(py, NativeAttr::BuildStaticPipelineResult)?,
256-
&[("context", context), ("plan_output", plan_output)],
261+
build_static_pipeline_result(
262+
py,
263+
Some(keywords(
264+
py,
265+
&[("context", context), ("plan_output", plan_output)],
266+
)?),
257267
)
258268
}
259269

@@ -275,15 +285,12 @@ fn python_selection<'py>(
275285
request: &Bound<'py, PyAny>,
276286
discovered: &Bound<'py, PyAny>,
277287
) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyFrozenSet>)> {
278-
let py: Python<'py> = request.py();
288+
let _py: Python<'py> = request.py();
279289
let python_graph: Bound<'py, PyAny> = python_node_graph(discovered)?;
280-
call_with(
281-
&registry::native(py, NativeAttr::RejectUnitTestSelectors)?,
282-
&[
283-
("select", request.getattr("select")?),
284-
("exclude", request.getattr("exclude")?),
285-
("discovered_inputs", discovered.clone()),
286-
],
290+
reject_unit_test_selectors(
291+
&request.getattr("select")?,
292+
&request.getattr("exclude")?,
293+
discovered,
287294
)?;
288295
let check_names: Bound<'py, PyFrozenSet> = selected_check_names(request, &python_graph)?;
289296
Ok((python_graph, check_names))
@@ -314,27 +321,28 @@ fn prepare<'py>(
314321
("python_node_names", PyFrozenSet::empty(py)?.into_any()),
315322
],
316323
)?;
317-
let lifecycle_plan: Bound<'py, PyAny> = call_with(
318-
&registry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?,
319-
&[
320-
("selection", selection),
321-
("python_graph", python_graph.clone()),
322-
],
323-
)?;
324+
let lifecycle_plan: Bound<'py, PyAny> =
325+
build_python_sql_run_lifecycle(&selection, &python_graph)?;
324326
let relation_refs: Bound<'py, PyAny> = call_with(
325327
&python_graph.getattr("selected_sql_refs")?,
326328
&[("selected_names", check_names.clone().into_any())],
327329
)?;
328330
let project: Bound<'py, PyAny> = pipeline.getattr("project")?;
329331
let plan_output: Bound<'py, PyAny> = pipeline.getattr("plan_output")?;
330-
let relation_targets: Bound<'py, PyAny> = call_with(
331-
&registry::native(py, NativeAttr::BuildPythonRelationTargets)?,
332-
&[
333-
("adapter", adapter.clone()),
334-
("project", project.clone()),
335-
("plan_output", plan_output.clone()),
336-
("required_refs", relation_refs.clone()),
337-
],
332+
let relation_targets: Bound<'py, PyAny> = object(
333+
py,
334+
build_python_relation_targets(
335+
py,
336+
Some(keywords(
337+
py,
338+
&[
339+
("adapter", adapter.clone()),
340+
("project", project.clone()),
341+
("plan_output", plan_output.clone()),
342+
("required_refs", relation_refs.clone()),
343+
],
344+
)?),
345+
)?,
338346
)?;
339347
Ok(Prepared {
340348
check_functions: PyTuple::new(py, check_functions)?.into_any(),
@@ -353,9 +361,15 @@ fn prepare<'py>(
353361
("effective_target_schema", "default_schema"),
354362
)?,
355363
),
356-
project_relations: call_with(
357-
&registry::native(py, NativeAttr::BuildProjectRelationTargets)?,
358-
&[("adapter", adapter), ("plan_output", plan_output)],
364+
project_relations: object(
365+
py,
366+
build_project_relation_targets(
367+
py,
368+
Some(keywords(
369+
py,
370+
&[("adapter", adapter), ("plan_output", plan_output)],
371+
)?),
372+
)?,
359373
)?,
360374
python_graph,
361375
pipeline,
@@ -556,8 +570,7 @@ fn execute<'py>(
556570
let event_writer: Bound<'py, PyAny> =
557571
registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?;
558572
let executed: PyResult<Vec<Bound<'py, PyAny>>> = (|| {
559-
let projector: Bound<'py, PyAny> =
560-
registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?;
573+
let projector: Bound<'py, PyAny> = current_native_progress_projector(py)?;
561574
if !projector.is_none() {
562575
let ordinals: Bound<'py, PyDict> = PyDict::new(py);
563576
let mut total: usize = 0;
@@ -617,16 +630,14 @@ fn complete<'py>(
617630
.call_method1("__truediv__", ("target",))?,
618631
&tuple,
619632
)?;
620-
call_with(
621-
&registry::native(py, NativeAttr::WriteExecutionJsonOutput)?,
622-
&[
623-
(
624-
"payload",
625-
PyString::new(py, &results_json(results)?).into_any(),
626-
),
627-
("json_output", request.getattr("json_output")?),
628-
("json_output_path", request.getattr("json_output_path")?),
629-
],
633+
object(
634+
py,
635+
write_execution_json_output(
636+
py,
637+
PyString::new(py, &results_json(results)?).into_any(),
638+
request.getattr("json_output")?.is_truthy()?,
639+
request.getattr("json_output_path")?,
640+
)?,
630641
)?;
631642
Ok(check_exit_code(&texts))
632643
}
@@ -648,7 +659,7 @@ fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<i32
648659
header.set_item("target", py.None())?;
649660
header.set_item("concurrency", PyInt::new(py, 1))?;
650661
header.set_item("use_color", invocation.getattr("use_color")?)?;
651-
registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?;
662+
write_execution_header(py, Some(header))?;
652663
let prepared: Prepared<'_> = prepare(request, &invocation)?;
653664
let session: Bound<'_, PyAny> = call_with(
654665
&host::entry(py, UserCodeHost::BuildProviderSession)?,

‎crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs‎

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
//! Python check planning: run-state recording, dependency relations and read-side preflight.
22
3+
use crate::bindings::_helpers::compile_driver::python_calls::keywords;
4+
use crate::bindings::_helpers::compile_driver::relation_targets::build_python_relation_targets;
5+
use crate::bindings::_helpers::graph::python_selection::resolve_python_check_excludes;
6+
use crate::bindings::_helpers::planner_phases::objects::object;
37
use pyo3::IntoPyObject;
48
use pyo3::prelude::{
59
Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python,
@@ -15,8 +19,6 @@ use crate::bindings::_helpers::native_host::adapter::AdapterHost;
1519
use crate::bindings::_helpers::native_host::executor::ExecutorHost;
1620
use crate::bindings::_helpers::native_host::host;
1721
use crate::bindings::_helpers::native_host::user_code::UserCodeHost;
18-
use crate::bindings::_helpers::records::ids::NativeAttr;
19-
use crate::bindings::_helpers::records::registry;
2022

2123
const MISSING_RELATION_CODE: &str = "C682";
2224
const MODEL_KIND: &str = "model";
@@ -170,14 +172,20 @@ pub(crate) fn build_check_relation_targets<'py>(
170172
&python_graph.getattr("selected_sql_refs")?,
171173
&[("selected_names", selected_python_names)],
172174
)?;
173-
call_with(
174-
&registry::native(py, NativeAttr::BuildPythonRelationTargets)?,
175-
&[
176-
("adapter", adapter.clone()),
177-
("project", pipeline_result.getattr("project")?),
178-
("plan_output", pipeline_result.getattr("plan_output")?),
179-
("required_refs", required),
180-
],
175+
object(
176+
py,
177+
build_python_relation_targets(
178+
py,
179+
Some(keywords(
180+
py,
181+
&[
182+
("adapter", adapter.clone()),
183+
("project", pipeline_result.getattr("project")?),
184+
("plan_output", pipeline_result.getattr("plan_output")?),
185+
("required_refs", required),
186+
],
187+
)?),
188+
)?,
181189
)
182190
}
183191

@@ -189,9 +197,9 @@ pub(crate) fn relevant_check_functions<'py>(
189197
(selected_dependency_names, completed_sql_names): (&Bound<'py, PyAny>, &Bound<'py, PyAny>),
190198
) -> PyResult<Vec<Bound<'py, PyAny>>> {
191199
let py: Python<'py> = discovered_inputs.py();
192-
let excluded: Bound<'py, PyAny> = call_with(
193-
&registry::native(py, NativeAttr::ResolvePythonCheckExcludes)?,
194-
&[("graph", python_graph.clone()), ("exclude", exclude)],
200+
let excluded: Bound<'py, PyAny> = object(
201+
py,
202+
resolve_python_check_excludes(python_graph, exclude.extract::<Vec<String>>()?)?,
195203
)?;
196204
let check_kind: Bound<'py, PyAny> =
197205
host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("CHECK")?;

‎crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ use sqlbuild_core::check_command::models::{CheckGrouping, CheckResultText, Check
1414
use sqlbuild_core::cli_text::models::CliStyle;
1515
use sqlbuild_core::json::models::JsonValue;
1616

17+
use crate::bindings::_helpers::cli::progress::current_native_progress_projector;
1718
use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments;
18-
use crate::bindings::_helpers::records::ids::NativeAttr;
19-
use crate::bindings::_helpers::records::registry;
2019
use crate::bindings::_helpers::result_output::integration_values::json_value;
20+
use crate::bindings::_helpers::result_output::terminal_index::current_terminal_event_index;
2121

2222
const WARN_SEVERITY: &str = "warn";
2323

@@ -124,8 +124,7 @@ pub(crate) fn write_results(
124124
let check: Bound<'_, PyAny> = check?;
125125
checks.insert(check.getattr("name")?.extract()?, check);
126126
}
127-
let projector: Bound<'_, PyAny> =
128-
registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?;
127+
let projector: Bound<'_, PyAny> = current_native_progress_projector(py)?;
129128
let mut groups: Vec<(String, Vec<CheckResultText>)> = Vec::new();
130129
for result in results {
131130
let text: CheckResultText = check_result_text(result)?;
@@ -169,8 +168,7 @@ pub(crate) fn results_json(results: &[Bound<'_, PyAny>]) -> PyResult<String> {
169168
return check_json(&[]).map_err(|error| PyValueError::new_err(format!("{error:?}")));
170169
};
171170
let py: Python<'_> = first.py();
172-
let index: Bound<'_, PyAny> =
173-
registry::native(py, NativeAttr::CurrentTerminalEventIndex)?.call0()?;
171+
let index: Bound<'_, PyAny> = current_terminal_event_index(py)?;
174172
let mut texts: Vec<CheckResultText> = Vec::new();
175173
for result in results {
176174
let name: Bound<'_, PyAny> = result.getattr("node_name")?;

0 commit comments

Comments
 (0)