Skip to content

Commit f3a58dc

Browse files
authored
Merge branch 'main' into perf/delta-compile-real-project-shapes
2 parents fe8d749 + 1ecc1d0 commit f3a58dc

84 files changed

Lines changed: 2038 additions & 136 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-analysis/src/column_references/_helpers/references.rs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,17 +346,52 @@ impl Analysis<'_> {
346346
let Some(body) = select_body(expression) else {
347347
return;
348348
};
349+
let shadowed: HashSet<(usize, usize)> = self.lambda_parameter_uses(body);
349350
for node in walk_in_scope(body, false) {
350351
let Expression::Column(column) = node else {
351352
continue;
352353
};
353354
if column.name.name == STAR || column.name.name.to_lowercase() != self.column {
354355
continue;
355356
}
357+
if column.table.is_none()
358+
&& column
359+
.span
360+
.is_some_and(|span| shadowed.contains(&(span.start, span.end)))
361+
{
362+
continue;
363+
}
356364
self.record_column(column, select, chain, role);
357365
}
358366
}
359367

368+
/// Spans of unqualified uses of a lambda parameter named like the column, which read the parameter.
369+
fn lambda_parameter_uses(&self, body: &Expression) -> HashSet<(usize, usize)> {
370+
let mut shadowed: HashSet<(usize, usize)> = HashSet::new();
371+
for node in walk_in_scope(body, false) {
372+
let Expression::Lambda(lambda) = node else {
373+
continue;
374+
};
375+
if !lambda
376+
.parameters
377+
.iter()
378+
.any(|parameter| parameter.name.to_lowercase() == self.column)
379+
{
380+
continue;
381+
}
382+
for inner in walk_in_scope(&lambda.body, false) {
383+
if let Expression::Column(column) = inner
384+
&& column.table.is_none()
385+
&& column.name.name.to_lowercase() == self.column
386+
&& let Some(span) = column.span
387+
{
388+
shadowed.insert((span.start, span.end));
389+
}
390+
}
391+
}
392+
shadowed
393+
}
394+
360395
fn record_column(
361396
&mut self,
362397
column: &polyglot_sql::expressions::Column,

‎crates/sqlbuild-analysis/src/column_references/tests/test_column_references.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ fn given_column_uses_when_analyzing_then_reports_rename_sites() -> Result<(), St
7676
expected_output_aliases: &[],
7777
expected_outputs: 0,
7878
},
79+
ColumnReferenceTestCase {
80+
description: "a lambda parameter named like the column shadows it in the body",
81+
sql: "SELECT list_filter([amount], amount -> amount > 0) AS kept, \
82+
list_filter([1], lambda amount: amount > 0) AS other FROM orders_fact",
83+
output_ctes: &[],
84+
expected_parsed: true,
85+
expected_names: &["amount"],
86+
expected_scopes: &["root"],
87+
expected_bare_projections: 0,
88+
expected_aliases: &[],
89+
expected_star_scopes: &[],
90+
expected_joins: 0,
91+
expected_output_aliases: &[],
92+
expected_outputs: 0,
93+
},
7994
ColumnReferenceTestCase {
8095
description: "output scopes report aliased and bare projections of every branch",
8196
sql: "WITH fixture AS (SELECT 1 AS amount UNION ALL SELECT 2 AS amount) \

‎crates/sqlbuild-attachments/src/audits/_helpers/rendering.rs‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Python's argument merge, SQL rendering and policy resolution for one attachment.
22
3-
use crate::audits::_helpers::parameters::{RenderStop, render_parameterized_sql};
3+
use crate::audits::_helpers::parameters::{
4+
RenderStop, parameter_matches, render_parameterized_sql,
5+
};
46
use crate::audits::_helpers::policies::{resolve_run_scope, resolve_severity};
57
use crate::audits::constants::{MODEL_RESOURCE, SEED_RESOURCE, SOURCE_RESOURCE};
68
use crate::audits::models::{ArgumentValue, AuditAttachment, PolicySource};
@@ -94,3 +96,36 @@ pub(crate) fn merged_arguments(
9496
}
9597
Ok(merged)
9698
}
99+
100+
/// The error for the first explicit argument neither the audit SQL nor its evidence SQL reads.
101+
pub(crate) fn unused_argument(attachment: &AuditAttachment) -> Option<String> {
102+
let mut used: Vec<&str> = parameter_matches(&attachment.sql_body)
103+
.into_iter()
104+
.map(|item| item.name)
105+
.collect();
106+
if let Some(evidence) = &attachment.evidence_sql {
107+
used.extend(
108+
parameter_matches(evidence)
109+
.into_iter()
110+
.map(|item| item.name),
111+
);
112+
}
113+
let implicit: Vec<&str> = attachment
114+
.implicit_arguments
115+
.iter()
116+
.map(|(name, _)| name.as_str())
117+
.collect();
118+
let (name, _) = attachment
119+
.explicit_arguments
120+
.iter()
121+
.find(|(name, _)| !used.contains(&name.as_str()) && !implicit.contains(&name.as_str()))?;
122+
let definition: String = attachment
123+
.definition_path
124+
.as_deref()
125+
.map_or_else(|| "its built-in definition".to_owned(), str::to_owned);
126+
Some(format!(
127+
"{} audit '{}' passes argument '{name}', which the audit's SQL in {definition} never \
128+
uses; remove '{name}' from the attachment",
129+
attachment.owner_label, attachment.definition_name
130+
))
131+
}

‎crates/sqlbuild-attachments/src/audits/main/render_attached_audit.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Attached generic audit rendering: argument merge, SQL, severity and run scope.
22
3-
use crate::audits::_helpers::rendering::{merged_arguments, policies, rendered};
3+
use crate::audits::_helpers::rendering::{merged_arguments, policies, rendered, unused_argument};
44
use crate::audits::models::{ArgumentValue, AuditAttachment, AuditRendering, RenderedAudit};
55

66
/// Render one attachment, or the error rendering raises.
@@ -14,6 +14,9 @@ pub fn render_attached_audit(attachment: &AuditAttachment) -> AuditRendering {
1414
.as_deref()
1515
.map(|evidence| rendered(attachment, evidence, &arguments))
1616
.transpose()?;
17+
if let Some(message) = unused_argument(attachment) {
18+
return Err(message);
19+
}
1720
Ok(RenderedAudit {
1821
sql_body,
1922
evidence_sql,

‎crates/sqlbuild-attachments/src/audits/models.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub struct AuditAttachment {
2828
/// Python's `str(owner_file)` and the generic audit name, which its error messages quote.
2929
pub owner_label: String,
3030
pub definition_name: String,
31+
/// The project file defining the audit, or `None` for a built-in audit.
32+
pub definition_path: Option<String>,
3133
pub sql_body: String,
3234
pub evidence_sql: Option<String>,
3335
/// Arguments from the attached resource, such as `model` and `column`; always text.

‎crates/sqlbuild-attachments/src/audits/tests/helpers.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub(super) fn attachment(
4040
AuditAttachment {
4141
owner_label: "models/schema.yml".to_owned(),
4242
definition_name: "accepted_values".to_owned(),
43+
definition_path: None,
4344
sql_body: "SELECT * FROM t WHERE @column NOT IN (@'values')".to_owned(),
4445
evidence_sql: Some("SELECT @column FROM t".to_owned()),
4546
implicit_arguments: vec![("column".to_owned(), "status".to_owned())],

‎crates/sqlbuild-attachments/src/audits/tests/test_render_attached_audit.rs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,28 @@ fn given_attached_audits_when_rendering_then_python_rendering_is_returned() {
8080
implicit column from attached context",
8181
),
8282
},
83+
AttachedAuditTestCase {
84+
description: "an argument the audit SQL never reads names itself for removal",
85+
attachment: AuditAttachment {
86+
definition_path: Some("audits/generic/accepted_values.sql".to_owned()),
87+
..attachment(
88+
vec![
89+
values.clone(),
90+
(
91+
"where_condition".to_owned(),
92+
ArgumentValue::Text("status <> 'void'".to_owned()),
93+
),
94+
],
95+
None,
96+
None,
97+
)
98+
},
99+
expected_rendering: failed(
100+
"models/schema.yml audit 'accepted_values' passes argument 'where_condition', \
101+
which the audit's SQL in audits/generic/accepted_values.sql never uses; remove \
102+
'where_condition' from the attachment",
103+
),
104+
},
83105
AttachedAuditTestCase {
84106
description: "a missing argument is Python's error before the policies",
85107
attachment: attachment(Vec::new(), Some("fatal"), None),

‎crates/sqlbuild-cli/src/dispatch/main/event_export_warning.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! The stderr warning for lifecycle events dropped or failed at command exit.
22
3+
use sqlbuild_core::event_export::constants::DEFAULT_EVENT_EXPORT_INVOCATION_TIMEOUT_SECONDS;
4+
35
use crate::dispatch::constants::SHUTDOWN_TIMEOUT_SETTING;
46
use crate::dispatch::models::{ExportSummary, ExporterLoss};
57

@@ -32,7 +34,9 @@ pub fn event_export_warning(summary: &ExportSummary, command_succeeded: bool) ->
3234
};
3335
Some(format!(
3436
"{prefix}: {} of {} {unit} dropped, {} failed ({sink_label} {names}). \
35-
Increase {SHUTDOWN_TIMEOUT_SETTING} or check sink health.",
37+
Check sink health: a sink call slower than {DEFAULT_EVENT_EXPORT_INVOCATION_TIMEOUT_SECONDS}s \
38+
drops that sink's events until it returns; events still queued at exit need a longer \
39+
{SHUTDOWN_TIMEOUT_SETTING}.",
3640
summary.dropped, summary.accepted, summary.failed
3741
))
3842
}

‎crates/sqlbuild-cli/src/dispatch/tests/test_event_export_warning.rs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ fn given_final_export_summary_when_formatting_warning_then_reports_loss_only() {
1818
expected_warning: Some(
1919
"Warning: command completed successfully, but lifecycle event export was \
2020
incomplete: 140 of 248 events dropped, 0 failed (sink 'orders_events'). \
21-
Increase sinks.lifecycle.shutdown_timeout or check sink health.",
21+
Check sink health: a sink call slower than 1s drops that sink's events until it \
22+
returns; events still queued at exit need a longer \
23+
sinks.lifecycle.shutdown_timeout.",
2224
),
2325
},
2426
EventExportWarningTestCase {
@@ -28,7 +30,9 @@ fn given_final_export_summary_when_formatting_warning_then_reports_loss_only() {
2830
expected_warning: Some(
2931
"Warning: lifecycle event export was incomplete: 0 of 10 events dropped, \
3032
1 failed (sink 'orders_events'). \
31-
Increase sinks.lifecycle.shutdown_timeout or check sink health.",
33+
Check sink health: a sink call slower than 1s drops that sink's events until it \
34+
returns; events still queued at exit need a longer \
35+
sinks.lifecycle.shutdown_timeout.",
3236
),
3337
},
3438
EventExportWarningTestCase {
@@ -41,7 +45,9 @@ fn given_final_export_summary_when_formatting_warning_then_reports_loss_only() {
4145
expected_warning: Some(
4246
"Warning: command completed successfully, but lifecycle event export was \
4347
incomplete: 4 of 40 event deliveries dropped, 1 failed (sink 'orders_events'). \
44-
Increase sinks.lifecycle.shutdown_timeout or check sink health.",
48+
Check sink health: a sink call slower than 1s drops that sink's events until it \
49+
returns; events still queued at exit need a longer \
50+
sinks.lifecycle.shutdown_timeout.",
4551
),
4652
},
4753
];

‎crates/sqlbuild-model-config/src/header_metadata/_helpers/audits.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ fn configured_audit<N: AuthoredNode>(
9090
let minimum_samples = optional_count(
9191
entry(options, "minimum_samples"),
9292
option_site,
93-
(definition, "minimum_samples"),
93+
(definition, "minimum_samples", options),
9494
)?;
9595
let evidence_limit = optional_count(
9696
entry(options, "evidence_limit"),
9797
option_site,
98-
(definition, "evidence_limit"),
98+
(definition, "evidence_limit", options),
9999
)?;
100100
let meta = optional_meta(entry(options, "meta"), option_site)?;
101101
let arguments: Vec<(N, N)> = options

0 commit comments

Comments
 (0)