Skip to content

Commit 59393b4

Browse files
committed
Match AT warning scan to effective filters
1 parent 686a2f1 commit 59393b4

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
@@ -282,6 +282,18 @@ GROUP BY region
282282
);
283283
----
284284

285+
statement error
286+
CREATE TABLE ctas_warning_parenthesized_cte_result AS (
287+
WITH cte_warning_parenthesized AS (
288+
SELECT region, 100.0 * AGGREGATE(revenue) / AGGREGATE(revenue) AT (ALL region) AS pct_of_total
289+
FROM sales_v
290+
WHERE year = 2023
291+
GROUP BY region
292+
)
293+
SELECT * FROM cte_warning_parenthesized
294+
);
295+
----
296+
285297
statement error
286298
CREATE TABLE ctas_warning_empty_result AS
287299
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() {
@@ -5767,6 +5772,26 @@ fn dimension_key_in_group_by(ident: &str, group_by_cols: &[String]) -> bool {
57675772
})
57685773
}
57695774

5775+
fn effective_at_where_filter_identifiers(modifiers: &[ContextModifier]) -> Vec<String> {
5776+
if modifiers
5777+
.iter()
5778+
.any(|modifier| matches!(modifier, ContextModifier::AllGlobal))
5779+
{
5780+
return Vec::new();
5781+
}
5782+
5783+
modifiers
5784+
.iter()
5785+
.find_map(|modifier| {
5786+
if let ContextModifier::Where(condition) = modifier {
5787+
Some(extract_where_filter_identifiers(condition))
5788+
} else {
5789+
None
5790+
}
5791+
})
5792+
.unwrap_or_default()
5793+
}
5794+
57705795
fn warning_for_at_all_ungrouped_where(
57715796
measure_name: &str,
57725797
modifiers: &[ContextModifier],
@@ -5813,19 +5838,7 @@ fn warning_for_at_all_ungrouped_where(
58135838
.iter()
58145839
.enumerate()
58155840
.filter_map(|(idx, modifier)| {
5816-
if let ContextModifier::Where(condition) = modifier {
5817-
let cleared_by_global_all = modifiers
5818-
.iter()
5819-
.enumerate()
5820-
.any(|(all_idx, other)| {
5821-
all_idx != idx && matches!(other, ContextModifier::AllGlobal)
5822-
});
5823-
if cleared_by_global_all {
5824-
None
5825-
} else {
5826-
Some(extract_where_filter_identifiers(condition))
5827-
}
5828-
} else if let ContextModifier::Set(dim, _) = modifier {
5841+
if let ContextModifier::Set(dim, _) = modifier {
58295842
let dim_key = normalize_dimension_key(dim.split('.').next_back().unwrap_or(dim).trim());
58305843
let removed_by_all =
58315844
modifiers.iter().enumerate().any(|(all_idx, other)| {
@@ -5853,6 +5866,10 @@ fn warning_for_at_all_ungrouped_where(
58535866
})
58545867
.flatten()
58555868
.collect();
5869+
let encoded_filter_dims: HashSet<String> = encoded_filter_dims
5870+
.into_iter()
5871+
.chain(effective_at_where_filter_identifiers(modifiers))
5872+
.collect();
58565873
let mut ungrouped_filters: Vec<String> = extract_where_filter_identifiers(where_clause)
58575874
.into_iter()
58585875
.filter(|ident| source_dims.is_empty() || source_dims.contains(ident))
@@ -5879,7 +5896,30 @@ fn warning_for_at_all_ungrouped_where(
58795896

58805897
/// Expand AGGREGATE() with AT modifiers in SQL
58815898
pub fn expand_aggregate_with_at(sql: &str) -> AggregateExpandResult {
5882-
let cte_expansion = expand_cte_queries(sql);
5899+
let cte_expansion = if let Some((body_start, body_end)) =
5900+
top_level_parenthesized_query_body_range(sql)
5901+
{
5902+
let body_sql = &sql[body_start..body_end];
5903+
let body_expansion = expand_cte_queries(body_sql);
5904+
if body_expansion.had_aggregate
5905+
|| body_expansion.sql != body_sql
5906+
|| !body_expansion.warnings.is_empty()
5907+
{
5908+
let mut expanded_sql = sql.to_string();
5909+
if body_expansion.sql != body_sql {
5910+
expanded_sql.replace_range(body_start..body_end, &body_expansion.sql);
5911+
}
5912+
CteExpansion {
5913+
sql: expanded_sql,
5914+
had_aggregate: body_expansion.had_aggregate,
5915+
warnings: body_expansion.warnings,
5916+
}
5917+
} else {
5918+
expand_cte_queries(sql)
5919+
}
5920+
} else {
5921+
expand_cte_queries(sql)
5922+
};
58835923
let mut sql = cte_expansion.sql;
58845924
let mut had_aggregate = cte_expansion.had_aggregate;
58855925
let mut warnings = cte_expansion.warnings;
@@ -7218,6 +7258,49 @@ FROM orders"#;
72187258
assert!(warning.is_none());
72197259
}
72207260

7261+
#[test]
7262+
fn test_at_all_warns_when_filter_only_in_overwritten_at_where() {
7263+
let source_dims = HashSet::from([
7264+
normalize_identifier_name("year"),
7265+
normalize_identifier_name("region"),
7266+
normalize_identifier_name("category"),
7267+
]);
7268+
let warning = warning_for_at_all_ungrouped_where(
7269+
"revenue",
7270+
&[
7271+
ContextModifier::All("region".to_string()),
7272+
ContextModifier::Where("category = 'A'".to_string()),
7273+
ContextModifier::Where("year = 2024".to_string()),
7274+
],
7275+
Some("year = 2024"),
7276+
&["region".to_string()],
7277+
&source_dims,
7278+
)
7279+
.unwrap();
7280+
assert!(warning.contains("year"));
7281+
}
7282+
7283+
#[test]
7284+
fn test_at_all_does_not_warn_when_filter_in_effective_at_where() {
7285+
let source_dims = HashSet::from([
7286+
normalize_identifier_name("year"),
7287+
normalize_identifier_name("region"),
7288+
normalize_identifier_name("category"),
7289+
]);
7290+
let warning = warning_for_at_all_ungrouped_where(
7291+
"revenue",
7292+
&[
7293+
ContextModifier::All("region".to_string()),
7294+
ContextModifier::Where("year = 2024".to_string()),
7295+
ContextModifier::Where("category = 'A'".to_string()),
7296+
],
7297+
Some("year = 2024"),
7298+
&["region".to_string()],
7299+
&source_dims,
7300+
);
7301+
assert!(warning.is_none());
7302+
}
7303+
72217304
#[test]
72227305
fn test_at_all_does_not_warn_when_set_encodes_filter_dimension() {
72237306
let source_dims = HashSet::from([

0 commit comments

Comments
 (0)