Skip to content

Commit 22d9b2a

Browse files
committed
Match AT warning scan to effective filters
1 parent 4bd7400 commit 22d9b2a

2 files changed

Lines changed: 111 additions & 16 deletions

File tree

test/sql/measures.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,18 @@ GROUP BY region
298298
);
299299
----
300300

301+
statement error
302+
CREATE TABLE ctas_warning_parenthesized_cte_result AS (
303+
WITH cte_warning_parenthesized AS (
304+
SELECT region, 100.0 * AGGREGATE(revenue) / AGGREGATE(revenue) AT (ALL region) AS pct_of_total
305+
FROM sales_v
306+
WHERE year = 2023
307+
GROUP BY region
308+
)
309+
SELECT * FROM cte_warning_parenthesized
310+
);
311+
----
312+
301313
statement error
302314
CREATE TABLE ctas_warning_empty_result AS
303315
SELECT region, 100.0 * AGGREGATE(revenue) / AGGREGATE(revenue) AT (ALL region) AS pct_of_total

yardstick-rs/src/sql/measures.rs

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,7 @@ fn expand_cte_queries(sql: &str) -> CteExpansion {
20562056
}
20572057
}
20582058

2059-
fn top_level_parenthesized_query_body(sql: &str) -> Option<&str> {
2059+
fn top_level_parenthesized_query_body_range(sql: &str) -> Option<(usize, usize)> {
20602060
let upper = sql.to_uppercase();
20612061
let mut idx = 0;
20622062
while let Some(as_pos) = find_top_level_keyword(sql, "AS", idx) {
@@ -2067,7 +2067,8 @@ fn top_level_parenthesized_query_body(sql: &str) -> Option<&str> {
20672067
|| matches_keyword_at(&upper, body_start, "WITH")
20682068
{
20692069
if let Ok((_, body)) = balanced_parens(&sql[open_pos + 1..]) {
2070-
return Some(body);
2070+
let start = open_pos + 1;
2071+
return Some((start, start + body.len()));
20712072
}
20722073
}
20732074
}
@@ -2076,6 +2077,10 @@ fn top_level_parenthesized_query_body(sql: &str) -> Option<&str> {
20762077
None
20772078
}
20782079

2080+
fn top_level_parenthesized_query_body(sql: &str) -> Option<&str> {
2081+
top_level_parenthesized_query_body_range(sql).map(|(start, end)| &sql[start..end])
2082+
}
2083+
20792084
fn extract_base_relation_sql(view_query: &str) -> Option<String> {
20802085
let query = view_query.trim().trim_end_matches(';').trim();
20812086
if query.is_empty() {
@@ -5955,6 +5960,26 @@ fn dimension_key_in_group_by(ident: &str, group_by_cols: &[String]) -> bool {
59555960
})
59565961
}
59575962

5963+
fn effective_at_where_filter_identifiers(modifiers: &[ContextModifier]) -> Vec<String> {
5964+
if modifiers
5965+
.iter()
5966+
.any(|modifier| matches!(modifier, ContextModifier::AllGlobal))
5967+
{
5968+
return Vec::new();
5969+
}
5970+
5971+
modifiers
5972+
.iter()
5973+
.find_map(|modifier| {
5974+
if let ContextModifier::Where(condition) = modifier {
5975+
Some(extract_where_filter_identifiers(condition))
5976+
} else {
5977+
None
5978+
}
5979+
})
5980+
.unwrap_or_default()
5981+
}
5982+
59585983
fn warning_for_at_all_ungrouped_where(
59595984
measure_name: &str,
59605985
modifiers: &[ContextModifier],
@@ -6001,19 +6026,7 @@ fn warning_for_at_all_ungrouped_where(
60016026
.iter()
60026027
.enumerate()
60036028
.filter_map(|(idx, modifier)| {
6004-
if let ContextModifier::Where(condition) = modifier {
6005-
let cleared_by_global_all = modifiers
6006-
.iter()
6007-
.enumerate()
6008-
.any(|(all_idx, other)| {
6009-
all_idx != idx && matches!(other, ContextModifier::AllGlobal)
6010-
});
6011-
if cleared_by_global_all {
6012-
None
6013-
} else {
6014-
Some(extract_where_filter_identifiers(condition))
6015-
}
6016-
} else if let ContextModifier::Set(dim, _) = modifier {
6029+
if let ContextModifier::Set(dim, _) = modifier {
60176030
let dim_key = normalize_dimension_key(dim.split('.').next_back().unwrap_or(dim).trim());
60186031
let removed_by_all =
60196032
modifiers.iter().enumerate().any(|(all_idx, other)| {
@@ -6041,6 +6054,10 @@ fn warning_for_at_all_ungrouped_where(
60416054
})
60426055
.flatten()
60436056
.collect();
6057+
let encoded_filter_dims: HashSet<String> = encoded_filter_dims
6058+
.into_iter()
6059+
.chain(effective_at_where_filter_identifiers(modifiers))
6060+
.collect();
60446061
let mut ungrouped_filters: Vec<String> = extract_where_filter_identifiers(where_clause)
60456062
.into_iter()
60466063
.filter(|ident| source_dims.is_empty() || source_dims.contains(ident))
@@ -6067,7 +6084,30 @@ fn warning_for_at_all_ungrouped_where(
60676084

60686085
/// Expand AGGREGATE() with AT modifiers in SQL
60696086
pub fn expand_aggregate_with_at(sql: &str) -> AggregateExpandResult {
6070-
let cte_expansion = expand_cte_queries(sql);
6087+
let cte_expansion = if let Some((body_start, body_end)) =
6088+
top_level_parenthesized_query_body_range(sql)
6089+
{
6090+
let body_sql = &sql[body_start..body_end];
6091+
let body_expansion = expand_cte_queries(body_sql);
6092+
if body_expansion.had_aggregate
6093+
|| body_expansion.sql != body_sql
6094+
|| !body_expansion.warnings.is_empty()
6095+
{
6096+
let mut expanded_sql = sql.to_string();
6097+
if body_expansion.sql != body_sql {
6098+
expanded_sql.replace_range(body_start..body_end, &body_expansion.sql);
6099+
}
6100+
CteExpansion {
6101+
sql: expanded_sql,
6102+
had_aggregate: body_expansion.had_aggregate,
6103+
warnings: body_expansion.warnings,
6104+
}
6105+
} else {
6106+
expand_cte_queries(sql)
6107+
}
6108+
} else {
6109+
expand_cte_queries(sql)
6110+
};
60716111
let mut sql = cte_expansion.sql;
60726112
let mut had_aggregate = cte_expansion.had_aggregate;
60736113
let mut warnings = cte_expansion.warnings;
@@ -7520,6 +7560,49 @@ FROM orders"#;
75207560
assert!(warning.is_none());
75217561
}
75227562

7563+
#[test]
7564+
fn test_at_all_warns_when_filter_only_in_overwritten_at_where() {
7565+
let source_dims = HashSet::from([
7566+
normalize_identifier_name("year"),
7567+
normalize_identifier_name("region"),
7568+
normalize_identifier_name("category"),
7569+
]);
7570+
let warning = warning_for_at_all_ungrouped_where(
7571+
"revenue",
7572+
&[
7573+
ContextModifier::All("region".to_string()),
7574+
ContextModifier::Where("category = 'A'".to_string()),
7575+
ContextModifier::Where("year = 2024".to_string()),
7576+
],
7577+
Some("year = 2024"),
7578+
&["region".to_string()],
7579+
&source_dims,
7580+
)
7581+
.unwrap();
7582+
assert!(warning.contains("year"));
7583+
}
7584+
7585+
#[test]
7586+
fn test_at_all_does_not_warn_when_filter_in_effective_at_where() {
7587+
let source_dims = HashSet::from([
7588+
normalize_identifier_name("year"),
7589+
normalize_identifier_name("region"),
7590+
normalize_identifier_name("category"),
7591+
]);
7592+
let warning = warning_for_at_all_ungrouped_where(
7593+
"revenue",
7594+
&[
7595+
ContextModifier::All("region".to_string()),
7596+
ContextModifier::Where("year = 2024".to_string()),
7597+
ContextModifier::Where("category = 'A'".to_string()),
7598+
],
7599+
Some("year = 2024"),
7600+
&["region".to_string()],
7601+
&source_dims,
7602+
);
7603+
assert!(warning.is_none());
7604+
}
7605+
75237606
#[test]
75247607
fn test_at_all_does_not_warn_when_set_encodes_filter_dimension() {
75257608
let source_dims = HashSet::from([

0 commit comments

Comments
 (0)