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::{BooleanArray, BooleanBufferBuilder, RecordBatch};
25use datatypes::arrow::buffer::BooleanBuffer;
26use datatypes::arrow::datatypes::Schema;
27use datatypes::prelude::Value;
28use datatypes::vectors::{Helper, VectorRef};
29use serde::{Deserialize, Serialize};
30use snafu::{OptionExt, ResultExt, ensure};
31use store_api::storage::RegionNumber;
32
33use crate::PartitionRule;
34use crate::checker::PartitionChecker;
35use crate::error::{self, Result, UndefinedColumnSnafu};
36use crate::expr::{Operand, PartitionExpr, RestrictedOp};
37use crate::partition::RegionMask;
38
39const DEFAULT_REGION: RegionNumber = 0;
41
42type PhysicalExprCache = Option<(Vec<Arc<dyn PhysicalExpr>>, Arc<Schema>)>;
43
44#[derive(Debug, Serialize, Deserialize)]
50pub struct MultiDimPartitionRule {
51 partition_columns: Vec<String>,
53 name_to_index: HashMap<String, usize>,
55 regions: Vec<RegionNumber>,
58 exprs: Vec<PartitionExpr>,
60 #[serde(skip)]
62 physical_expr_cache: RwLock<PhysicalExprCache>,
63}
64
65impl MultiDimPartitionRule {
66 pub fn try_new(
71 partition_columns: Vec<String>,
72 regions: Vec<RegionNumber>,
73 exprs: Vec<PartitionExpr>,
74 check_exprs: bool,
75 ) -> Result<Self> {
76 let name_to_index = partition_columns
77 .iter()
78 .enumerate()
79 .map(|(i, name)| (name.clone(), i))
80 .collect::<HashMap<_, _>>();
81
82 let rule = Self {
83 partition_columns,
84 name_to_index,
85 regions,
86 exprs,
87 physical_expr_cache: RwLock::new(None),
88 };
89
90 if check_exprs {
91 let checker = PartitionChecker::try_new(&rule)?;
92 checker.check()?;
93 }
94
95 Ok(rule)
96 }
97
98 pub fn exprs(&self) -> &[PartitionExpr] {
99 &self.exprs
100 }
101
102 fn find_region(&self, values: &[Value]) -> Result<RegionNumber> {
103 ensure!(
104 values.len() == self.partition_columns.len(),
105 error::RegionKeysSizeSnafu {
106 expect: self.partition_columns.len(),
107 actual: values.len(),
108 }
109 );
110
111 for (region_index, expr) in self.exprs.iter().enumerate() {
112 if self.evaluate_expr(expr, values)? {
113 return Ok(self.regions[region_index]);
114 }
115 }
116
117 Ok(DEFAULT_REGION)
119 }
120
121 fn evaluate_expr(&self, expr: &PartitionExpr, values: &[Value]) -> Result<bool> {
122 match (expr.lhs.as_ref(), expr.rhs.as_ref()) {
123 (Operand::Column(name), Operand::Value(r)) => {
124 let index = self
125 .name_to_index
126 .get(name)
127 .context(UndefinedColumnSnafu { column: name })?;
128 let l = &values[*index];
129 Self::perform_op(l, &expr.op, r)
130 }
131 (Operand::Value(l), Operand::Column(name)) => {
132 let index = self
133 .name_to_index
134 .get(name)
135 .context(UndefinedColumnSnafu { column: name })?;
136 let r = &values[*index];
137 Self::perform_op(l, &expr.op, r)
138 }
139 (Operand::Expr(lhs), Operand::Expr(rhs)) => {
140 let lhs = self.evaluate_expr(lhs, values)?;
141 let rhs = self.evaluate_expr(rhs, values)?;
142 match expr.op {
143 RestrictedOp::And => Ok(lhs && rhs),
144 RestrictedOp::Or => Ok(lhs || rhs),
145 _ => unreachable!(),
146 }
147 }
148 _ => unreachable!(),
149 }
150 }
151
152 fn perform_op(lhs: &Value, op: &RestrictedOp, rhs: &Value) -> Result<bool> {
153 let result = match op {
154 RestrictedOp::Eq => lhs.eq(rhs),
155 RestrictedOp::NotEq => lhs.ne(rhs),
156 RestrictedOp::Lt => lhs.partial_cmp(rhs) == Some(Ordering::Less),
157 RestrictedOp::LtEq => {
158 let result = lhs.partial_cmp(rhs);
159 result == Some(Ordering::Less) || result == Some(Ordering::Equal)
160 }
161 RestrictedOp::Gt => lhs.partial_cmp(rhs) == Some(Ordering::Greater),
162 RestrictedOp::GtEq => {
163 let result = lhs.partial_cmp(rhs);
164 result == Some(Ordering::Greater) || result == Some(Ordering::Equal)
165 }
166 RestrictedOp::And | RestrictedOp::Or => unreachable!(),
167 };
168
169 Ok(result)
170 }
171
172 pub fn row_at(&self, cols: &[VectorRef], index: usize, row: &mut [Value]) -> Result<()> {
173 for (col_idx, col) in cols.iter().enumerate() {
174 row[col_idx] = col.get(index);
175 }
176 Ok(())
177 }
178
179 pub fn record_batch_to_cols(&self, record_batch: &RecordBatch) -> Result<Vec<VectorRef>> {
180 self.partition_columns
181 .iter()
182 .map(|col_name| {
183 record_batch
184 .column_by_name(col_name)
185 .context(UndefinedColumnSnafu { column: col_name })
186 .and_then(|array| {
187 Helper::try_into_vector(array).context(error::ConvertToVectorSnafu)
188 })
189 })
190 .collect::<Result<Vec<_>>>()
191 }
192
193 pub fn split_record_batch_naive(
194 &self,
195 record_batch: &RecordBatch,
196 ) -> Result<HashMap<RegionNumber, BooleanArray>> {
197 let num_rows = record_batch.num_rows();
198
199 let mut result = self
200 .regions
201 .iter()
202 .map(|region| {
203 let mut builder = BooleanBufferBuilder::new(num_rows);
204 builder.append_n(num_rows, false);
205 (*region, builder)
206 })
207 .collect::<HashMap<_, _>>();
208
209 let cols = self.record_batch_to_cols(record_batch)?;
210 let mut current_row = vec![Value::Null; self.partition_columns.len()];
211 for row_idx in 0..num_rows {
212 self.row_at(&cols, row_idx, &mut current_row)?;
213 let current_region = self.find_region(¤t_row)?;
214 let region_mask = result
215 .get_mut(¤t_region)
216 .unwrap_or_else(|| panic!("Region {} must be initialized", current_region));
217 region_mask.set_bit(row_idx, true);
218 }
219
220 Ok(result
221 .into_iter()
222 .map(|(region, mut mask)| (region, BooleanArray::new(mask.finish(), None)))
223 .collect())
224 }
225
226 pub fn split_record_batch(
227 &self,
228 record_batch: &RecordBatch,
229 ) -> Result<HashMap<RegionNumber, RegionMask>> {
230 let num_rows = record_batch.num_rows();
231 if self.regions.len() == 1 {
232 return Ok([(
233 self.regions[0],
234 RegionMask::from(BooleanArray::from(vec![true; num_rows])),
235 )]
236 .into_iter()
237 .collect());
238 }
239 let physical_exprs = {
240 let cache_read_guard = self.physical_expr_cache.read().unwrap();
241 if let Some((cached_exprs, schema)) = cache_read_guard.as_ref()
242 && schema == record_batch.schema_ref()
243 {
244 cached_exprs.clone()
245 } else {
246 drop(cache_read_guard); let schema = record_batch.schema();
249 let new_cache = self
250 .exprs
251 .iter()
252 .map(|e| e.try_as_physical_expr(&schema))
253 .collect::<Result<Vec<_>>>()?;
254
255 let mut cache_write_guard = self.physical_expr_cache.write().unwrap();
256 cache_write_guard.replace((new_cache.clone(), schema));
257 new_cache
258 }
259 };
260
261 let mut result: HashMap<u32, RegionMask> = physical_exprs
262 .iter()
263 .zip(self.regions.iter())
264 .filter_map(|(expr, region_num)| {
265 let col_val = match expr
266 .evaluate(record_batch)
267 .context(error::EvaluateRecordBatchSnafu)
268 {
269 Ok(array) => array,
270 Err(e) => {
271 return Some(Err(e));
272 }
273 };
274 let array = match columnar_value_to_boolean_array(col_val, num_rows) {
275 Ok(array) => array,
276 Err(e) => {
277 return Some(Err(e));
278 }
279 };
280 let selected_rows = array.true_count();
281 if selected_rows == 0 {
282 return None;
284 }
285 Some(Ok((*region_num, RegionMask::new(array, selected_rows))))
286 })
287 .collect::<error::Result<_>>()?;
288
289 let selected = if result.len() == 1 {
290 result.values().next().unwrap().array().clone()
291 } else {
292 let mut selected = BooleanArray::new(BooleanBuffer::new_unset(num_rows), None);
293 for region_mask in result.values() {
294 selected = arrow::compute::kernels::boolean::or(&selected, region_mask.array())
295 .context(error::ComputeArrowKernelSnafu)?;
296 }
297 selected
298 };
299
300 if selected.true_count() == num_rows {
302 return Ok(result);
303 }
304
305 let unselected = arrow::compute::kernels::boolean::not(&selected)
307 .context(error::ComputeArrowKernelSnafu)?;
308 match result.entry(DEFAULT_REGION) {
309 Entry::Occupied(mut o) => {
310 let default_region_mask = RegionMask::from(
312 arrow::compute::kernels::boolean::or(o.get().array(), &unselected)
313 .context(error::ComputeArrowKernelSnafu)?,
314 );
315 o.insert(default_region_mask);
316 }
317 Entry::Vacant(v) => {
318 v.insert(RegionMask::from(unselected));
320 }
321 }
322 Ok(result)
323 }
324}
325
326fn columnar_value_to_boolean_array(
327 col_val: ColumnarValue,
328 num_rows: usize,
329) -> Result<BooleanArray> {
330 let column = col_val
331 .into_array(num_rows)
332 .context(error::EvaluateRecordBatchSnafu)?;
333 let array = column
334 .as_any()
335 .downcast_ref::<BooleanArray>()
336 .with_context(|| error::UnexpectedColumnTypeSnafu {
337 data_type: column.data_type().clone(),
338 })?;
339 Ok(array.clone())
340}
341
342impl PartitionRule for MultiDimPartitionRule {
343 fn as_any(&self) -> &dyn Any {
344 self
345 }
346
347 fn partition_columns(&self) -> &[String] {
348 &self.partition_columns
349 }
350
351 fn find_region(&self, values: &[Value]) -> Result<RegionNumber> {
352 self.find_region(values)
353 }
354
355 fn split_record_batch(
356 &self,
357 record_batch: &RecordBatch,
358 ) -> Result<HashMap<RegionNumber, RegionMask>> {
359 self.split_record_batch(record_batch)
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use std::assert_matches;
366
367 use super::*;
368 use crate::error::{self, Error};
369 use crate::expr::col;
370
371 #[test]
372 fn test_find_region() {
373 let rule = MultiDimPartitionRule::try_new(
379 vec!["b".to_string()],
380 vec![1, 2, 3],
381 vec![
382 PartitionExpr::new(
383 Operand::Column("b".to_string()),
384 RestrictedOp::Lt,
385 Operand::Value(datatypes::value::Value::String("hz".into())),
386 ),
387 PartitionExpr::new(
388 Operand::Expr(PartitionExpr::new(
389 Operand::Column("b".to_string()),
390 RestrictedOp::GtEq,
391 Operand::Value(datatypes::value::Value::String("hz".into())),
392 )),
393 RestrictedOp::And,
394 Operand::Expr(PartitionExpr::new(
395 Operand::Column("b".to_string()),
396 RestrictedOp::Lt,
397 Operand::Value(datatypes::value::Value::String("sh".into())),
398 )),
399 ),
400 PartitionExpr::new(
401 Operand::Column("b".to_string()),
402 RestrictedOp::GtEq,
403 Operand::Value(datatypes::value::Value::String("sh".into())),
404 ),
405 ],
406 true,
407 )
408 .unwrap();
409 assert_matches!(
410 rule.find_region(&["foo".into(), 1000_i32.into()]),
411 Err(error::Error::RegionKeysSize {
412 expect: 1,
413 actual: 2,
414 ..
415 })
416 );
417 assert_matches!(rule.find_region(&["foo".into()]), Ok(1));
418 assert_matches!(rule.find_region(&["bar".into()]), Ok(1));
419 assert_matches!(rule.find_region(&["hz".into()]), Ok(2));
420 assert_matches!(rule.find_region(&["hzz".into()]), Ok(2));
421 assert_matches!(rule.find_region(&["sh".into()]), Ok(3));
422 assert_matches!(rule.find_region(&["zzzz".into()]), Ok(3));
423 }
424
425 #[test]
426 fn test_find_region_rejects_undeclared_column_on_lhs() {
427 let rule = MultiDimPartitionRule::try_new(
428 vec!["host".to_string()],
429 vec![1],
430 vec![PartitionExpr::new(
431 Operand::Column("rack".to_string()),
432 RestrictedOp::Lt,
433 Operand::Value(Value::String("n".into())),
434 )],
435 false,
436 )
437 .unwrap();
438
439 assert_matches!(
440 rule.find_region(&[Value::String("z".into())]),
441 Err(Error::UndefinedColumn { column, .. }) if column == "rack"
442 );
443 }
444
445 #[test]
446 fn test_find_region_rejects_undeclared_column_on_rhs() {
447 let rule = MultiDimPartitionRule::try_new(
448 vec!["host".to_string()],
449 vec![1],
450 vec![PartitionExpr::new(
451 Operand::Value(Value::String("n".into())),
452 RestrictedOp::Gt,
453 Operand::Column("rack".to_string()),
454 )],
455 false,
456 )
457 .unwrap();
458
459 assert_matches!(
460 rule.find_region(&[Value::String("z".into())]),
461 Err(Error::UndefinedColumn { column, .. }) if column == "rack"
462 );
463 }
464
465 #[test]
466 fn invalid_expr_case_1() {
467 let rule = MultiDimPartitionRule::try_new(
471 vec!["a".to_string(), "b".to_string()],
472 vec![1],
473 vec![PartitionExpr::new(
474 Operand::Column("b".to_string()),
475 RestrictedOp::LtEq,
476 Operand::Expr(PartitionExpr::new(
477 Operand::Expr(PartitionExpr::new(
478 Operand::Column("b".to_string()),
479 RestrictedOp::GtEq,
480 Operand::Value(datatypes::value::Value::String("hz".into())),
481 )),
482 RestrictedOp::And,
483 Operand::Expr(PartitionExpr::new(
484 Operand::Column("b".to_string()),
485 RestrictedOp::Lt,
486 Operand::Value(datatypes::value::Value::String("sh".into())),
487 )),
488 )),
489 )],
490 true,
491 );
492
493 assert_matches!(rule.unwrap_err(), Error::InvalidExpr { .. });
495 }
496
497 #[test]
498 fn invalid_expr_case_2() {
499 let rule = MultiDimPartitionRule::try_new(
503 vec!["a".to_string(), "b".to_string()],
504 vec![1],
505 vec![PartitionExpr::new(
506 Operand::Expr(PartitionExpr::new(
507 Operand::Column("b".to_string()),
508 RestrictedOp::GtEq,
509 Operand::Value(datatypes::value::Value::String("hz".into())),
510 )),
511 RestrictedOp::And,
512 Operand::Value(datatypes::value::Value::String("sh".into())),
513 )],
514 true,
515 );
516
517 assert_matches!(rule.unwrap_err(), Error::InvalidExpr { .. });
519 }
520
521 #[test]
530 fn empty_expr_case_1() {
531 let rule = MultiDimPartitionRule::try_new(
536 vec!["a".to_string(), "b".to_string()],
537 vec![1, 2],
538 vec![
539 PartitionExpr::new(
540 Operand::Column("b".to_string()),
541 RestrictedOp::LtEq,
542 Operand::Value(datatypes::value::Value::String("h".into())),
543 ),
544 PartitionExpr::new(
545 Operand::Column("b".to_string()),
546 RestrictedOp::GtEq,
547 Operand::Value(datatypes::value::Value::String("s".into())),
548 ),
549 ],
550 true,
551 );
552
553 assert_matches!(rule.unwrap_err(), Error::CheckpointNotCovered { .. });
555 }
556
557 #[test]
572 fn empty_expr_case_2() {
573 let rule = MultiDimPartitionRule::try_new(
578 vec!["a".to_string(), "b".to_string()],
579 vec![1, 2],
580 vec![
581 PartitionExpr::new(
582 Operand::Expr(PartitionExpr::new(
583 Operand::Expr(PartitionExpr::new(
584 Operand::Expr(PartitionExpr::new(
586 Operand::Expr(PartitionExpr::new(
587 Operand::Column("a".to_string()),
588 RestrictedOp::GtEq,
589 Operand::Value(datatypes::value::Value::Int64(100)),
590 )),
591 RestrictedOp::And,
592 Operand::Expr(PartitionExpr::new(
593 Operand::Column("b".to_string()),
594 RestrictedOp::LtEq,
595 Operand::Value(datatypes::value::Value::Int64(10)),
596 )),
597 )),
598 RestrictedOp::Or,
599 Operand::Expr(PartitionExpr::new(
601 Operand::Expr(PartitionExpr::new(
602 Operand::Expr(PartitionExpr::new(
603 Operand::Column("a".to_string()),
604 RestrictedOp::Gt,
605 Operand::Value(datatypes::value::Value::Int64(100)),
606 )),
607 RestrictedOp::And,
608 Operand::Expr(PartitionExpr::new(
609 Operand::Column("a".to_string()),
610 RestrictedOp::LtEq,
611 Operand::Value(datatypes::value::Value::Int64(200)),
612 )),
613 )),
614 RestrictedOp::And,
615 Operand::Expr(PartitionExpr::new(
616 Operand::Column("b".to_string()),
617 RestrictedOp::LtEq,
618 Operand::Value(datatypes::value::Value::Int64(10)),
619 )),
620 )),
621 )),
622 RestrictedOp::Or,
623 Operand::Expr(PartitionExpr::new(
625 Operand::Expr(PartitionExpr::new(
626 Operand::Expr(PartitionExpr::new(
627 Operand::Column("a".to_string()),
628 RestrictedOp::GtEq,
629 Operand::Value(datatypes::value::Value::Int64(200)),
630 )),
631 RestrictedOp::And,
632 Operand::Expr(PartitionExpr::new(
633 Operand::Column("b".to_string()),
634 RestrictedOp::Gt,
635 Operand::Value(datatypes::value::Value::Int64(10)),
636 )),
637 )),
638 RestrictedOp::And,
639 Operand::Expr(PartitionExpr::new(
640 Operand::Column("b".to_string()),
641 RestrictedOp::LtEq,
642 Operand::Value(datatypes::value::Value::Int64(20)),
643 )),
644 )),
645 )),
646 RestrictedOp::Or,
647 Operand::Expr(PartitionExpr::new(
649 Operand::Expr(PartitionExpr::new(
650 Operand::Column("a".to_string()),
651 RestrictedOp::Gt,
652 Operand::Value(datatypes::value::Value::Int64(200)),
653 )),
654 RestrictedOp::And,
655 Operand::Expr(PartitionExpr::new(
656 Operand::Column("b".to_string()),
657 RestrictedOp::LtEq,
658 Operand::Value(datatypes::value::Value::Int64(20)),
659 )),
660 )),
661 ),
662 PartitionExpr::new(
663 Operand::Expr(PartitionExpr::new(
665 Operand::Expr(PartitionExpr::new(
666 Operand::Column("a".to_string()),
667 RestrictedOp::Lt,
668 Operand::Value(datatypes::value::Value::Int64(100)),
669 )),
670 RestrictedOp::And,
671 Operand::Expr(PartitionExpr::new(
672 Operand::Column("b".to_string()),
673 RestrictedOp::LtEq,
674 Operand::Value(datatypes::value::Value::Int64(20)),
675 )),
676 )),
677 RestrictedOp::Or,
678 Operand::Expr(PartitionExpr::new(
680 Operand::Expr(PartitionExpr::new(
681 Operand::Column("a".to_string()),
682 RestrictedOp::GtEq,
683 Operand::Value(datatypes::value::Value::Int64(100)),
684 )),
685 RestrictedOp::And,
686 Operand::Expr(PartitionExpr::new(
687 Operand::Column("b".to_string()),
688 RestrictedOp::GtEq,
689 Operand::Value(datatypes::value::Value::Int64(20)),
690 )),
691 )),
692 ),
693 ],
694 true,
695 );
696
697 assert_matches!(rule.unwrap_err(), Error::CheckpointNotCovered { .. });
699 }
700
701 #[test]
702 fn duplicate_expr_case_1() {
703 let rule = MultiDimPartitionRule::try_new(
708 vec!["a".to_string(), "b".to_string()],
709 vec![1, 2],
710 vec![
711 PartitionExpr::new(
712 Operand::Column("a".to_string()),
713 RestrictedOp::LtEq,
714 Operand::Value(datatypes::value::Value::Int64(20)),
715 ),
716 PartitionExpr::new(
717 Operand::Column("a".to_string()),
718 RestrictedOp::GtEq,
719 Operand::Value(datatypes::value::Value::Int64(10)),
720 ),
721 ],
722 true,
723 );
724
725 assert_matches!(rule.unwrap_err(), Error::CheckpointOverlapped { .. });
727 }
728
729 #[test]
730 fn duplicate_expr_case_2() {
731 let rule = MultiDimPartitionRule::try_new(
737 vec!["a".to_string(), "b".to_string()],
738 vec![1, 2],
739 vec![
740 PartitionExpr::new(
741 Operand::Column("a".to_string()),
742 RestrictedOp::NotEq,
743 Operand::Value(datatypes::value::Value::Int64(20)),
744 ),
745 PartitionExpr::new(
746 Operand::Column("a".to_string()),
747 RestrictedOp::LtEq,
748 Operand::Value(datatypes::value::Value::Int64(20)),
749 ),
750 PartitionExpr::new(
751 Operand::Column("a".to_string()),
752 RestrictedOp::Gt,
753 Operand::Value(datatypes::value::Value::Int64(20)),
754 ),
755 ],
756 true,
757 );
758
759 assert_matches!(rule.unwrap_err(), Error::CheckpointOverlapped { .. });
761 }
762
763 #[test]
774 fn test_partial_divided() {
775 let _rule = MultiDimPartitionRule::try_new(
776 vec!["host".to_string(), "value".to_string()],
777 vec![0, 1, 2, 3],
778 vec![
779 col("host")
780 .lt(Value::String("server10".into()))
781 .and(col("value").lt(Value::Int64(10))),
782 col("host")
783 .lt(Value::String("server10".into()))
784 .and(col("value").gt_eq(Value::Int64(10))),
785 col("host").gt_eq(Value::String("server10".into())),
786 ],
787 true,
788 )
789 .unwrap();
790 }
791}
792
793#[cfg(test)]
794mod test_split_record_batch {
795 use std::sync::Arc;
796
797 use datafusion_common::ScalarValue;
798 use datatypes::arrow::array::{Int64Array, StringArray};
799 use datatypes::arrow::datatypes::{DataType, Field, Schema};
800 use datatypes::arrow::record_batch::RecordBatch;
801 use rand::Rng;
802
803 use super::*;
804 use crate::expr::{Operand, col};
805
806 fn test_schema() -> Arc<Schema> {
807 Arc::new(Schema::new(vec![
808 Field::new("host", DataType::Utf8, false),
809 Field::new("value", DataType::Int64, false),
810 ]))
811 }
812
813 fn generate_random_record_batch(num_rows: usize) -> RecordBatch {
814 let schema = test_schema();
815 let mut rng = rand::thread_rng();
816 let mut host_array = Vec::with_capacity(num_rows);
817 let mut value_array = Vec::with_capacity(num_rows);
818 for _ in 0..num_rows {
819 host_array.push(format!("server{}", rng.gen_range(0..20)));
820 value_array.push(rng.gen_range(0..20));
821 }
822 let host_array = StringArray::from(host_array);
823 let value_array = Int64Array::from(value_array);
824 RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)]).unwrap()
825 }
826
827 #[test]
828 fn test_split_record_batch_by_one_column() {
829 let rule = MultiDimPartitionRule::try_new(
831 vec!["host".to_string(), "value".to_string()],
832 vec![0, 1],
833 vec![
834 col("host").lt(Value::String("server1".into())),
835 col("host").gt_eq(Value::String("server1".into())),
836 ],
837 true,
838 )
839 .unwrap();
840
841 let batch = generate_random_record_batch(1000);
842 let result = rule.split_record_batch(&batch).unwrap();
844 let expected = rule.split_record_batch_naive(&batch).unwrap();
845 assert_eq!(result.len(), expected.len());
846 for (region, value) in &result {
847 assert_eq!(
848 value.array(),
849 expected.get(region).unwrap(),
850 "failed on region: {}",
851 region
852 );
853 }
854 }
855
856 #[test]
857 fn test_split_record_batch_empty() {
858 let rule = MultiDimPartitionRule::try_new(
860 vec!["host".to_string()],
861 vec![1],
862 vec![
863 col("host").lt(Value::String("server1".into())),
864 col("host").gt_eq(Value::String("server1".into())),
865 ],
866 true,
867 )
868 .unwrap();
869
870 let schema = test_schema();
871 let host_array = StringArray::from(Vec::<&str>::new());
872 let value_array = Int64Array::from(Vec::<i64>::new());
873 let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)])
874 .unwrap();
875
876 let result = rule.split_record_batch(&batch).unwrap();
877 assert_eq!(result.len(), 1);
878 }
879
880 #[test]
881 fn test_split_record_batch_by_two_columns() {
882 let rule = MultiDimPartitionRule::try_new(
883 vec!["host".to_string(), "value".to_string()],
884 vec![0, 1, 2, 3],
885 vec![
886 col("host")
887 .lt(Value::String("server10".into()))
888 .and(col("value").lt(Value::Int64(10))),
889 col("host")
890 .lt(Value::String("server10".into()))
891 .and(col("value").gt_eq(Value::Int64(10))),
892 col("host")
893 .gt_eq(Value::String("server10".into()))
894 .and(col("value").lt(Value::Int64(10))),
895 col("host")
896 .gt_eq(Value::String("server10".into()))
897 .and(col("value").gt_eq(Value::Int64(10))),
898 ],
899 true,
900 )
901 .unwrap();
902
903 let batch = generate_random_record_batch(1000);
904 let result = rule.split_record_batch(&batch).unwrap();
905 let expected = rule.split_record_batch_naive(&batch).unwrap();
906 assert_eq!(result.len(), expected.len());
907 for (region, value) in &result {
908 assert_eq!(value.array(), expected.get(region).unwrap());
909 }
910 }
911
912 #[test]
913 fn test_all_rows_selected() {
914 let rule = MultiDimPartitionRule::try_new(
916 vec!["value".to_string()],
917 vec![1, 2],
918 vec![
919 col("value").lt(Value::Int64(30)),
920 col("value").gt_eq(Value::Int64(30)),
921 ],
922 true,
923 )
924 .unwrap();
925
926 let schema = test_schema();
927 let host_array = StringArray::from(vec!["server1", "server2", "server3", "server4"]);
928 let value_array = Int64Array::from(vec![10, 20, 30, 40]);
929 let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)])
930 .unwrap();
931
932 let result = rule.split_record_batch(&batch).unwrap();
933
934 assert_eq!(result.len(), 2);
936 assert!(result.contains_key(&1));
937 assert!(result.contains_key(&2));
938
939 assert_eq!(result.get(&1).unwrap().selected_rows(), 2); assert_eq!(result.get(&2).unwrap().selected_rows(), 2); }
943
944 #[test]
945 fn test_split_record_batch_with_scalar_predicate() {
946 let rule = MultiDimPartitionRule::try_new(
948 vec!["host".to_string()],
949 vec![0, 1],
950 vec![
951 PartitionExpr::new(
952 Operand::Column("host".to_string()),
953 RestrictedOp::Lt,
954 Operand::Value(Value::String("never_happen_1".into())),
955 ),
956 PartitionExpr::new(
957 Operand::Expr(PartitionExpr::new(
958 Operand::Column("host".to_string()),
959 RestrictedOp::GtEq,
960 Operand::Value(Value::String("never_happen_1".into())),
961 )),
962 RestrictedOp::And,
963 Operand::Value(Value::Boolean(false)),
964 ),
965 ],
966 false,
967 )
968 .unwrap();
969
970 let batch = generate_random_record_batch(8);
971 let result = rule.split_record_batch(&batch).unwrap();
972
973 assert_eq!(result.len(), 1);
974 assert!(result.contains_key(&0));
975
976 let total_rows = result.get(&0).unwrap().selected_rows();
977 assert_eq!(total_rows, batch.num_rows());
978 }
979
980 #[test]
981 fn test_columnar_value_to_boolean_array_scalar_false() {
982 let result = columnar_value_to_boolean_array(
983 ColumnarValue::Scalar(ScalarValue::Boolean(Some(false))),
984 4,
985 )
986 .unwrap();
987 assert_eq!(result.len(), 4);
988 assert_eq!(result.true_count(), 0);
989 }
990
991 #[test]
992 fn test_columnar_value_to_boolean_array_scalar_true() {
993 let result = columnar_value_to_boolean_array(
994 ColumnarValue::Scalar(ScalarValue::Boolean(Some(true))),
995 4,
996 )
997 .unwrap();
998 assert_eq!(result.len(), 4);
999 assert_eq!(result.true_count(), 4);
1000 }
1001}