Skip to content

Commit e092705

Browse files
refactor(query-engine): replace panics with Result/None (#342)
* refactor(query-engine): replace panics with Result/None * added a separate error type * fix
1 parent b5bdb8a commit e092705

10 files changed

Lines changed: 154 additions & 66 deletions

File tree

‎asap-query-engine/src/drivers/query/adapters/elastic_http.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl QueryRequestAdapter for ElasticHttpAdapter {
9191
match self.config.language {
9292
QueryLanguage::elastic_sql => "/_sql",
9393
QueryLanguage::elastic_querydsl => "/_search",
94-
_ => panic!("Invalid query language configured for Elastic"),
94+
_ => unreachable!("Elastic adapter config is validated at startup"),
9595
}
9696
}
9797

‎asap-query-engine/src/engines/simple_engine/mod.rs‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::sync::{Arc, RwLock};
1616
use std::time::Instant;
1717
use tracing::{debug, warn};
1818

19+
use crate::precompute_operators::AccumulatorError;
1920
use crate::AggregateCore;
2021

2122
use asap_types::enums::WindowType;
@@ -1050,16 +1051,22 @@ impl SimpleEngine {
10501051
debug!(" Merging accumulators (should_merge=true)");
10511052
#[cfg(feature = "extra_debugging")]
10521053
let merge_start = Instant::now();
1053-
let merged_accumulator = self.merge_accumulators(&precomputes);
1054-
#[cfg(feature = "extra_debugging")]
1055-
let merge_duration = merge_start.elapsed();
1056-
#[cfg(feature = "extra_debugging")]
1057-
debug!(
1058-
" Merge completed in {:.2}ms, result type: {}",
1059-
merge_duration.as_secs_f64() * 1000.0,
1060-
merged_accumulator.get_accumulator_type()
1061-
);
1062-
merged.insert(key.clone(), merged_accumulator);
1054+
match self.merge_accumulators(&precomputes) {
1055+
Ok(merged_accumulator) => {
1056+
#[cfg(feature = "extra_debugging")]
1057+
let merge_duration = merge_start.elapsed();
1058+
#[cfg(feature = "extra_debugging")]
1059+
debug!(
1060+
" Merge completed in {:.2}ms, result type: {}",
1061+
merge_duration.as_secs_f64() * 1000.0,
1062+
merged_accumulator.get_accumulator_type()
1063+
);
1064+
merged.insert(key.clone(), merged_accumulator);
1065+
}
1066+
Err(e) => {
1067+
warn!("Failed to merge accumulators for key {:?}: {}", key, e);
1068+
}
1069+
}
10631070
} else {
10641071
assert_eq!(
10651072
precomputes.len(),
@@ -1088,21 +1095,21 @@ impl SimpleEngine {
10881095
fn merge_accumulators(
10891096
&self,
10901097
accumulators: &[Box<dyn crate::data_model::AggregateCore>],
1091-
) -> Box<dyn crate::data_model::AggregateCore> {
1098+
) -> Result<Box<dyn crate::data_model::AggregateCore>, AccumulatorError> {
10921099
if accumulators.is_empty() {
1093-
panic!("No accumulators to merge");
1100+
return Err(AccumulatorError::EmptySlice);
10941101
}
10951102

10961103
if accumulators.len() == 1 {
1097-
return accumulators[0].clone_boxed_core();
1104+
return Ok(accumulators[0].clone_boxed_core());
10981105
}
10991106

11001107
// Try to use optimized batch merge for KLL accumulators
11011108
if accumulators[0].get_accumulator_type() == AggregationType::DatasketchesKLL {
11021109
use crate::precompute_operators::datasketches_kll_accumulator::DatasketchesKLLAccumulator;
11031110

11041111
match DatasketchesKLLAccumulator::merge_multiple(accumulators) {
1105-
Ok(merged) => return Box::new(merged),
1112+
Ok(merged) => return Ok(Box::new(merged)),
11061113
Err(e) => {
11071114
warn!(
11081115
"Batch merge failed: {}. Falling back to sequential merge.",
@@ -1118,7 +1125,7 @@ impl SimpleEngine {
11181125
use crate::precompute_operators::count_min_sketch_accumulator::CountMinSketchAccumulator;
11191126

11201127
match CountMinSketchAccumulator::merge_multiple(accumulators) {
1121-
Ok(merged) => return Box::new(merged),
1128+
Ok(merged) => return Ok(Box::new(merged)),
11221129
Err(e) => {
11231130
warn!(
11241131
"Batch merge failed: {}. Falling back to sequential merge.",
@@ -1145,7 +1152,7 @@ impl SimpleEngine {
11451152
}
11461153
}
11471154

1148-
result
1155+
Ok(result)
11491156
}
11501157

11511158
/// Collects results when key and value use different aggregations

‎asap-query-engine/src/engines/simple_engine/sql.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,9 @@ impl SimpleEngine {
265265
(QueryType::Spatial, QueryType::TemporalQuantile) => {
266266
QueryPatternType::OneTemporalOneSpatial
267267
}
268-
_ => panic!("Unsupported query type found"),
268+
_ => return None,
269269
},
270-
_ => panic!("Unsupported query type found"),
270+
_ => return None,
271271
};
272272

273273
// For nested queries (spatial of temporal), the outer query has no time clause,

‎asap-query-engine/src/precompute_operators/delta_set_aggregator_accumulator.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::data_model::{
55
use asap_sketchlib::sketches::delta_set_aggregator::{deserialize_msgpack, serialize_msgpack};
66
use serde_json::Value;
77
use std::collections::{HashMap, HashSet};
8+
use tracing::warn;
89

910
use promql_utilities::query_logics::enums::Statistic;
1011

@@ -247,7 +248,11 @@ impl AggregateCore for DeltaSetAggregatorAccumulator {
247248

248249
fn get_keys(&self) -> Option<Vec<KeyByLabelValues>> {
249250
if !self.removed.is_empty() {
250-
panic!("DeltaSetAggregatorAccumulator does not support get_keys when removed items are present");
251+
warn!(
252+
"DeltaSetAggregatorAccumulator::get_keys called with {} removed items; returning None",
253+
self.removed.len()
254+
);
255+
return None;
251256
}
252257
Some(self.added.iter().cloned().collect())
253258
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::fmt;
2+
3+
#[derive(Debug, PartialEq)]
4+
pub enum AccumulatorError {
5+
/// Returned by constructors when `sub_type` is not "min" or "max".
6+
InvalidSubType(String),
7+
/// Returned by `SimpleEngine::merge_accumulators` when called with an empty
8+
/// slice. This is a programming error (violated precondition), not a domain
9+
/// error from the `MergeableAccumulator` trait impls — those erase their
10+
/// errors into `Box<dyn Error>` to accommodate heterogeneous accumulator
11+
/// types (KLL, CMS, etc.) that produce library-specific errors.
12+
EmptySlice,
13+
/// Returned when merging accumulators whose `sub_type` fields disagree.
14+
MergeTypeMismatch { expected: String, got: String },
15+
}
16+
17+
impl fmt::Display for AccumulatorError {
18+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19+
match self {
20+
Self::InvalidSubType(s) => write!(f, "sub_type must be 'min' or 'max', got '{s}'"),
21+
Self::EmptySlice => write!(f, "merge_accumulators called with empty slice"),
22+
Self::MergeTypeMismatch { expected, got } => write!(
23+
f,
24+
"cannot merge accumulators: expected sub_type '{expected}', got '{got}'"
25+
),
26+
}
27+
}
28+
}
29+
30+
impl std::error::Error for AccumulatorError {}

‎asap-query-engine/src/precompute_operators/min_max_accumulator.rs‎

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::error::AccumulatorError;
12
use crate::data_model::{
23
AggregateCore, AggregationType, MergeableAccumulator, SerializableToSink,
34
SingleSubpopulationAggregate, SingleSubpopulationAggregateFactory,
@@ -29,19 +30,19 @@ impl MinMaxAccumulator {
2930
}
3031
}
3132

32-
pub fn new(sub_type: String) -> Self {
33+
pub fn new(sub_type: String) -> Result<Self, AccumulatorError> {
3334
match sub_type.as_str() {
34-
"min" => Self::new_min(),
35-
"max" => Self::new_max(),
36-
_ => panic!("sub_type must be 'min' or 'max'"),
35+
"min" => Ok(Self::new_min()),
36+
"max" => Ok(Self::new_max()),
37+
_ => Err(AccumulatorError::InvalidSubType(sub_type)),
3738
}
3839
}
3940

40-
pub fn with_value(value: f64, sub_type: String) -> Self {
41+
pub fn with_value(value: f64, sub_type: String) -> Result<Self, AccumulatorError> {
4142
if sub_type != "min" && sub_type != "max" {
42-
panic!("sub_type must be 'min' or 'max'");
43+
return Err(AccumulatorError::InvalidSubType(sub_type));
4344
}
44-
Self { value, sub_type }
45+
Ok(Self { value, sub_type })
4546
}
4647

4748
pub fn update(&mut self, value: f64) {
@@ -56,7 +57,7 @@ impl MinMaxAccumulator {
5657
self.value = value;
5758
}
5859
}
59-
_ => panic!("Invalid sub_type"),
60+
_ => unreachable!("MinMaxAccumulator sub_type is always 'min' or 'max'"),
6061
}
6162
}
6263

@@ -73,7 +74,7 @@ impl MinMaxAccumulator {
7374
return Err("sub_type must be 'min' or 'max'".into());
7475
}
7576

76-
Ok(Self::with_value(value, sub_type))
77+
Ok(Self::with_value(value, sub_type)?)
7778
}
7879

7980
pub fn deserialize_from_bytes(buffer: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
@@ -91,7 +92,7 @@ impl MinMaxAccumulator {
9192
_ => return Err("Invalid sub_type byte".into()),
9293
};
9394

94-
Ok(Self::with_value(value, sub_type))
95+
Ok(Self::with_value(value, sub_type)?)
9596
}
9697
}
9798

@@ -108,7 +109,7 @@ impl SerializableToSink for MinMaxAccumulator {
108109
let sub_type_byte = match self.sub_type.as_str() {
109110
"min" => 0u8,
110111
"max" => 1u8,
111-
_ => panic!("Invalid sub_type"),
112+
_ => unreachable!("MinMaxAccumulator sub_type is always 'min' or 'max'"),
112113
};
113114
bytes.push(sub_type_byte);
114115
bytes
@@ -120,19 +121,23 @@ impl MergeableAccumulator<MinMaxAccumulator> for MinMaxAccumulator {
120121
accumulators: Vec<MinMaxAccumulator>,
121122
) -> Result<MinMaxAccumulator, Box<dyn std::error::Error + Send + Sync>> {
122123
if accumulators.is_empty() {
123-
return Err("No accumulators to merge".into());
124+
return Err(AccumulatorError::EmptySlice.into());
124125
}
125126

126127
let sub_type = &accumulators[0].sub_type;
127128

128129
// Verify all accumulators have the same sub_type
129130
for acc in &accumulators {
130131
if acc.sub_type != *sub_type {
131-
return Err("Cannot merge accumulators with different sub_types".into());
132+
return Err(AccumulatorError::MergeTypeMismatch {
133+
expected: sub_type.clone(),
134+
got: acc.sub_type.clone(),
135+
}
136+
.into());
132137
}
133138
}
134139

135-
let mut result = MinMaxAccumulator::new(sub_type.clone());
140+
let mut result = MinMaxAccumulator::new(sub_type.clone())?;
136141

137142
for acc in accumulators {
138143
result.update(acc.value);
@@ -282,7 +287,7 @@ impl SingleSubpopulationAggregateFactory for MinMaxAccumulatorFactory {
282287
Ok(Box::new(MinMaxAccumulator::with_value(
283288
result_value,
284289
self.sub_type.clone(),
285-
)))
290+
)?))
286291
}
287292

288293
fn create_default(&self) -> Box<dyn SingleSubpopulationAggregate> {
@@ -330,9 +335,9 @@ mod tests {
330335

331336
#[test]
332337
fn test_merge_min_accumulators() {
333-
let acc1 = MinMaxAccumulator::with_value(10.0, "min".to_string());
334-
let acc2 = MinMaxAccumulator::with_value(5.0, "min".to_string());
335-
let acc3 = MinMaxAccumulator::with_value(15.0, "min".to_string());
338+
let acc1 = MinMaxAccumulator::with_value(10.0, "min".to_string()).unwrap();
339+
let acc2 = MinMaxAccumulator::with_value(5.0, "min".to_string()).unwrap();
340+
let acc3 = MinMaxAccumulator::with_value(15.0, "min".to_string()).unwrap();
336341

337342
let merged =
338343
<MinMaxAccumulator as MergeableAccumulator<MinMaxAccumulator>>::merge_accumulators(
@@ -345,9 +350,9 @@ mod tests {
345350

346351
#[test]
347352
fn test_merge_max_accumulators() {
348-
let acc1 = MinMaxAccumulator::with_value(10.0, "max".to_string());
349-
let acc2 = MinMaxAccumulator::with_value(5.0, "max".to_string());
350-
let acc3 = MinMaxAccumulator::with_value(15.0, "max".to_string());
353+
let acc1 = MinMaxAccumulator::with_value(10.0, "max".to_string()).unwrap();
354+
let acc2 = MinMaxAccumulator::with_value(5.0, "max".to_string()).unwrap();
355+
let acc3 = MinMaxAccumulator::with_value(15.0, "max".to_string()).unwrap();
351356

352357
let merged =
353358
<MinMaxAccumulator as MergeableAccumulator<MinMaxAccumulator>>::merge_accumulators(
@@ -360,8 +365,8 @@ mod tests {
360365

361366
#[test]
362367
fn test_merge_different_types_error() {
363-
let acc1 = MinMaxAccumulator::with_value(10.0, "min".to_string());
364-
let acc2 = MinMaxAccumulator::with_value(5.0, "max".to_string());
368+
let acc1 = MinMaxAccumulator::with_value(10.0, "min".to_string()).unwrap();
369+
let acc2 = MinMaxAccumulator::with_value(5.0, "max".to_string()).unwrap();
365370

366371
assert!(
367372
<MinMaxAccumulator as MergeableAccumulator<MinMaxAccumulator>>::merge_accumulators(
@@ -373,7 +378,7 @@ mod tests {
373378

374379
#[test]
375380
fn test_serialization() {
376-
let acc = MinMaxAccumulator::with_value(42.5, "min".to_string());
381+
let acc = MinMaxAccumulator::with_value(42.5, "min".to_string()).unwrap();
377382

378383
// Test JSON serialization
379384
let json = acc.serialize_to_json();
@@ -391,10 +396,22 @@ mod tests {
391396
#[test]
392397
fn test_single_subpopulation_aggregate_trait() {
393398
let acc: Box<dyn SingleSubpopulationAggregate> =
394-
Box::new(MinMaxAccumulator::with_value(42.0, "max".to_string()));
399+
Box::new(MinMaxAccumulator::with_value(42.0, "max".to_string()).unwrap());
395400

396401
assert_eq!(acc.query(Statistic::Max, None).unwrap(), 42.0);
397402
assert!(acc.query(Statistic::Min, None).is_err());
398403
assert_eq!(acc.type_name(), "MinMaxAccumulator");
399404
}
405+
406+
#[test]
407+
fn test_new_invalid_sub_type() {
408+
let err = MinMaxAccumulator::new("mean".to_string()).unwrap_err();
409+
assert!(matches!(err, AccumulatorError::InvalidSubType(s) if s == "mean"));
410+
}
411+
412+
#[test]
413+
fn test_with_value_invalid_sub_type() {
414+
let err = MinMaxAccumulator::with_value(1.0, "sum".to_string()).unwrap_err();
415+
assert!(matches!(err, AccumulatorError::InvalidSubType(s) if s == "sum"));
416+
}
400417
}

‎asap-query-engine/src/precompute_operators/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod count_min_sketch_accumulator;
22
pub mod count_min_sketch_with_heap_accumulator;
33
pub mod datasketches_kll_accumulator;
44
pub mod delta_set_aggregator_accumulator;
5+
pub mod error;
56
pub mod hydra_kll_accumulator;
67
pub mod increase_accumulator;
78
pub mod min_max_accumulator;
@@ -15,6 +16,7 @@ pub use count_min_sketch_accumulator::*;
1516
pub use count_min_sketch_with_heap_accumulator::*;
1617
pub use datasketches_kll_accumulator::*;
1718
pub use delta_set_aggregator_accumulator::*;
19+
pub use error::AccumulatorError;
1820
pub use hydra_kll_accumulator::*;
1921
pub use increase_accumulator::*;
2022
pub use min_max_accumulator::*;

0 commit comments

Comments
 (0)