1use std::any::Any;
16use std::cmp::Ordering;
17use std::collections::HashMap;
18use std::collections::hash_map::Entry;
19use std::sync::{Arc, RwLock};
20
21use datafusion_expr::ColumnarValue;
22use datafusion_physical_expr::PhysicalExpr;
23use datatypes::arrow;
24use datatypes::arrow::array::{Array, BooleanArray, BooleanBufferBuilder, RecordBatch};
25use datatypes::arrow::buffer::BooleanBuffer;
26use datatypes::arrow::compute::prep_null_mask_filter;
27use datatypes::arrow::datatypes::Schema;
28use datatypes::prelude::Value;
29use datatypes::vectors::{Helper, VectorRef};
30use serde::{Deserialize, Serialize};
31use snafu::{OptionExt, ResultExt, ensure};
32use store_api::storage::RegionNumber;
33
34use crate::PartitionRule;
35use crate::checker::PartitionChecker;
36use crate::error::{self, Result, UndefinedColumnSnafu};
37use crate::expr::{Operand, PartitionExpr, RestrictedOp};
38use crate::partition::RegionMask;
39
40const DEFAULT_REGION: RegionNumber = 0;
42
43type PhysicalExprCache = Option<(Vec<Arc<dyn PhysicalExpr>>, Arc<Schema>)>;
44
45#[derive(Debug, Serialize, Deserialize)]
51pub struct MultiDimPartitionRule {
52 partition_columns: Vec<String>,
54 name_to_index: HashMap<String, usize>,
56 regions: Vec<RegionNumber>,
59 exprs: Vec<PartitionExpr>,
61 #[serde(skip)]
63 physical_expr_cache: RwLock<PhysicalExprCache>,
64}
65
66impl MultiDimPartitionRule {
67 pub fn try_new(
72 partition_columns: Vec<String>,
73 regions: Vec<RegionNumber>,
74 exprs: Vec<PartitionExpr>,
75 check_exprs: bool,
76 ) -> Result<Self> {
77 let name_to_index = partition_columns
78 .iter()
79 .enumerate()
80 .map(|(i, name)| (name.clone(), i))
81 .collect::<HashMap<_, _>>();
82
83 let rule = Self {
84 partition_columns,
85 name_to_index,
86 regions,
87 exprs,
88 physical_expr_cache: RwLock::new(None),
89 };
90
91 if check_exprs {
92 let checker = PartitionChecker::try_new(&rule)?;
93 checker.check()?;
94 }
95
96 Ok(rule)
97 }
98
99 pub fn exprs(&self) -> &[PartitionExpr] {
100 &self.exprs
101 }
102
103 fn find_region(&self, values: &[Value]) -> Result<RegionNumber> {
104 ensure!(
105 values.len() == self.partition_columns.len(),
106 error::RegionKeysSizeSnafu {
107 expect: self.partition_columns.len(),
108 actual: values.len(),
109 }
110 );
111
112 for (region_index, expr) in self.exprs.iter().enumerate() {
113 if self.evaluate_expr(expr, values)? {
114 return Ok(self.regions[region_index]);
115 }
116 }
117
118 Ok(DEFAULT_REGION)
120 }
121
122 fn evaluate_expr(&self, expr: &PartitionExpr, values: &[Value]) -> Result<bool> {
123 match (expr.lhs.as_ref(), expr.rhs.as_ref()) {
124 (Operand::Column(name), Operand::Value(r)) => {
125 let index = self
126 .name_to_index
127 .get(name)
128 .context(UndefinedColumnSnafu { column: name })?;
129 let l = &values[*index];
130 Self::perform_op(l, &expr.op, r)
131 }
132 (Operand::Value(l), Operand::Column(name)) => {
133 let index = self
134 .name_to_index
135 .get(name)
136 .context(UndefinedColumnSnafu { column: name })?;
137 let r = &values[*index];
138 Self::perform_op(l, &expr.op, r)
139 }
140 (Operand::Expr(lhs), Operand::Expr(rhs)) => {
141 let lhs = self.evaluate_expr(lhs, values)?;
142 let rhs = self.evaluate_expr(rhs, values)?;
143 match expr.op {
144 RestrictedOp::And => Ok(lhs && rhs),
145 RestrictedOp::Or => Ok(lhs || rhs),
146 _ => unreachable!(),
147 }
148 }
149 _ => unreachable!(),
150 }
151 }
152
153 fn perform_op(lhs: &Value, op: &RestrictedOp, rhs: &Value) -> Result<bool> {
154 let result = match op {
155 RestrictedOp::Eq => lhs.eq(rhs),
156 RestrictedOp::NotEq => lhs.ne(rhs),
157 RestrictedOp::Lt => lhs.partial_cmp(rhs) == Some(Ordering::Less),
158 RestrictedOp::LtEq => {
159 let result = lhs.partial_cmp(rhs);
160 result == Some(Ordering::Less) || result == Some(Ordering::Equal)
161 }
162 RestrictedOp::Gt => lhs.partial_cmp(rhs) == Some(Ordering::Greater),
163 RestrictedOp::GtEq => {
164 let result = lhs.partial_cmp(rhs);
165 result == Some(Ordering::Greater) || result == Some(Ordering::Equal)
166 }
167 RestrictedOp::And | RestrictedOp::Or => unreachable!(),
168 };
169
170 Ok(result)
171 }
172
173 pub fn row_at(&self, cols: &[VectorRef], index: usize, row: &mut [Value]) -> Result<()> {
174 for (col_idx, col) in cols.iter().enumerate() {
175 row[col_idx] = col.get(index);
176 }
177 Ok(())
178 }
179
180 pub fn record_batch_to_cols(&self, record_batch: &RecordBatch) -> Result<Vec<VectorRef>> {
181 self.partition_columns
182 .iter()
183 .map(|col_name| {
184 record_batch
185 .column_by_name(col_name)
186 .context(UndefinedColumnSnafu { column: col_name })
187 .and_then(|array| {
188 Helper::try_into_vector(array).context(error::ConvertToVectorSnafu)
189 })
190 })
191 .collect::<Result<Vec<_>>>()
192 }
193
194 pub fn split_record_batch_naive(
195 &self,
196 record_batch: &RecordBatch,
197 ) -> Result<HashMap<RegionNumber, BooleanArray>> {
198 let num_rows = record_batch.num_rows();
199
200 let mut result = self
201 .regions
202 .iter()
203 .map(|region| {
204 let mut builder = BooleanBufferBuilder::new(num_rows);
205 builder.append_n(num_rows, false);
206 (*region, builder)
207 })
208 .collect::<HashMap<_, _>>();
209
210 let cols = self.record_batch_to_cols(record_batch)?;
211 let mut current_row = vec![Value::Null; self.partition_columns.len()];
212 for row_idx in 0..num_rows {
213 self.row_at(&cols, row_idx, &mut current_row)?;
214 let current_region = self.find_region(¤t_row)?;
215 let region_mask = result
216 .get_mut(¤t_region)
217 .unwrap_or_else(|| panic!("Region {} must be initialized", current_region));
218 region_mask.set_bit(row_idx, true);
219 }
220
221 Ok(result
222 .into_iter()
223 .map(|(region, mut mask)| (region, BooleanArray::new(mask.finish(), None)))
224 .collect())
225 }
226
227 pub fn split_record_batch(
228 &self,
229 record_batch: &RecordBatch,
230 ) -> Result<HashMap<RegionNumber, RegionMask>> {
231 let num_rows = record_batch.num_rows();
232 if self.regions.len() == 1 {
233 return Ok([(
234 self.regions[0],
235 RegionMask::from(BooleanArray::from(vec![true; num_rows])),
236 )]
237 .into_iter()
238 .collect());
239 }
240 let physical_exprs = {
241 let cache_read_guard = self.physical_expr_cache.read().unwrap();
242 if let Some((cached_exprs, schema)) = cache_read_guard.as_ref()
243 && schema == record_batch.schema_ref()
244 {
245 cached_exprs.clone()
246 } else {
247 drop(cache_read_guard); let schema = record_batch.schema();
250 let new_cache = self
251 .exprs
252 .iter()
253 .map(|e| e.try_as_physical_expr(&schema))
254 .collect::<Result<Vec<_>>>()?;
255
256 let mut cache_write_guard = self.physical_expr_cache.write().unwrap();
257 cache_write_guard.replace((new_cache.clone(), schema));
258 new_cache
259 }
260 };
261
262 let mut result: HashMap<u32, RegionMask> = physical_exprs
263 .iter()
264 .zip(self.regions.iter())
265 .filter_map(|(expr, region_num)| {
266 let col_val = match expr
267 .evaluate(record_batch)
268 .context(error::EvaluateRecordBatchSnafu)
269 {
270 Ok(array) => array,
271 Err(e) => {
272 return Some(Err(e));
273 }
274 };
275 let array = match columnar_value_to_boolean_array(col_val, num_rows) {
276 Ok(array) if array.null_count() > 0 => prep_null_mask_filter(&array),
279 Ok(array) => array,
280 Err(e) => {
281 return Some(Err(e));
282 }
283 };
284 let selected_rows = array.true_count();
285 if selected_rows == 0 {
286 return None;
288 }
289 Some(Ok((*region_num, RegionMask::new(array, selected_rows))))
290 })
291 .collect::<error::Result<_>>()?;
292
293 let selected = if result.len() == 1 {
294 result.values().next().unwrap().array().clone()
295 } else {
296 let mut selected = BooleanArray::new(BooleanBuffer::new_unset(num_rows), None);
297 for region_mask in result.values() {
298 selected = arrow::compute::kernels::boolean::or(&selected, region_mask.array())
299 .context(error::ComputeArrowKernelSnafu)?;
300 }
301 selected
302 };
303
304 if selected.true_count() == num_rows {
306 return Ok(result);
307 }
308
309 let unselected = arrow::compute::kernels::boolean::not(&selected)
311 .context(error::ComputeArrowKernelSnafu)?;
312 match result.entry(DEFAULT_REGION) {
313 Entry::Occupied(mut o) => {
314 let default_region_mask = RegionMask::from(
316 arrow::compute::kernels::boolean::or(o.get().array(), &unselected)
317 .context(error::ComputeArrowKernelSnafu)?,
318 );
319 o.insert(default_region_mask);
320 }
321 Entry::Vacant(v) => {
322 v.insert(RegionMask::from(unselected));
324 }
325 }
326 Ok(result)
327 }
328}
329
330fn columnar_value_to_boolean_array(
331 col_val: ColumnarValue,
332 num_rows: usize,
333) -> Result<BooleanArray> {
334 let column = col_val
335 .into_array(num_rows)
336 .context(error::EvaluateRecordBatchSnafu)?;
337 let array = column
338 .as_any()
339 .downcast_ref::<BooleanArray>()
340 .with_context(|| error::UnexpectedColumnTypeSnafu {
341 data_type: column.data_type().clone(),
342 })?;
343 Ok(array.clone())
344}
345
346impl PartitionRule for MultiDimPartitionRule {
347 fn as_any(&self) -> &dyn Any {
348 self
349 }
350
351 fn partition_columns(&self) -> &[String] {
352 &self.partition_columns
353 }
354
355 fn find_region(&self, values: &[Value]) -> Result<RegionNumber> {
356 self.find_region(values)
357 }
358
359 fn split_record_batch(
360 &self,
361 record_batch: &RecordBatch,
362 ) -> Result<HashMap<RegionNumber, RegionMask>> {
363 self.split_record_batch(record_batch)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use std::assert_matches;
370
371 use super::*;
372 use crate::error::{self, Error};
373 use crate::expr::col;
374
375 #[test]
376 fn test_find_region() {
377 let rule = MultiDimPartitionRule::try_new(
383 vec!["b".to_string()],
384 vec![1, 2, 3],
385 vec![
386 PartitionExpr::new(
387 Operand::Column("b".to_string()),
388 RestrictedOp::Lt,
389 Operand::Value(datatypes::value::Value::String("hz".into())),
390 ),
391 PartitionExpr::new(
392 Operand::Expr(PartitionExpr::new(
393 Operand::Column("b".to_string()),
394 RestrictedOp::GtEq,
395 Operand::Value(datatypes::value::Value::String("hz".into())),
396 )),
397 RestrictedOp::And,
398 Operand::Expr(PartitionExpr::new(
399 Operand::Column("b".to_string()),
400 RestrictedOp::Lt,
401 Operand::Value(datatypes::value::Value::String("sh".into())),
402 )),
403 ),
404 PartitionExpr::new(
405 Operand::Column("b".to_string()),
406 RestrictedOp::GtEq,
407 Operand::Value(datatypes::value::Value::String("sh".into())),
408 ),
409 ],
410 true,
411 )
412 .unwrap();
413 assert_matches!(
414 rule.find_region(&["foo".into(), 1000_i32.into()]),
415 Err(error::Error::RegionKeysSize {
416 expect: 1,
417 actual: 2,
418 ..
419 })
420 );
421 assert_matches!(rule.find_region(&["foo".into()]), Ok(1));
422 assert_matches!(rule.find_region(&["bar".into()]), Ok(1));
423 assert_matches!(rule.find_region(&["hz".into()]), Ok(2));
424 assert_matches!(rule.find_region(&["hzz".into()]), Ok(2));
425 assert_matches!(rule.find_region(&["sh".into()]), Ok(3));
426 assert_matches!(rule.find_region(&["zzzz".into()]), Ok(3));
427 }
428
429 #[test]
430 fn test_find_region_rejects_undeclared_column_on_lhs() {
431 let rule = MultiDimPartitionRule::try_new(
432 vec!["host".to_string()],
433 vec![1],
434 vec![PartitionExpr::new(
435 Operand::Column("rack".to_string()),
436 RestrictedOp::Lt,
437 Operand::Value(Value::String("n".into())),
438 )],
439 false,
440 )
441 .unwrap();
442
443 assert_matches!(
444 rule.find_region(&[Value::String("z".into())]),
445 Err(Error::UndefinedColumn { column, .. }) if column == "rack"
446 );
447 }
448
449 #[test]
450 fn test_find_region_rejects_undeclared_column_on_rhs() {
451 let rule = MultiDimPartitionRule::try_new(
452 vec!["host".to_string()],
453 vec![1],
454 vec![PartitionExpr::new(
455 Operand::Value(Value::String("n".into())),
456 RestrictedOp::Gt,
457 Operand::Column("rack".to_string()),
458 )],
459 false,
460 )
461 .unwrap();
462
463 assert_matches!(
464 rule.find_region(&[Value::String("z".into())]),
465 Err(Error::UndefinedColumn { column, .. }) if column == "rack"
466 );
467 }
468
469 #[test]
470 fn invalid_expr_case_1() {
471 let rule = MultiDimPartitionRule::try_new(
475 vec!["a".to_string(), "b".to_string()],
476 vec![1],
477 vec![PartitionExpr::new(
478 Operand::Column("b".to_string()),
479 RestrictedOp::LtEq,
480 Operand::Expr(PartitionExpr::new(
481 Operand::Expr(PartitionExpr::new(
482 Operand::Column("b".to_string()),
483 RestrictedOp::GtEq,
484 Operand::Value(datatypes::value::Value::String("hz".into())),
485 )),
486 RestrictedOp::And,
487 Operand::Expr(PartitionExpr::new(
488 Operand::Column("b".to_string()),
489 RestrictedOp::Lt,
490 Operand::Value(datatypes::value::Value::String("sh".into())),
491 )),
492 )),
493 )],
494 true,
495 );
496
497 assert_matches!(rule.unwrap_err(), Error::InvalidExpr { .. });
499 }
500
501 #[test]
502 fn invalid_expr_case_2() {
503 let rule = MultiDimPartitionRule::try_new(
507 vec!["a".to_string(), "b".to_string()],
508 vec![1],
509 vec![PartitionExpr::new(
510 Operand::Expr(PartitionExpr::new(
511 Operand::Column("b".to_string()),
512 RestrictedOp::GtEq,
513 Operand::Value(datatypes::value::Value::String("hz".into())),
514 )),
515 RestrictedOp::And,
516 Operand::Value(datatypes::value::Value::String("sh".into())),
517 )],
518 true,
519 );
520
521 assert_matches!(rule.unwrap_err(), Error::InvalidExpr { .. });
523 }
524
525 #[test]
534 fn empty_expr_case_1() {
535 let rule = MultiDimPartitionRule::try_new(
540 vec!["a".to_string(), "b".to_string()],
541 vec![1, 2],
542 vec![
543 PartitionExpr::new(
544 Operand::Column("b".to_string()),
545 RestrictedOp::LtEq,
546 Operand::Value(datatypes::value::Value::String("h".into())),
547 ),
548 PartitionExpr::new(
549 Operand::Column("b".to_string()),
550 RestrictedOp::GtEq,
551 Operand::Value(datatypes::value::Value::String("s".into())),
552 ),
553 ],
554 true,
555 );
556
557 assert_matches!(rule.unwrap_err(), Error::CheckpointNotCovered { .. });
559 }
560
561 #[test]
576 fn empty_expr_case_2() {
577 let rule = MultiDimPartitionRule::try_new(
582 vec!["a".to_string(), "b".to_string()],
583 vec![1, 2],
584 vec![
585 PartitionExpr::new(
586 Operand::Expr(PartitionExpr::new(
587 Operand::Expr(PartitionExpr::new(
588 Operand::Expr(PartitionExpr::new(
590 Operand::Expr(PartitionExpr::new(
591 Operand::Column("a".to_string()),
592 RestrictedOp::GtEq,
593 Operand::Value(datatypes::value::Value::Int64(100)),
594 )),
595 RestrictedOp::And,
596 Operand::Expr(PartitionExpr::new(
597 Operand::Column("b".to_string()),
598 RestrictedOp::LtEq,
599 Operand::Value(datatypes::value::Value::Int64(10)),
600 )),
601 )),
602 RestrictedOp::Or,
603 Operand::Expr(PartitionExpr::new(
605 Operand::Expr(PartitionExpr::new(
606 Operand::Expr(PartitionExpr::new(
607 Operand::Column("a".to_string()),
608 RestrictedOp::Gt,
609 Operand::Value(datatypes::value::Value::Int64(100)),
610 )),
611 RestrictedOp::And,
612 Operand::Expr(PartitionExpr::new(
613 Operand::Column("a".to_string()),
614 RestrictedOp::LtEq,
615 Operand::Value(datatypes::value::Value::Int64(200)),
616 )),
617 )),
618 RestrictedOp::And,
619 Operand::Expr(PartitionExpr::new(
620 Operand::Column("b".to_string()),
621 RestrictedOp::LtEq,
622 Operand::Value(datatypes::value::Value::Int64(10)),
623 )),
624 )),
625 )),
626 RestrictedOp::Or,
627 Operand::Expr(PartitionExpr::new(
629 Operand::Expr(PartitionExpr::new(
630 Operand::Expr(PartitionExpr::new(
631 Operand::Column("a".to_string()),
632 RestrictedOp::GtEq,
633 Operand::Value(datatypes::value::Value::Int64(200)),
634 )),
635 RestrictedOp::And,
636 Operand::Expr(PartitionExpr::new(
637 Operand::Column("b".to_string()),
638 RestrictedOp::Gt,
639 Operand::Value(datatypes::value::Value::Int64(10)),
640 )),
641 )),
642 RestrictedOp::And,
643 Operand::Expr(PartitionExpr::new(
644 Operand::Column("b".to_string()),
645 RestrictedOp::LtEq,
646 Operand::Value(datatypes::value::Value::Int64(20)),
647 )),
648 )),
649 )),
650 RestrictedOp::Or,
651 Operand::Expr(PartitionExpr::new(
653 Operand::Expr(PartitionExpr::new(
654 Operand::Column("a".to_string()),
655 RestrictedOp::Gt,
656 Operand::Value(datatypes::value::Value::Int64(200)),
657 )),
658 RestrictedOp::And,
659 Operand::Expr(PartitionExpr::new(
660 Operand::Column("b".to_string()),
661 RestrictedOp::LtEq,
662 Operand::Value(datatypes::value::Value::Int64(20)),
663 )),
664 )),
665 ),
666 PartitionExpr::new(
667 Operand::Expr(PartitionExpr::new(
669 Operand::Expr(PartitionExpr::new(
670 Operand::Column("a".to_string()),
671 RestrictedOp::Lt,
672 Operand::Value(datatypes::value::Value::Int64(100)),
673 )),
674 RestrictedOp::And,
675 Operand::Expr(PartitionExpr::new(
676 Operand::Column("b".to_string()),
677 RestrictedOp::LtEq,
678 Operand::Value(datatypes::value::Value::Int64(20)),
679 )),
680 )),
681 RestrictedOp::Or,
682 Operand::Expr(PartitionExpr::new(
684 Operand::Expr(PartitionExpr::new(
685 Operand::Column("a".to_string()),
686 RestrictedOp::GtEq,
687 Operand::Value(datatypes::value::Value::Int64(100)),
688 )),
689 RestrictedOp::And,
690 Operand::Expr(PartitionExpr::new(
691 Operand::Column("b".to_string()),
692 RestrictedOp::GtEq,
693 Operand::Value(datatypes::value::Value::Int64(20)),
694 )),
695 )),
696 ),
697 ],
698 true,
699 );
700
701 assert_matches!(rule.unwrap_err(), Error::CheckpointNotCovered { .. });
703 }
704
705 #[test]
706 fn duplicate_expr_case_1() {
707 let rule = MultiDimPartitionRule::try_new(
712 vec!["a".to_string(), "b".to_string()],
713 vec![1, 2],
714 vec![
715 PartitionExpr::new(
716 Operand::Column("a".to_string()),
717 RestrictedOp::LtEq,
718 Operand::Value(datatypes::value::Value::Int64(20)),
719 ),
720 PartitionExpr::new(
721 Operand::Column("a".to_string()),
722 RestrictedOp::GtEq,
723 Operand::Value(datatypes::value::Value::Int64(10)),
724 ),
725 ],
726 true,
727 );
728
729 assert_matches!(rule.unwrap_err(), Error::CheckpointOverlapped { .. });
731 }
732
733 #[test]
734 fn duplicate_expr_case_2() {
735 let rule = MultiDimPartitionRule::try_new(
741 vec!["a".to_string(), "b".to_string()],
742 vec![1, 2],
743 vec![
744 PartitionExpr::new(
745 Operand::Column("a".to_string()),
746 RestrictedOp::NotEq,
747 Operand::Value(datatypes::value::Value::Int64(20)),
748 ),
749 PartitionExpr::new(
750 Operand::Column("a".to_string()),
751 RestrictedOp::LtEq,
752 Operand::Value(datatypes::value::Value::Int64(20)),
753 ),
754 PartitionExpr::new(
755 Operand::Column("a".to_string()),
756 RestrictedOp::Gt,
757 Operand::Value(datatypes::value::Value::Int64(20)),
758 ),
759 ],
760 true,
761 );
762
763 assert_matches!(rule.unwrap_err(), Error::CheckpointOverlapped { .. });
765 }
766
767 #[test]
778 fn test_partial_divided() {
779 let _rule = MultiDimPartitionRule::try_new(
780 vec!["host".to_string(), "value".to_string()],
781 vec![0, 1, 2, 3],
782 vec![
783 col("host")
784 .lt(Value::String("server10".into()))
785 .and(col("value").lt(Value::Int64(10))),
786 col("host")
787 .lt(Value::String("server10".into()))
788 .and(col("value").gt_eq(Value::Int64(10))),
789 col("host").gt_eq(Value::String("server10".into())),
790 ],
791 true,
792 )
793 .unwrap();
794 }
795}
796
797#[cfg(test)]
798mod test_split_record_batch {
799 use std::sync::Arc;
800
801 use common_time::timestamp::Timestamp;
802 use datafusion_common::ScalarValue;
803 use datatypes::arrow::array::{Int64Array, StringArray, TimestampNanosecondArray};
804 use datatypes::arrow::datatypes::{DataType, Field, Schema, TimeUnit};
805 use datatypes::arrow::record_batch::RecordBatch;
806 use rand::Rng;
807
808 use super::*;
809 use crate::expr::{Operand, col};
810
811 fn test_schema() -> Arc<Schema> {
812 Arc::new(Schema::new(vec![
813 Field::new("host", DataType::Utf8, false),
814 Field::new("value", DataType::Int64, false),
815 ]))
816 }
817
818 fn generate_random_record_batch(num_rows: usize) -> RecordBatch {
819 let schema = test_schema();
820 let mut rng = rand::thread_rng();
821 let mut host_array = Vec::with_capacity(num_rows);
822 let mut value_array = Vec::with_capacity(num_rows);
823 for _ in 0..num_rows {
824 host_array.push(format!("server{}", rng.gen_range(0..20)));
825 value_array.push(rng.gen_range(0..20));
826 }
827 let host_array = StringArray::from(host_array);
828 let value_array = Int64Array::from(value_array);
829 RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)]).unwrap()
830 }
831
832 #[test]
833 fn test_split_record_batch_by_one_column() {
834 let rule = MultiDimPartitionRule::try_new(
836 vec!["host".to_string(), "value".to_string()],
837 vec![0, 1],
838 vec![
839 col("host").lt(Value::String("server1".into())),
840 col("host").gt_eq(Value::String("server1".into())),
841 ],
842 true,
843 )
844 .unwrap();
845
846 let batch = generate_random_record_batch(1000);
847 let result = rule.split_record_batch(&batch).unwrap();
849 let expected = rule.split_record_batch_naive(&batch).unwrap();
850 assert_eq!(result.len(), expected.len());
851 for (region, value) in &result {
852 assert_eq!(
853 value.array(),
854 expected.get(region).unwrap(),
855 "failed on region: {}",
856 region
857 );
858 }
859 }
860
861 #[test]
862 fn test_split_record_batch_widened_time_index_unit() {
863 let bound = Value::from(Timestamp::new_millisecond(1_000));
868 let rule = MultiDimPartitionRule::try_new(
869 vec!["ts".to_string()],
870 vec![0, 1],
871 vec![col("ts").lt(bound.clone()), col("ts").gt_eq(bound)],
872 true,
873 )
874 .unwrap();
875
876 let schema = Arc::new(Schema::new(vec![Field::new(
877 "ts",
878 DataType::Timestamp(TimeUnit::Nanosecond, None),
879 false,
880 )]));
881 let batch = RecordBatch::try_new(
882 schema,
883 vec![Arc::new(TimestampNanosecondArray::from(vec![
884 500_000_000,
885 2_000_000_000,
886 ]))],
887 )
888 .unwrap();
889
890 let expected = rule.split_record_batch_naive(&batch).unwrap();
892 assert_eq!(expected.len(), 2);
893 let result = rule.split_record_batch(&batch).unwrap();
894 assert_eq!(result.len(), expected.len());
895 for (region, value) in &result {
896 assert_eq!(value.array(), expected.get(region).unwrap());
897 }
898 }
899
900 #[test]
901 fn test_split_record_batch_empty() {
902 let rule = MultiDimPartitionRule::try_new(
904 vec!["host".to_string()],
905 vec![1],
906 vec![
907 col("host").lt(Value::String("server1".into())),
908 col("host").gt_eq(Value::String("server1".into())),
909 ],
910 true,
911 )
912 .unwrap();
913
914 let schema = test_schema();
915 let host_array = StringArray::from(Vec::<&str>::new());
916 let value_array = Int64Array::from(Vec::<i64>::new());
917 let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)])
918 .unwrap();
919
920 let result = rule.split_record_batch(&batch).unwrap();
921 assert_eq!(result.len(), 1);
922 }
923
924 #[test]
925 fn test_split_record_batch_by_two_columns() {
926 let rule = MultiDimPartitionRule::try_new(
927 vec!["host".to_string(), "value".to_string()],
928 vec![0, 1, 2, 3],
929 vec![
930 col("host")
931 .lt(Value::String("server10".into()))
932 .and(col("value").lt(Value::Int64(10))),
933 col("host")
934 .lt(Value::String("server10".into()))
935 .and(col("value").gt_eq(Value::Int64(10))),
936 col("host")
937 .gt_eq(Value::String("server10".into()))
938 .and(col("value").lt(Value::Int64(10))),
939 col("host")
940 .gt_eq(Value::String("server10".into()))
941 .and(col("value").gt_eq(Value::Int64(10))),
942 ],
943 true,
944 )
945 .unwrap();
946
947 let batch = generate_random_record_batch(1000);
948 let result = rule.split_record_batch(&batch).unwrap();
949 let expected = rule.split_record_batch_naive(&batch).unwrap();
950 assert_eq!(result.len(), expected.len());
951 for (region, value) in &result {
952 assert_eq!(value.array(), expected.get(region).unwrap());
953 }
954 }
955
956 #[test]
957 fn test_all_rows_selected() {
958 let rule = MultiDimPartitionRule::try_new(
960 vec!["value".to_string()],
961 vec![1, 2],
962 vec![
963 col("value").lt(Value::Int64(30)),
964 col("value").gt_eq(Value::Int64(30)),
965 ],
966 true,
967 )
968 .unwrap();
969
970 let schema = test_schema();
971 let host_array = StringArray::from(vec!["server1", "server2", "server3", "server4"]);
972 let value_array = Int64Array::from(vec![10, 20, 30, 40]);
973 let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)])
974 .unwrap();
975
976 let result = rule.split_record_batch(&batch).unwrap();
977
978 assert_eq!(result.len(), 2);
980 assert!(result.contains_key(&1));
981 assert!(result.contains_key(&2));
982
983 assert_eq!(result.get(&1).unwrap().selected_rows(), 2); assert_eq!(result.get(&2).unwrap().selected_rows(), 2); }
987
988 #[test]
989 fn test_split_record_batch_with_scalar_predicate() {
990 let rule = MultiDimPartitionRule::try_new(
992 vec!["host".to_string()],
993 vec![0, 1],
994 vec![
995 PartitionExpr::new(
996 Operand::Column("host".to_string()),
997 RestrictedOp::Lt,
998 Operand::Value(Value::String("never_happen_1".into())),
999 ),
1000 PartitionExpr::new(
1001 Operand::Expr(PartitionExpr::new(
1002 Operand::Column("host".to_string()),
1003 RestrictedOp::GtEq,
1004 Operand::Value(Value::String("never_happen_1".into())),
1005 )),
1006 RestrictedOp::And,
1007 Operand::Value(Value::Boolean(false)),
1008 ),
1009 ],
1010 false,
1011 )
1012 .unwrap();
1013
1014 let batch = generate_random_record_batch(8);
1015 let result = rule.split_record_batch(&batch).unwrap();
1016
1017 assert_eq!(result.len(), 1);
1018 assert!(result.contains_key(&0));
1019
1020 let total_rows = result.get(&0).unwrap().selected_rows();
1021 assert_eq!(total_rows, batch.num_rows());
1022 }
1023
1024 #[test]
1025 fn test_comparison_batch_matches_row() {
1026 use datatypes::arrow::array::Array;
1027 use datatypes::arrow::datatypes::{DataType, Field};
1028
1029 let inputs = [None, Some(4), Some(5), Some(6)];
1030 let batch = RecordBatch::try_new(
1031 Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, true)])),
1032 vec![Arc::new(Int64Array::from(inputs.to_vec()))],
1033 )
1034 .unwrap();
1035 let mut mismatches = vec![];
1036 for op in [
1037 RestrictedOp::Eq,
1038 RestrictedOp::NotEq,
1039 RestrictedOp::Lt,
1040 RestrictedOp::LtEq,
1041 RestrictedOp::Gt,
1042 RestrictedOp::GtEq,
1043 ] {
1044 for bound in [Value::Null, Value::Int64(5)] {
1045 for expr in [
1048 PartitionExpr::new(col("a"), op.clone(), bound.clone().into()),
1049 PartitionExpr {
1050 lhs: Box::new(bound.clone().into()),
1051 op: op.clone(),
1052 rhs: Box::new(col("a")),
1053 },
1054 ] {
1055 let rule = MultiDimPartitionRule::try_new(
1056 vec!["a".to_string()],
1057 vec![1],
1058 vec![expr.clone()],
1059 false,
1060 )
1061 .unwrap();
1062 let by_batch = match expr
1063 .try_as_physical_expr(batch.schema_ref())
1064 .unwrap()
1065 .evaluate(&batch)
1066 {
1067 Ok(by_batch) => {
1068 columnar_value_to_boolean_array(by_batch, batch.num_rows()).unwrap()
1069 }
1070 Err(err) => {
1071 mismatches.push(format!("{expr}: batch error {err}"));
1072 continue;
1073 }
1074 };
1075 for (row, input) in inputs.iter().enumerate() {
1076 let value = input.map_or(Value::Null, Value::Int64);
1077 let by_row = rule.evaluate_expr(&expr, &[value]).unwrap();
1078 let by_batch = by_batch.is_valid(row) && by_batch.value(row);
1079 if by_row != by_batch {
1080 mismatches.push(format!(
1081 "{expr} with a = {input:?}: row {by_row}, batch {by_batch}"
1082 ));
1083 }
1084 }
1085 }
1086 }
1087 }
1088 assert!(mismatches.is_empty(), "{mismatches:#?}");
1089 }
1090
1091 #[test]
1092 fn test_split_null_rows_match_row_routing() {
1093 use datatypes::arrow::datatypes::{DataType, Field};
1094
1095 let rule = MultiDimPartitionRule::try_new(
1096 vec!["host".to_string()],
1097 vec![0, 1],
1098 vec![
1099 col("host").not_eq(Value::from("a")),
1100 col("host").eq(Value::from("a")),
1101 ],
1102 true,
1103 )
1104 .unwrap();
1105 for hosts in [vec![Some("a"), None, Some("z")], vec![Some("a"), None]] {
1106 let batch = RecordBatch::try_new(
1107 Arc::new(Schema::new(vec![Field::new("host", DataType::Utf8, true)])),
1108 vec![Arc::new(StringArray::from(hosts.clone()))],
1109 )
1110 .unwrap();
1111 let by_row = rule.split_record_batch_naive(&batch).unwrap();
1112 let by_batch = rule.split_record_batch(&batch).unwrap();
1113 for (region, expected) in by_row {
1114 let actual = by_batch
1115 .get(®ion)
1116 .map(|mask| mask.array().clone())
1117 .unwrap_or_else(|| BooleanArray::from(vec![false; batch.num_rows()]));
1118 assert_eq!(actual, expected, "{hosts:?}, region {region}");
1119 }
1120 }
1121 }
1122
1123 #[test]
1124 fn test_columnar_value_to_boolean_array_scalar_false() {
1125 let result = columnar_value_to_boolean_array(
1126 ColumnarValue::Scalar(ScalarValue::Boolean(Some(false))),
1127 4,
1128 )
1129 .unwrap();
1130 assert_eq!(result.len(), 4);
1131 assert_eq!(result.true_count(), 0);
1132 }
1133
1134 #[test]
1135 fn test_columnar_value_to_boolean_array_scalar_true() {
1136 let result = columnar_value_to_boolean_array(
1137 ColumnarValue::Scalar(ScalarValue::Boolean(Some(true))),
1138 4,
1139 )
1140 .unwrap();
1141 assert_eq!(result.len(), 4);
1142 assert_eq!(result.true_count(), 4);
1143 }
1144}