Skip to content

Commit 760d387

Browse files
committed
feat(aggregation, stats): add missing value handling for collapse ops and improve group by error handling
- add exclude_missing and missing_val config to Collapse calculator - skip empty field values when exclude_missing is true - replace empty values with configured missing_val in formatted output - add tests for new missing value handling features - fix KeyExtractor strict mode flag for stats command - add proper error propagation for out-of-bounds group by fields - remove redundant group by sorting and simplify iteration - clean up obsolete group by test cases
1 parent 303107f commit 760d387

5 files changed

Lines changed: 2040 additions & 516 deletions

File tree

src/cmd_tva/stats.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ pub fn execute(matches: &ArgMatches) -> anyhow::Result<()> {
467467
if idxs.is_empty() {
468468
None
469469
} else {
470-
Some(KeyExtractor::new(Some(idxs), false, false)) // strict=false for stats
470+
Some(KeyExtractor::new(Some(idxs), false, true)) // strict=true for stats
471471
}
472472
} else {
473473
None
@@ -546,10 +546,17 @@ pub fn execute(matches: &ArgMatches) -> anyhow::Result<()> {
546546
.as_mut()
547547
.unwrap()
548548
.extract_from_row(row, opt_delimiter);
549-
let key = match key_res {
550-
Ok(k) => k.into_owned(),
551-
Err(_) => KeyBuffer::new(),
552-
};
549+
let key = key_res
550+
.map_err(|idx| {
551+
std::io::Error::new(
552+
std::io::ErrorKind::InvalidData,
553+
format!(
554+
"Not enough fields in line for group-by key. Required field index: {}",
555+
idx
556+
),
557+
)
558+
})?
559+
.into_owned();
553560

554561
let agg = groups
555562
.entry(key)
@@ -568,11 +575,7 @@ pub fn execute(matches: &ArgMatches) -> anyhow::Result<()> {
568575

569576
if let Some(proc) = &processor {
570577
if use_grouping {
571-
let mut keys: Vec<_> = groups.keys().collect();
572-
keys.sort();
573-
574-
for key in keys {
575-
let agg = &groups[key];
578+
for (key, agg) in &groups {
576579
print!("{}", String::from_utf8_lossy(key));
577580

578581
let values = proc.format_results(agg);

src/libs/aggregation/ops/text.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,29 @@ pub struct Collapse {
3838
pub field_idx: usize,
3939
pub string_values_slot: usize,
4040
pub delimiter: String,
41+
pub exclude_missing: bool,
42+
pub missing_val: Option<String>,
4143
}
4244

4345
impl Calculator for Collapse {
4446
fn update(&self, agg: &mut Aggregator, row: &dyn Row) {
45-
agg.string_values[self.string_values_slot].push(get_str(row, self.field_idx));
47+
let val = get_str(row, self.field_idx);
48+
if self.exclude_missing && val.is_empty() {
49+
return;
50+
}
51+
agg.string_values[self.string_values_slot].push(val);
4652
}
4753

4854
fn format(&self, agg: &Aggregator) -> String {
49-
agg.string_values[self.string_values_slot].join(&self.delimiter)
55+
let vals = &agg.string_values[self.string_values_slot];
56+
if let Some(ref replacement) = self.missing_val {
57+
vals.iter()
58+
.map(|v| if v.is_empty() { replacement.as_str() } else { v.as_str() })
59+
.collect::<Vec<&str>>()
60+
.join(&self.delimiter)
61+
} else {
62+
vals.join(&self.delimiter)
63+
}
5064
}
5165
}
5266

@@ -138,14 +152,52 @@ mod tests {
138152
field_idx: 0,
139153
string_values_slot: 0,
140154
delimiter: ",".to_string(),
155+
exclude_missing: false,
156+
missing_val: None,
157+
};
158+
159+
calc.update(&mut agg, &StrSliceRow { fields: &["A"] });
160+
calc.update(&mut agg, &StrSliceRow { fields: &["B"] });
161+
162+
assert_eq!(calc.format(&agg), "A,B");
163+
}
164+
165+
#[test]
166+
fn test_collapse_exclude_missing() {
167+
let mut agg = new_agg();
168+
let calc = Collapse {
169+
field_idx: 0,
170+
string_values_slot: 0,
171+
delimiter: ",".to_string(),
172+
exclude_missing: true,
173+
missing_val: None,
141174
};
142175

143176
calc.update(&mut agg, &StrSliceRow { fields: &["A"] });
177+
calc.update(&mut agg, &StrSliceRow { fields: &[""] });
144178
calc.update(&mut agg, &StrSliceRow { fields: &["B"] });
145179

146180
assert_eq!(calc.format(&agg), "A,B");
147181
}
148182

183+
#[test]
184+
fn test_collapse_replace_missing() {
185+
let mut agg = new_agg();
186+
let calc = Collapse {
187+
field_idx: 0,
188+
string_values_slot: 0,
189+
delimiter: ",".to_string(),
190+
exclude_missing: false,
191+
missing_val: Some("NA".to_string()),
192+
};
193+
194+
calc.update(&mut agg, &StrSliceRow { fields: &["A"] });
195+
calc.update(&mut agg, &StrSliceRow { fields: &[""] });
196+
calc.update(&mut agg, &StrSliceRow { fields: &["B"] });
197+
198+
assert_eq!(calc.format(&agg), "A,NA,B");
199+
}
200+
149201
#[test]
150202
fn test_rand() {
151203
let mut agg = new_agg();

src/libs/aggregation/processor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ impl StatsProcessor {
334334
field_idx: idx,
335335
string_values_slot: slot,
336336
delimiter: config.delimiter.to_string(),
337+
exclude_missing: config.exclude_missing,
338+
missing_val: config.missing_val.clone(),
337339
}));
338340
}
339341
}

0 commit comments

Comments
 (0)