From 540eb59d438c65826195a4e655fe7542b606002e Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Fri, 18 Sep 2026 08:38:00 +0100 Subject: [PATCH] fix: accept proven dependency wildcards --- .../src/engine/tests/test_evaluation.rs | 16 +++++++++ .../rules/_helpers/explicit_output_types.rs | 33 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/crates/sqlbuild-rules-native/src/engine/tests/test_evaluation.rs b/crates/sqlbuild-rules-native/src/engine/tests/test_evaluation.rs index 4da3dcae3b..6235af48d1 100644 --- a/crates/sqlbuild-rules-native/src/engine/tests/test_evaluation.rs +++ b/crates/sqlbuild-rules-native/src/engine/tests/test_evaluation.rs @@ -347,6 +347,22 @@ fn given_enforced_contract_outputs_when_evaluating_explicit_type_rule_then_retur expected_fault_count: 1, expected_message_fragment: "wildcard output", }, + test_types::ExplicitOutputTypeTestCase { + description: "proven dependency import wildcard", + query_sql: "WITH final AS (SELECT * FROM __ref(\"orders\")) SELECT order_id FROM final", + columns: json!([{"name": "order_id", "type": "INTEGER", "type_proven": true}]), + contract: "enforced", + expected_fault_count: 0, + expected_message_fragment: "", + }, + test_types::ExplicitOutputTypeTestCase { + description: "unproven dependency import wildcard", + query_sql: "WITH final AS (SELECT * FROM __ref(\"orders\")) SELECT order_id FROM final", + columns: json!([{"name": "order_id", "type": "INTEGER", "type_proven": false}]), + contract: "enforced", + expected_fault_count: 1, + expected_message_fragment: "wildcard output", + }, test_types::ExplicitOutputTypeTestCase { description: "positional set branches require casts", query_sql: "SELECT order_id FROM current_orders UNION ALL SELECT order_id FROM archived_orders", diff --git a/crates/sqlbuild-rules-native/src/rules/_helpers/explicit_output_types.rs b/crates/sqlbuild-rules-native/src/rules/_helpers/explicit_output_types.rs index b00d4b65ce..f3ed9ed4ef 100644 --- a/crates/sqlbuild-rules-native/src/rules/_helpers/explicit_output_types.rs +++ b/crates/sqlbuild-rules-native/src/rules/_helpers/explicit_output_types.rs @@ -2,7 +2,9 @@ use crate::constants::ENFORCED_CONTRACT; use crate::models::{Column, Fault, Model, RuleMetadata}; use crate::rules::_helpers::evaluation::{plain_select, root_select, sole_table_name, top_ctes}; use crate::rules::models::FaultCollector; -use sqlparser::ast::{Expr, Query, Select, SelectItem, SetExpr, SetQuantifier, Spanned}; +use sqlparser::ast::{ + Expr, Query, Select, SelectItem, SetExpr, SetQuantifier, Spanned, TableFactor, +}; #[derive(Clone, Copy)] struct OutputScope<'a> { @@ -95,6 +97,9 @@ impl OutputRule<'_> { item, SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _) ) { + if proven_dependency_wildcard(select, scope.columns) { + continue; + } self.push( Some(source_position(item)), "wildcard output does not establish enforced contract types explicitly".into(), @@ -168,6 +173,29 @@ impl OutputRule<'_> { } } +fn proven_dependency_wildcard(select: &Select, columns: &[Column]) -> bool { + let Some(source) = select.from.first().filter(|_| select.from.len() == 1) else { + return false; + }; + if !plain_select(select, true) || !source.joins.is_empty() { + return false; + } + let TableFactor::Table { + name, + args: Some(_), + .. + } = &source.relation + else { + return false; + }; + let dependency = name.to_string().to_ascii_lowercase(); + matches!(dependency.as_str(), "__ref" | "__source") + && columns + .iter() + .filter(|column| !column.data_type.is_empty()) + .all(|column| column.type_proven) +} + fn final_cte_output_boundary<'a>( query: &'a Query, columns: &[Column], @@ -234,6 +262,9 @@ fn columns_in_output_order_with_missing( let Some(output_names) = set_expression_output_names_with_missing(set_expr) else { return columns.to_vec(); }; + if output_names.len() == 1 && output_names[0].is_none() { + return columns.to_vec(); + } output_names .into_iter() .enumerate()