Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0f93a23
build: guard native bindings against new Python crossings, Python-typ…
kvlonge Oct 17, 2026
e764a37
refactor: read native pyclass getters directly when reading fields by…
kvlonge Oct 17, 2026
6e4fc15
refactor: compute native record properties directly when reading fiel…
kvlonge Oct 17, 2026
9f1e8c1
refactor: capture native error classes at module load instead of look…
kvlonge Oct 17, 2026
2517355
refactor: read multi-line and interned record field chains natively
kvlonge Oct 17, 2026
9739a25
build: skip constant definitions when counting native Python crossings
kvlonge Oct 17, 2026
15c114b
refactor: read Python dicts through the C API instead of calling dict…
kvlonge Oct 17, 2026
c6c4537
refactor: cache stable stdlib lookups, iterate dicts natively and rea…
kvlonge Oct 17, 2026
faeadb8
test: fail test runs on new native field fallbacks to Python for SQLB…
kvlonge Oct 17, 2026
a86039e
test: count SQL test planning and model-chain work natively instead o…
kvlonge Oct 17, 2026
5f721fc
test: count background SQL test planning natively and drop the planne…
kvlonge Oct 17, 2026
3dcdbac
test: assert SQL test planning through native stage work and written …
kvlonge Oct 17, 2026
5cf87ba
fix: report an unreadable native field allowlist and keep strict-mode…
kvlonge Oct 17, 2026
a797308
refactor: read pyo3 field getters directly and record the strict nati…
kvlonge Oct 17, 2026
8fd0f63
test: allow the remaining capture-encoder and DAG row enum reads in s…
kvlonge Oct 17, 2026
5a74bd6
test: allow the dbt interop connection progress callback read in stri…
kvlonge Oct 17, 2026
2e85f5f
test: allow the remaining dbt interop connection progress callback re…
kvlonge Oct 17, 2026
600985d
test: allow the dbt interop build progress and command reads in stric…
kvlonge Oct 17, 2026
63dab5a
Merge branch 'main' into feat/chi599-strict-fields
kvlonge Oct 17, 2026
4b93cd9
fix: let interrupts win over strict native field errors and name clas…
kvlonge Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ use crate::bindings::_helpers::analysis::conversions;
use crate::bindings::_helpers::boundary::panics::compiler_error;
use crate::bindings::_helpers::records::construct::new_record;
use crate::bindings::_helpers::records::fields::field as native_field;
use crate::bindings::_helpers::records::fields::mapping_get;
use crate::bindings::_helpers::records::fields::mapping_get_or;
use crate::bindings::_helpers::records::fields::mapping_items;
use crate::bindings::_helpers::records::fields::mapping_values;
use crate::bindings::_helpers::records::ids::RecordId;
use crate::bindings::models::{CatalogInput, ProjectCatalog};
use crate::bindings::types::CompilerDetach;
Expand All @@ -35,17 +39,17 @@ const GENERIC_DIALECT: &str = "generic";
#[pyclass(module = "sqlbuild._native")]
pub(crate) struct BindingCatalog {
#[pyo3(get)]
schemas: Py<PyDict>,
pub(crate) schemas: Py<PyDict>,
#[pyo3(get)]
dialect: String,
pub(crate) dialect: String,
#[pyo3(get)]
quoted_ignore_case: bool,
pub(crate) quoted_ignore_case: bool,
#[pyo3(get)]
analysis_shapes: Py<PyDict>,
pub(crate) analysis_shapes: Py<PyDict>,
#[pyo3(get)]
expression_shapes: Py<PyDict>,
pub(crate) expression_shapes: Py<PyDict>,
#[pyo3(get)]
native: Py<ProjectCatalog>,
pub(crate) native: Py<ProjectCatalog>,
}

/// A catalog's dialect, case mode, known functions and known types.
Expand Down Expand Up @@ -90,7 +94,7 @@ impl BindingCatalog {
let additions: Bound<'_, PyDict> = PyDict::new(py);
for request in requests {
let schema: Bound<'_, PyAny> = native_field(request, "schema")?;
for item in schema.call_method0("items")?.try_iter()? {
for item in mapping_items(&schema)?.try_iter()? {
let (name, columns): (Bound<'_, PyAny>, Bound<'_, PyAny>) = item?.extract()?;
if columns.is_truthy()? && !schemas.contains(&name)? {
additions.set_item(&name, &columns)?;
Expand Down Expand Up @@ -164,9 +168,9 @@ impl BindingCatalog {
) -> PyResult<()> {
let shapes: &Bound<'_, PyDict> = self.analysis_shapes.bind(py);
let updates: Bound<'_, PyDict> = PyDict::new(py);
for item in nullability.call_method0("items")?.try_iter()? {
for item in mapping_items(nullability)?.try_iter()? {
let (name, columns): (Bound<'_, PyAny>, Bound<'_, PyAny>) = item?.extract()?;
let typed: Bound<'_, PyAny> = types.call_method1("get", (&name, PyDict::new(py)))?;
let typed: Bound<'_, PyAny> = mapping_get_or(types, &name, PyDict::new(py))?;
let shape: Bound<'_, PyTuple> = PyTuple::new(py, [typed, columns])?;
let unchanged: bool = match shapes.get_item(&name)? {
Some(known) => known.eq(&shape)?,
Expand Down Expand Up @@ -221,7 +225,7 @@ impl BindingCatalog {
return Ok(columns.clone());
}
let mut exact_input: bool = false;
for shape in inputs.call_method0("values")?.try_iter()? {
for shape in mapping_values(inputs)?.try_iter()? {
for name in shape?.try_iter()? {
let name: String = name?.extract()?;
exact_input |= name.contains(QUOTED_IDENTIFIER_DELIMITER);
Expand Down Expand Up @@ -256,7 +260,7 @@ pub(crate) fn ignores_quoted_case(
if dialect.is_none() || dialect.extract::<String>()? != SNOWFLAKE_DIALECT {
return Ok(false);
}
let parameters: Bound<'_, PyAny> = connection.call_method1("get", (SESSION_PARAMETERS,))?;
let parameters: Bound<'_, PyAny> = mapping_get(connection, SESSION_PARAMETERS)?;
let Ok(parameters) = parameters.downcast::<PyDict>() else {
return Ok(false);
};
Expand Down Expand Up @@ -295,9 +299,7 @@ pub(crate) fn schema_validations<'py>(
for (index, request) in requests.iter().enumerate() {
let key: CatalogKey = (
native_dialect(&native_field(request, "dialect")?)?,
request
.getattr("quoted_identifiers_ignore_case")?
.extract()?,
native_field(request, "quoted_identifiers_ignore_case")?.extract()?,
native_field(request, "known_functions")?.extract()?,
native_field(request, "known_types")?.extract()?,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ use crate::bindings::_helpers::compile_driver::python_calls::imported;
use crate::bindings::_helpers::graph::execution_gates::model_python_hook_names;
use crate::bindings::_helpers::native_host::host;
use crate::bindings::_helpers::native_host::user_code::UserCodeHost;
use crate::bindings::_helpers::records::construct::error_class;
use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, record_class};
use crate::bindings::_helpers::records::fields::field as native_field;
use crate::bindings::_helpers::records::fields::mapping_items;
use crate::bindings::_helpers::records::ids::{ErrorId, RecordId};
use crate::bindings::_helpers::records::registry;
use crate::bindings::types::CompilerDetach;

const EXPLICIT_REFERENCES_CONTEXT: &str = "native explicit-reference checks";
Expand Down Expand Up @@ -100,8 +101,7 @@ pub(crate) fn explicit_reference_report<'py>(
{
path_order.set_item(path?, index)?;
}
let retains_old_names: bool = !discovered_inputs
.getattr("project_config")?
let retains_old_names: bool = !native_field(discovered_inputs, "project_config")?
.getattr("migrations")?
.getattr("old_name_views")?
.is_none();
Expand Down Expand Up @@ -141,7 +141,7 @@ pub(crate) fn explicit_reference_report<'py>(
{
Ok(report) => python_report(py, &report, &owners),
Err(ReferenceFailure::CompileInput(message)) => Err(PyErr::from_value(
registry::error(py, ErrorId::CompileInput)?.call1((message,))?,
error_class(py, ErrorId::CompileInput)?.call1((message,))?,
)),
Err(ReferenceFailure::UnknownDialect(message)) => Err(compiler_error(message)),
Err(ReferenceFailure::Internal(reason)) => Err(compiler_error(native_failure(
Expand Down Expand Up @@ -205,8 +205,7 @@ fn loader_owners<'py>(
let mut sources_by_loader: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for source in native_field(project, "sources")?.try_iter()? {
let source: Bound<'py, PyAny> = source?;
let loader: Option<String> = source
.getattr("source_entry")?
let loader: Option<String> = native_field(&source, "source_entry")?
.getattr("loader")?
.extract()?;
if let Some(loader) = loader {
Expand Down Expand Up @@ -405,7 +404,7 @@ pub(crate) fn hard_coded_relation_remedy(
);
let mut upstream_loaders: Vec<(String, String)> = Vec::new();
if let Some(loaders) = upstream_loader_by_source.filter(|loaders| !loaders.is_none()) {
for item in loaders.call_method0("items")?.try_iter()? {
for item in mapping_items(loaders)?.try_iter()? {
upstream_loaders.push(item?.extract()?);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ pub(crate) fn unchecked_columns(model: &Bound<'_, PyAny>) -> PyResult<Vec<String
if let Ok(view) = model.downcast::<NativeCompiledModel>() {
return Ok(view.get().unchecked_columns().to_vec());
}
model
.getattr("unchecked_output_columns")?
native_field(model, "unchecked_output_columns")?
.try_iter()?
.map(|column| column?.extract())
.collect()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::bindings::_helpers::records::construct::record_class;
use crate::bindings::_helpers::records::fields::field as native_field;
use crate::bindings::_helpers::records::fields::mapping_get;
use crate::bindings::_helpers::records::ids::RecordId;
use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python};
use pyo3::types::{PyBool, PyList, PyTuple};
Expand Down Expand Up @@ -82,8 +83,7 @@ pub(crate) fn project_facts(
let hook: Bound<'_, PyAny> = hook?;
hooks.push(PythonHook {
name: native_field(&hook, "name")?.extract()?,
path: hook
.getattr("relative_path")?
path: native_field(&hook, "relative_path")?
.call_method0("as_posix")?
.extract()?,
reads: ref_keys(&native_field(&hook, "reads")?)?,
Expand Down Expand Up @@ -124,7 +124,7 @@ fn model_references(
native_field(model, "destination_row")?.extract()?;
let mut sql_hooks: Vec<SqlHook> = Vec::new();
for hook_key in MODEL_HOOK_KEYS {
for (index, entry) in hook_items(&values.call_method1("get", (hook_key,))?)?
for (index, entry) in hook_items(&mapping_get(&values, hook_key)?)?
.into_iter()
.enumerate()
{
Expand Down Expand Up @@ -166,7 +166,7 @@ fn python_hook_names(
) -> PyResult<Vec<String>> {
let mut names: Vec<String> = Vec::new();
for hook_key in MODEL_HOOK_KEYS {
let value: Bound<'_, PyAny> = values.call_method1("get", (hook_key,))?;
let value: Bound<'_, PyAny> = mapping_get(values, hook_key)?;
let items: Vec<Bound<'_, PyAny>> = if value.is_instance(&entries.python)? {
vec![value]
} else if is_sequence(&value) {
Expand Down Expand Up @@ -201,7 +201,7 @@ fn is_sequence(value: &Bound<'_, PyAny>) -> bool {

/// The `migrate_from` string when the model keeps an old-name view, as Python resolves it.
fn migrate_from(values: &Bound<'_, PyAny>, retains_old_names: bool) -> PyResult<Option<String>> {
let raw: Bound<'_, PyAny> = values.call_method1("get", (MIGRATE_FROM_KEY,))?;
let raw: Bound<'_, PyAny> = mapping_get(values, MIGRATE_FROM_KEY)?;
let Ok(text) = raw.extract::<String>() else {
return Ok(None);
};
Expand All @@ -225,8 +225,7 @@ fn audit_gate(audit: &Bound<'_, PyAny>) -> PyResult<AuditGate> {
};
Ok(AuditGate {
name: native_field(audit, "name")?.extract()?,
path: audit
.getattr("audit_file")?
path: native_field(audit, "audit_file")?
.getattr("relative_path")?
.str()?
.extract()?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,11 @@ pub(crate) fn comparison_request(
set_difference_operator: &str,
sql_analysis_dialect: Option<&str>,
) -> PyResult<Value> {
let chain: Vec<Value> = entry
.getattr("chain")?
let chain: Vec<Value> = native_field(entry, "chain")?
.try_iter()?
.map(|step| chain_step_request(&step?))
.collect::<PyResult<_>>()?;
let assertions: Vec<Value> = entry
.getattr("assertions")?
let assertions: Vec<Value> = native_field(entry, "assertions")?
.try_iter()?
.map(|assertion| {
let assertion: Bound<'_, PyAny> = assertion?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost;
use crate::bindings::_helpers::native_host::host;
use crate::bindings::_helpers::records::construct::enum_member;
use crate::bindings::_helpers::records::fields::field as native_field;
use crate::bindings::_helpers::records::fields::mapping_get;
use crate::bindings::_helpers::records::fields::mapping_items;
use crate::bindings::_helpers::records::ids::RecordId;
use crate::bindings::_helpers::semantic_shapes::shapes::{VALID_FROM_DEFAULT, VALID_TO_DEFAULT};

Expand Down Expand Up @@ -95,7 +97,7 @@ fn session_request<'py>(
let py: Python<'py> = catalog.py();
let profile: &Bound<'py, PyAny> = &request.profile;
let mut families: Vec<(String, Vec<_>)> = Vec::new();
for item in request.dynamic_families.call_method0("items")?.try_iter()? {
for item in mapping_items(&request.dynamic_families)?.try_iter()? {
let (name, declared): (String, Bound<'py, PyAny>) = item?.extract()?;
families.push((name, family_rows(&declared)?));
}
Expand Down Expand Up @@ -198,9 +200,7 @@ fn record_catalog_changes<'py>(
}
schemas.call_method1("update", (&additions,))?;
if !additions.is_empty() {
catalog
.getattr("native")?
.call_method1("update_relations", (&additions,))?;
native_field(catalog, "native")?.call_method1("update_relations", (&additions,))?;
}
let selected_types: Bound<'py, PyDict> = PyDict::new(py);
let selected_nullability: Bound<'py, PyDict> = PyDict::new(py);
Expand All @@ -217,9 +217,7 @@ fn record_catalog_changes<'py>(
let arguments: Bound<'py, PyDict> = PyDict::new(py);
arguments.set_item("types", selected_types)?;
arguments.set_item("nullability", selected_nullability)?;
catalog
.getattr("prepare_analysis")?
.call((), Some(&arguments))?;
native_field(catalog, "prepare_analysis")?.call((), Some(&arguments))?;
Ok(())
}

Expand Down Expand Up @@ -260,7 +258,7 @@ fn model_rows<'py>(
let mut rows: Vec<Bound<'py, PyAny>> = Vec::with_capacity(model_inputs.len());
for model_input in model_inputs {
let values: Bound<'py, PyAny> = native_field(model_input, "config")?.getattr("values")?;
let get = |key: &str| values.call_method1("get", (key,));
let get = |key: &str| mapping_get(&values, key);
let query_sql: Bound<'py, PyAny> = PyString::new(
py,
&analysis_cursor_sql(
Expand All @@ -273,7 +271,7 @@ fn model_rows<'py>(
let mut placeholders: Vec<(String, String)> = Vec::new();
let raw: Bound<'py, PyAny> = get("placeholders")?;
if raw.is_instance_of::<PyDict>() {
for item in raw.call_method0("items")?.try_iter()? {
for item in mapping_items(&raw)?.try_iter()? {
let (key, value): (Bound<'py, PyAny>, Bound<'py, PyAny>) = item?.extract()?;
placeholders.push((key.str()?.extract()?, value.str()?.extract()?));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::bindings::_helpers::native_host::adapter::AdapterHost;
use crate::bindings::_helpers::native_host::host;
use crate::bindings::_helpers::records::construct::record_class;
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;

const GENERIC_DIALECT: &str = "generic";
Expand All @@ -37,8 +38,7 @@ pub(crate) fn nullability_rules(profile: &Bound<'_, PyAny>) -> PyResult<(Pairs,
}
let adapter: Bound<'_, PyDict> = PyDict::new(py);
let mut rows: Pairs = Vec::new();
for item in profile
.getattr("function_nullability_rules")?
for item in native_field(profile, "function_nullability_rules")?
.call_method0("items")?
.try_iter()?
{
Expand All @@ -62,10 +62,10 @@ pub(crate) fn nullability_rules(profile: &Bound<'_, PyAny>) -> PyResult<(Pairs,
/// `{relation: {column: value}}` as ordered rows, each value as `str(value)`.
pub(crate) fn shape_rows(shapes: &Bound<'_, PyAny>) -> PyResult<Shapes> {
let mut rows: Shapes = Vec::new();
for item in shapes.call_method0("items")?.try_iter()? {
for item in mapping_items(shapes)?.try_iter()? {
let (name, shape): (String, Bound<'_, PyAny>) = item?.extract()?;
let mut pairs: Pairs = Vec::new();
for column in shape.call_method0("items")?.try_iter()? {
for column in mapping_items(&shape)?.try_iter()? {
let (column, value): (String, Bound<'_, PyAny>) = column?.extract()?;
pairs.push((column, value.str()?.extract()?));
}
Expand All @@ -76,8 +76,7 @@ pub(crate) fn shape_rows(shapes: &Bound<'_, PyAny>) -> PyResult<Shapes> {

/// A `{key: value}` text mapping's items in order.
pub(crate) fn pairs(mapping: &Bound<'_, PyAny>) -> PyResult<Pairs> {
mapping
.call_method0("items")?
mapping_items(mapping)?
.try_iter()?
.map(|item| item?.extract())
.collect()
Expand Down Expand Up @@ -109,10 +108,7 @@ pub(crate) fn case_sensitive_shapes(
profile: &Bound<'_, PyAny>,
dialect: &Bound<'_, PyAny>,
) -> PyResult<bool> {
if profile
.getattr("quoted_identifiers_ignore_case")?
.is_truthy()?
{
if native_field(profile, "quoted_identifiers_ignore_case")?.is_truthy()? {
return Ok(false);
}
if dialect.is_none() {
Expand All @@ -136,9 +132,7 @@ pub(crate) fn profile_catalog<'py>(
py,
(
dialect.to_owned(),
profile
.getattr("quoted_identifiers_ignore_case")?
.extract()?,
native_field(profile, "quoted_identifiers_ignore_case")?.extract()?,
Vec::new(),
Vec::new(),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ impl NativeModelAnalysisSession {

/// `(shared members, shared members re-analysed alone)` as of the last step.
#[getter]
fn sharing(&self) -> (usize, usize) {
pub(crate) fn sharing(&self) -> (usize, usize) {
self.sharing
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::bindings::_helpers::compile_driver::stage_work::{
};
use crate::bindings::_helpers::records::construct::{enum_from_value, new_record};
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 crate::bindings::models::ProjectCatalog;

Expand Down Expand Up @@ -216,7 +217,7 @@ pub(crate) fn dynamic_contract_proofs<'py>(
let contracts: Vec<ContractRow> = if session.is_none() {
let by_table: Bound<'py, PyAny> = native_field(tables, "dynamic_families_by_table")?;
let mut families: Vec<(String, Vec<FamilyRow>)> = Vec::new();
for item in by_table.call_method0("items")?.try_iter()? {
for item in mapping_items(&by_table)?.try_iter()? {
let (name, declared): (String, Bound<'py, PyAny>) = item?.extract()?;
families.push((name, family_rows(&declared)?));
}
Expand Down Expand Up @@ -325,8 +326,7 @@ pub(crate) fn query_column_rows(
fn query_row(query: &Bound<'_, PyAny>) -> PyResult<QueryRow> {
let placeholders: Bound<'_, PyAny> = native_field(query, "placeholders")?;
let placeholders: Pairs = if placeholders.is_truthy()? {
placeholders
.call_method0("items")?
mapping_items(&placeholders)?
.try_iter()?
.map(|item| item?.extract())
.collect::<PyResult<_>>()?
Expand Down
Loading
Loading