1use std::collections::{BTreeMap, HashMap};
16use std::sync::Arc;
17
18use datatypes::arrow::array::{BooleanArray, Float64Array, Float64Builder, RecordBatch};
19use datatypes::arrow::datatypes::{DataType, Field, Schema};
20use datatypes::value::OrderedF64;
21
22use crate::collider::{CHECK_STEP, Collider, NORMALIZE_STEP, PartitionDim};
23use crate::error::{
24 CheckpointNotCoveredSnafu, CheckpointOverlappedSnafu, DuplicateExprSnafu, Result,
25};
26use crate::expr::{PartitionExpr, RestrictedOp};
27use crate::multi_dim::MultiDimPartitionRule;
28
29pub struct PartitionChecker<'a> {
30 rule: &'a MultiDimPartitionRule,
31 collider: Collider<'a>,
32 dims: Vec<PartitionDim>,
35}
36
37impl<'a> PartitionChecker<'a> {
38 pub fn try_new(rule: &'a MultiDimPartitionRule) -> Result<Self> {
39 let collider = Collider::new(rule.exprs())?;
40 let mut dims = collider
41 .normalized_values
42 .iter()
43 .filter(|(_, values)| !values.is_empty())
44 .map(|(dim, _)| dim.clone())
45 .collect::<Vec<_>>();
46 dims.sort_unstable();
47 Ok(Self {
48 rule,
49 collider,
50 dims,
51 })
52 }
53
54 pub fn check(&self) -> Result<()> {
55 self.run()?;
56 Ok(())
57 }
58}
59
60impl<'a> PartitionChecker<'a> {
62 fn run(&self) -> Result<()> {
63 let mut atomic_exprs = BTreeMap::new();
65 for expr in self.collider.atomic_exprs.iter() {
66 let key = &expr.nucleons;
67 atomic_exprs.insert(key, expr);
68 }
69 if atomic_exprs.len() != self.collider.atomic_exprs.len() {
70 for expr in self.collider.atomic_exprs.iter() {
72 if atomic_exprs.get(&expr.nucleons).unwrap().source_expr_index
73 != expr.source_expr_index
74 {
75 let expr = self.rule.exprs()[expr.source_expr_index].clone();
76 return DuplicateExprSnafu { expr }.fail();
77 }
78 }
79 return DuplicateExprSnafu {
81 expr: PartitionExpr::new(
82 crate::expr::Operand::Column("unknown".to_string()),
83 RestrictedOp::Eq,
84 crate::expr::Operand::Column("expr".to_string()),
85 ),
86 }
87 .fail();
88 }
89
90 let field_names = (0..self.dims.len())
94 .map(|index| index.to_string())
95 .collect::<Vec<_>>();
96 let mut matrix_foundation = HashMap::new();
97 for (dim, field_name) in self.dims.iter().zip(&field_names) {
98 let values = &self.collider.normalized_values[dim];
99 let mut cornerstones = Vec::with_capacity(values.len() * 2 + 1);
100 cornerstones.push(values[0].1 - CHECK_STEP);
101 for value in values {
102 cornerstones.push(value.1);
103 cornerstones.push(value.1 + CHECK_STEP);
104 }
105 matrix_foundation.insert(field_name.as_str(), cornerstones);
106 }
107
108 if matrix_foundation.is_empty() {
110 return Ok(());
111 }
112
113 let matrix_generator = MatrixGenerator::new(matrix_foundation);
114
115 let mut results = Vec::with_capacity(self.collider.atomic_exprs.len());
117 let physical_exprs = self
118 .collider
119 .atomic_exprs
120 .iter()
121 .map(|expr| {
122 expr.to_physical_expr(matrix_generator.schema(), |dim| {
123 self.dims.binary_search(dim).unwrap().to_string()
125 })
126 })
127 .collect::<Vec<_>>();
128 for batch in matrix_generator {
129 results.clear();
130 for physical_expr in &physical_exprs {
131 let columnar_result = physical_expr.evaluate(&batch).unwrap();
132 let array_result = columnar_result.into_array(batch.num_rows()).unwrap();
133 results.push(array_result);
134 }
135 let boolean_results = results
136 .iter()
137 .map(|result| result.as_any().downcast_ref::<BooleanArray>().unwrap())
138 .collect::<Vec<_>>();
139
140 for i in 0..batch.num_rows() {
142 let mut true_count = 0;
143 for result in boolean_results.iter() {
144 if result.value(i) {
145 true_count += 1;
146 }
147 }
148
149 if true_count == 0 {
150 return CheckpointNotCoveredSnafu {
151 checkpoint: self.remap_checkpoint(i, &batch),
152 }
153 .fail();
154 } else if true_count > 1 {
155 return CheckpointOverlappedSnafu {
156 checkpoint: self.remap_checkpoint(i, &batch),
157 }
158 .fail();
159 }
160 }
161 }
162
163 Ok(())
164 }
165
166 fn remap_checkpoint(&self, i: usize, batch: &RecordBatch) -> String {
168 let normalized_row = batch
169 .columns()
170 .iter()
171 .map(|col| {
172 let array = col.as_any().downcast_ref::<Float64Array>().unwrap();
173 array.value(i)
174 })
175 .collect::<Vec<_>>();
176
177 let mut check_point = String::new();
178 let schema = batch.schema();
179 for (col_index, normalized_value) in normalized_row.iter().enumerate() {
180 let dim = schema
181 .field(col_index)
182 .name()
183 .parse::<usize>()
184 .ok()
185 .and_then(|index| self.dims.get(index));
186 let col_name = dim.map(ToString::to_string).unwrap_or_default();
187
188 if col_index > 0 {
189 check_point.push_str(", ");
190 }
191
192 if let Some(values) = dim.and_then(|dim| self.collider.normalized_values.get(dim)) {
194 let normalize_step = NORMALIZE_STEP.0;
195
196 let remainder = normalized_value % normalize_step;
198 let is_on_step = remainder.abs() < f64::EPSILON
199 || (normalize_step - remainder).abs() < f64::EPSILON * 2.0;
200
201 if is_on_step {
202 let index = (normalized_value / normalize_step).round() as usize;
203 if index < values.len() {
204 let original_value = &values[index].0;
205 check_point.push_str(&format!("{}={}", col_name, original_value));
206 } else {
207 check_point.push_str(&format!("{}=unknown", col_name));
208 }
209 } else {
210 let lower_index = (normalized_value / normalize_step).floor() as usize;
211 let upper_index = (normalized_value / normalize_step).ceil() as usize;
212
213 if lower_index == upper_index && lower_index == 0 {
215 let first_original = &values[0].0;
217 check_point.push_str(&format!("{}<{}", col_name, first_original));
218 } else if upper_index == values.len() {
219 let last_original = &values[values.len() - 1].0;
221 check_point.push_str(&format!("{}>{}", col_name, last_original));
222 } else {
223 let lower_original = if lower_index < values.len() {
225 values[lower_index].0.to_string()
226 } else {
227 "unknown".to_string()
228 };
229
230 let upper_original = if upper_index < values.len() {
231 values[upper_index].0.to_string()
232 } else {
233 "unknown".to_string()
234 };
235
236 check_point.push_str(&format!(
237 "{}<{}<{}",
238 lower_original, col_name, upper_original
239 ));
240 }
241 }
242 } else {
243 check_point.push_str(&format!("{}:unknown", col_name));
245 }
246 }
247
248 check_point
249 }
250}
251
252struct MatrixGenerator {
254 matrix_foundation: HashMap<String, Vec<OrderedF64>>,
255 current_index: usize,
257 schema: Schema,
258 column_names: Vec<String>,
259 total_combinations: usize,
262 biased_suffix_product: Vec<usize>,
268}
269
270const MAX_BATCH_SIZE: usize = 8192;
271
272impl MatrixGenerator {
273 pub fn new(matrix_foundation: HashMap<&str, Vec<OrderedF64>>) -> Self {
274 let owned_matrix_foundation: HashMap<String, Vec<OrderedF64>> = matrix_foundation
276 .into_iter()
277 .map(|(k, v)| (k.to_string(), v))
278 .collect();
279
280 let mut fields = owned_matrix_foundation
281 .keys()
282 .map(|k| Field::new(k.clone(), DataType::Float64, false))
283 .collect::<Vec<_>>();
284 fields.sort_unstable();
285 let schema = Schema::new(fields.clone());
286
287 let column_names: Vec<String> = fields.iter().map(|field| field.name().clone()).collect();
289
290 let mut biased_suffix_product = Vec::with_capacity(column_names.len() + 1);
292 let mut product = 1;
293 biased_suffix_product.push(product);
294 for col_name in column_names.iter().rev() {
295 product *= owned_matrix_foundation[col_name].len();
296 biased_suffix_product.push(product);
297 }
298 biased_suffix_product.pop();
299 biased_suffix_product.reverse();
300
301 Self {
302 matrix_foundation: owned_matrix_foundation,
303 current_index: 0,
304 schema,
305 column_names,
306 total_combinations: product,
307 biased_suffix_product,
308 }
309 }
310
311 pub fn schema(&self) -> &Schema {
312 &self.schema
313 }
314
315 fn generate_batch(&self, start_index: usize, batch_size: usize) -> RecordBatch {
316 let actual_batch_size = batch_size.min(self.total_combinations - start_index);
317
318 let mut array_builders: Vec<Float64Builder> = Vec::with_capacity(self.column_names.len());
320 for _ in 0..self.column_names.len() {
321 array_builders.push(Float64Builder::with_capacity(actual_batch_size));
322 }
323
324 for combination_offset in 0..actual_batch_size {
326 let combination_index = start_index + combination_offset;
327
328 for (col_idx, col_name) in self.column_names.iter().enumerate() {
330 let values = &self.matrix_foundation[col_name];
331 let stride = self.biased_suffix_product[col_idx];
332 let value_index = (combination_index / stride) % values.len();
333 let value = *values[value_index].as_ref();
334
335 array_builders[col_idx].append_value(value);
336 }
337 }
338
339 let arrays: Vec<_> = array_builders
341 .into_iter()
342 .map(|mut builder| Arc::new(builder.finish()) as _)
343 .collect();
344
345 RecordBatch::try_new(Arc::new(self.schema.clone()), arrays)
346 .expect("Failed to create RecordBatch from generated arrays")
347 }
348}
349
350impl Iterator for MatrixGenerator {
351 type Item = RecordBatch;
352
353 fn next(&mut self) -> Option<Self::Item> {
354 if self.current_index >= self.total_combinations {
355 return None;
356 }
357
358 let remaining = self.total_combinations - self.current_index;
359 let batch_size = remaining.min(MAX_BATCH_SIZE);
360
361 let batch = self.generate_batch(self.current_index, batch_size);
362 self.current_index += batch_size;
363
364 Some(batch)
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use std::collections::HashMap;
371
372 use datatypes::value::Value;
373
374 use super::*;
375 use crate::expr::col;
376 use crate::multi_dim::MultiDimPartitionRule;
377
378 #[test]
379 fn test_hash_and_same_named_column_are_distinct() {
380 use crate::expr::Operand;
381 use crate::function::IcebergHash;
382
383 let hash = IcebergHash::new("host");
384 let result = MultiDimPartitionRule::try_new(
387 vec!["host".to_string(), hash.to_string()],
388 vec![1, 2],
389 vec![
390 Operand::IcebergHash(hash.clone()).lt(Value::Int32(0)),
391 col(hash.to_string()).gt_eq(Value::Int32(0)),
392 ],
393 true,
394 );
395 assert!(result.is_err());
396 }
397
398 #[test]
399 fn test_matrix_generator_single_column() {
400 let mut matrix_foundation = HashMap::new();
401 matrix_foundation.insert(
402 "col1",
403 vec![
404 OrderedF64::from(1.0),
405 OrderedF64::from(2.0),
406 OrderedF64::from(3.0),
407 ],
408 );
409
410 let mut generator = MatrixGenerator::new(matrix_foundation);
411 let batch = generator.next().unwrap();
412
413 assert_eq!(batch.num_rows(), 3);
414 assert_eq!(batch.num_columns(), 1);
415 assert_eq!(batch.schema().field(0).name(), "col1");
416
417 let col1_array = batch
418 .column(0)
419 .as_any()
420 .downcast_ref::<datatypes::arrow::array::Float64Array>()
421 .unwrap();
422 assert_eq!(col1_array.value(0), 1.0);
423 assert_eq!(col1_array.value(1), 2.0);
424 assert_eq!(col1_array.value(2), 3.0);
425
426 assert!(generator.next().is_none());
428 }
429
430 #[test]
431 fn test_matrix_generator_three_columns_cartesian_product() {
432 let mut matrix_foundation = HashMap::new();
433 matrix_foundation.insert("a", vec![OrderedF64::from(1.0), OrderedF64::from(2.0)]);
434 matrix_foundation.insert("b", vec![OrderedF64::from(10.0), OrderedF64::from(20.0)]);
435 matrix_foundation.insert(
436 "c",
437 vec![
438 OrderedF64::from(100.0),
439 OrderedF64::from(200.0),
440 OrderedF64::from(300.0),
441 ],
442 );
443
444 let mut generator = MatrixGenerator::new(matrix_foundation);
445 let batch = generator.next().unwrap();
446
447 assert_eq!(batch.num_rows(), 12);
449 assert_eq!(batch.num_columns(), 3);
450
451 let a_array = batch
452 .column(0)
453 .as_any()
454 .downcast_ref::<datatypes::arrow::array::Float64Array>()
455 .unwrap();
456 let b_array = batch
457 .column(1)
458 .as_any()
459 .downcast_ref::<datatypes::arrow::array::Float64Array>()
460 .unwrap();
461 let c_array = batch
462 .column(2)
463 .as_any()
464 .downcast_ref::<datatypes::arrow::array::Float64Array>()
465 .unwrap();
466
467 let expected = vec![
469 (1.0, 10.0, 100.0),
470 (1.0, 10.0, 200.0),
471 (1.0, 10.0, 300.0),
472 (1.0, 20.0, 100.0),
473 (1.0, 20.0, 200.0),
474 (1.0, 20.0, 300.0),
475 (2.0, 10.0, 100.0),
476 (2.0, 10.0, 200.0),
477 (2.0, 10.0, 300.0),
478 (2.0, 20.0, 100.0),
479 (2.0, 20.0, 200.0),
480 (2.0, 20.0, 300.0),
481 ];
482 #[allow(clippy::needless_range_loop)]
483 for i in 0..batch.num_rows() {
484 assert_eq!(
485 (a_array.value(i), b_array.value(i), c_array.value(i)),
486 expected[i]
487 );
488 }
489
490 assert!(generator.next().is_none());
492 }
493
494 #[test]
495 fn test_matrix_generator_iterator_small_batches() {
496 let mut matrix_foundation = HashMap::new();
497 matrix_foundation.insert("col1", vec![OrderedF64::from(1.0), OrderedF64::from(2.0)]);
498 matrix_foundation.insert(
499 "col2",
500 vec![
501 OrderedF64::from(10.0),
502 OrderedF64::from(20.0),
503 OrderedF64::from(30.0),
504 ],
505 );
506
507 let generator = MatrixGenerator::new(matrix_foundation);
508
509 assert_eq!(generator.total_combinations, 6);
511
512 let mut total_rows = 0;
513
514 for batch in generator {
515 total_rows += batch.num_rows();
516 assert_eq!(batch.num_columns(), 2);
517
518 assert!(batch.num_rows() > 0);
520 assert!(batch.num_rows() <= MAX_BATCH_SIZE);
521 }
522
523 assert_eq!(total_rows, 6);
524 }
525
526 #[test]
527 fn test_matrix_generator_empty_column_values() {
528 let mut matrix_foundation = HashMap::new();
529 matrix_foundation.insert("col1", vec![]);
530
531 let mut generator = MatrixGenerator::new(matrix_foundation);
532
533 assert_eq!(generator.total_combinations, 0);
535
536 assert!(generator.next().is_none());
538 }
539
540 #[test]
541 fn test_matrix_generator_large_dataset_batching() {
542 let mut matrix_foundation = HashMap::new();
545
546 let values1: Vec<OrderedF64> = (0..20).map(|i| OrderedF64::from(i as f64)).collect();
547 let values2: Vec<OrderedF64> = (0..20)
548 .map(|i| OrderedF64::from(i as f64 + 100.0))
549 .collect();
550 let values3: Vec<OrderedF64> = (0..21)
551 .map(|i| OrderedF64::from(i as f64 + 1000.0))
552 .collect();
553
554 matrix_foundation.insert("col1", values1);
555 matrix_foundation.insert("col2", values2);
556 matrix_foundation.insert("col3", values3);
557
558 let generator = MatrixGenerator::new(matrix_foundation);
559
560 assert_eq!(generator.total_combinations, 8400);
561
562 let mut total_rows = 0;
563 let mut batch_count = 0;
564 let mut first_batch_size = None;
565
566 for batch in generator {
567 batch_count += 1;
568 let batch_size = batch.num_rows();
569 total_rows += batch_size;
570
571 if first_batch_size.is_none() {
572 first_batch_size = Some(batch_size);
573 }
574
575 assert!(batch_size <= MAX_BATCH_SIZE);
577 assert_eq!(batch.num_columns(), 3);
578 }
579
580 assert_eq!(total_rows, 8400);
581 assert!(batch_count > 1);
582 assert_eq!(first_batch_size.unwrap(), MAX_BATCH_SIZE);
583 }
584
585 #[test]
586 fn test_remap_checkpoint_values() {
587 let rule = MultiDimPartitionRule::try_new(
589 vec!["host".to_string(), "value".to_string()],
590 vec![1, 2, 3],
591 vec![
592 col("host")
593 .lt(Value::Int64(0))
594 .and(col("value").lt(Value::Int64(0))),
595 col("host")
596 .lt(Value::Int64(0))
597 .and(col("value").gt_eq(Value::Int64(0))),
598 col("host")
599 .gt_eq(Value::Int64(0))
600 .and(col("host").lt(Value::Int64(1)))
601 .and(col("value").lt(Value::Int64(1))),
602 col("host")
603 .gt_eq(Value::Int64(0))
604 .and(col("host").lt(Value::Int64(1)))
605 .and(col("value").gt_eq(Value::Int64(1))),
606 col("host")
607 .gt_eq(Value::Int64(1))
608 .and(col("host").lt(Value::Int64(2)))
609 .and(col("value").lt(Value::Int64(2))),
610 col("host")
611 .gt_eq(Value::Int64(1))
612 .and(col("host").lt(Value::Int64(2)))
613 .and(col("value").gt_eq(Value::Int64(2))),
614 col("host")
615 .gt_eq(Value::Int64(2))
616 .and(col("host").lt(Value::Int64(3)))
617 .and(col("value").lt(Value::Int64(3))),
618 col("host")
619 .gt_eq(Value::Int64(2))
620 .and(col("host").lt(Value::Int64(3)))
621 .and(col("value").gt_eq(Value::Int64(3))),
622 col("host").gt_eq(Value::Int64(3)),
623 ],
624 true,
625 )
626 .unwrap();
627 let checker = PartitionChecker::try_new(&rule).unwrap();
628
629 let schema = Arc::new(Schema::new(vec![
630 Field::new("0", DataType::Float64, false),
631 Field::new("1", DataType::Float64, false),
632 ]));
633 let host_array = Float64Array::from(vec![-0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5]);
634 let value_array = Float64Array::from(vec![-0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5]);
635 let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)])
636 .unwrap();
637
638 let checkpoint = checker.remap_checkpoint(0, &batch);
639 assert_eq!(checkpoint, "host<0, value<0");
640 let checkpoint = checker.remap_checkpoint(1, &batch);
641 assert_eq!(checkpoint, "host=0, value=0");
642 let checkpoint = checker.remap_checkpoint(6, &batch);
643 assert_eq!(checkpoint, "2<host<3, 2<value<3");
644 let checkpoint = checker.remap_checkpoint(7, &batch);
645 assert_eq!(checkpoint, "host=3, value=3");
646 let checkpoint = checker.remap_checkpoint(8, &batch);
647 assert_eq!(checkpoint, "host>3, value>3");
648 }
649}