1use std::collections::HashMap;
16use std::sync::Arc;
17
18use api::v1::region::{
19 BulkInsertRequest, DeleteRequest, DeleteRequests, InsertRequest, InsertRequests, RegionRequest,
20 bulk_insert_request, region_request::Body,
21};
22use api::v1::{ArrowIpc, PartitionExprVersion, Rows};
23use common_error::ext::BoxedError;
24use common_grpc::flight::{self, FlightDecoder};
25use datatypes::arrow::array::{Array, BinaryArray, StringBuilder};
26use datatypes::arrow::compute;
27use datatypes::arrow::datatypes::{DataType, Field, Schema};
28use datatypes::arrow::record_batch::RecordBatch;
29use mito_codec::row_converter::sparse::{SparseOffsetsCache, SparsePrimaryKeyView};
30use partition::PartitionRuleRef;
31use partition::manager::PartitionRuleManager;
32use partition::partition::RegionMask;
33use partition::splitter::RowSplitter;
34use snafu::{OptionExt, ResultExt, ensure};
35use store_api::storage::consts;
36use store_api::storage::{RegionId, RegionNumber, TableId};
37use table::metadata::TableInfo;
38
39use crate::error::{self, Result, SplitDeleteSnafu, SplitInsertSnafu};
40use crate::write_dispatcher::{WriteAttribution, write_row_count};
41
42pub struct Partitioner<'a> {
44 partition_manager: &'a PartitionRuleManager,
45}
46
47pub(crate) struct TablePartitioner {
49 table_id: TableId,
50 rule: PartitionRuleRef,
51 versions: HashMap<RegionNumber, Option<PartitionExprVersion>>,
52}
53
54impl<'a> Partitioner<'a> {
55 pub fn new(partition_manager: &'a PartitionRuleManager) -> Self {
57 Self { partition_manager }
58 }
59
60 pub async fn partition_insert_requests(
62 &self,
63 table_info: &TableInfo,
64 rows: Rows,
65 skip_wal: bool,
66 ) -> Result<Vec<InsertRequest>> {
67 TablePartitioner::try_new(self.partition_manager, table_info)
68 .await
69 .context(SplitInsertSnafu)?
70 .partition_insert_requests(rows, skip_wal)
71 }
72
73 pub async fn partition_delete_requests(
75 &self,
76 table_info: &TableInfo,
77 rows: Rows,
78 ) -> Result<Vec<DeleteRequest>> {
79 TablePartitioner::try_new(self.partition_manager, table_info)
80 .await
81 .context(SplitDeleteSnafu)?
82 .partition_delete_requests(rows)
83 }
84}
85
86impl TablePartitioner {
87 pub(crate) fn rebuild_attributed_write(
88 &self,
89 request: RegionRequest,
90 table_info: &TableInfo,
91 attribution: WriteAttribution,
92 ) -> Result<Vec<(RegionRequest, WriteAttribution)>> {
93 if !attribution.is_tracked() {
94 return Ok(self
95 .rebuild_write_requests(request, table_info)?
96 .into_iter()
97 .map(|request| (request, WriteAttribution::default()))
98 .collect());
99 }
100 let RegionRequest { header, body } = request;
101 if let Some(Body::BulkInsert(bulk)) = body {
102 return Ok(self
103 .rebuild_bulk_insert_request(bulk, table_info, attribution)?
104 .into_iter()
105 .map(|(body, attribution)| {
106 (
107 RegionRequest {
108 header: header.clone(),
109 body: Some(body),
110 },
111 attribution,
112 )
113 })
114 .collect());
115 }
116 self.rebuild_write_requests(RegionRequest { header, body }, table_info)?
117 .into_iter()
118 .map(|request| {
119 let attribution = attribution
120 .for_rows(write_row_count(&request))
121 .map_err(BoxedError::new)
122 .context(error::ExternalSnafu)?;
123 Ok((request, attribution))
124 })
125 .collect()
126 }
127
128 pub fn partition_attributed_bulk_requests(
129 &self,
130 batch: RecordBatch,
131 skip_wal: bool,
132 attribution: WriteAttribution,
133 ) -> Result<Vec<(BulkInsertRequest, WriteAttribution)>> {
134 if !attribution.is_tracked() {
135 return Ok(self
136 .partition_bulk_insert_requests(batch, None, skip_wal)?
137 .into_iter()
138 .map(|request| (request, WriteAttribution::default()))
139 .collect());
140 }
141 let masks = self
142 .rule
143 .split_record_batch(&batch)
144 .context(SplitInsertSnafu)?;
145 self.build_attributed_bulk_requests(batch, masks, None, skip_wal, attribution)
146 }
147
148 fn build_attributed_bulk_requests(
149 &self,
150 batch: RecordBatch,
151 masks: HashMap<RegionNumber, RegionMask>,
152 original_ipc: Option<ArrowIpc>,
153 skip_wal: bool,
154 attribution: WriteAttribution,
155 ) -> Result<Vec<(BulkInsertRequest, WriteAttribution)>> {
156 let mut by_region = masks
157 .iter()
158 .filter(|(_, mask)| !mask.select_none())
159 .map(|(region, mask)| {
160 Ok((
161 *region,
162 attribution
163 .filter(mask)
164 .map_err(BoxedError::new)
165 .context(error::ExternalSnafu)?,
166 ))
167 })
168 .collect::<Result<HashMap<_, _>>>()?;
169 self.build_bulk_insert_requests(batch, masks, original_ipc, skip_wal)?
170 .into_iter()
171 .map(|request| {
172 let attribution = by_region
173 .remove(&RegionId::from_u64(request.region_id).region_number())
174 .context(error::UnexpectedSnafu {
175 violated: "Missing write ownership after partitioning",
176 })?;
177 Ok((request, attribution))
178 })
179 .collect()
180 }
181
182 pub(crate) fn rebuild_tracked_write_requests(
184 &self,
185 request: RegionRequest,
186 table_info: &TableInfo,
187 owners: Option<Vec<usize>>,
188 ) -> Result<Vec<(RegionRequest, Option<Vec<usize>>)>> {
189 let Some(owners) = owners else {
190 return Ok(self
191 .rebuild_write_requests(request, table_info)?
192 .into_iter()
193 .map(|request| (request, None))
194 .collect());
195 };
196 let RegionRequest { header, body } = request;
197 if let Some(Body::BulkInsert(bulk)) = body {
198 let region_id = RegionId::from_u64(bulk.region_id);
199 if self.is_partition_expr_unchanged(region_id, bulk.partition_expr_version) {
200 return Ok(vec![(
201 RegionRequest {
202 header,
203 body: Some(Body::BulkInsert(bulk)),
204 },
205 Some(owners),
206 )]);
207 }
208 let bulk_insert_request::Body::ArrowIpc(ipc) =
209 bulk.body.context(error::UnexpectedSnafu {
210 violated: "Cannot rebuild a bulk insert without Arrow IPC data",
211 })?;
212 let batch = FlightDecoder::try_from_schema_bytes(&ipc.schema)
213 .and_then(|mut decoder| {
214 decoder.try_decode_record_batch(&ipc.data_header, &ipc.payload)
215 })
216 .map_err(BoxedError::new)
217 .context(error::ExternalSnafu)?;
218 let routing = self.restore_partition_tags(&batch, table_info)?;
219 let masks = self
220 .rule
221 .split_record_batch(routing.as_ref().unwrap_or(&batch))
222 .context(SplitInsertSnafu)?;
223 return self
224 .build_tracked_bulk_insert_requests(batch, masks, owners, Some(ipc), bulk.skip_wal)
225 .map(|requests| {
226 requests
227 .into_iter()
228 .map(|(request, owners)| {
229 (
230 RegionRequest {
231 header: header.clone(),
232 body: Some(Body::BulkInsert(request)),
233 },
234 Some(owners),
235 )
236 })
237 .collect()
238 });
239 }
240 let owner = owners.first().copied().context(error::UnexpectedSnafu {
241 violated: "Missing row ownership",
242 })?;
243 ensure!(
244 owners.iter().all(|value| *value == owner),
245 error::UnexpectedSnafu {
246 violated: "Mixed row ownership requires Arrow batching"
247 }
248 );
249 self.rebuild_write_requests(RegionRequest { header, body }, table_info)?
250 .into_iter()
251 .map(|request| {
252 let count = match &request.body {
253 Some(Body::Inserts(inserts)) => inserts
254 .requests
255 .iter()
256 .filter_map(|r| r.rows.as_ref())
257 .map(|r| r.rows.len())
258 .sum(),
259 _ => {
260 return error::UnexpectedSnafu {
261 violated: "Unsupported tracked write",
262 }
263 .fail();
264 }
265 };
266 Ok((request, Some(vec![owner; count])))
267 })
268 .collect()
269 }
270
271 pub(crate) fn partition_tracked_bulk_insert_requests(
272 &self,
273 batch: RecordBatch,
274 owners: Vec<usize>,
275 skip_wal: bool,
276 ) -> Result<Vec<(BulkInsertRequest, Vec<usize>)>> {
277 let masks = self
278 .rule
279 .split_record_batch(&batch)
280 .context(SplitInsertSnafu)?;
281 self.build_tracked_bulk_insert_requests(batch, masks, owners, None, skip_wal)
282 }
283
284 fn build_tracked_bulk_insert_requests(
285 &self,
286 batch: RecordBatch,
287 masks: HashMap<RegionNumber, RegionMask>,
288 owners: Vec<usize>,
289 original_ipc: Option<ArrowIpc>,
290 skip_wal: bool,
291 ) -> Result<Vec<(BulkInsertRequest, Vec<usize>)>> {
292 ensure!(
293 owners.len() == batch.num_rows(),
294 error::UnexpectedSnafu {
295 violated: "Write ownership does not match row count"
296 }
297 );
298 let mut selected = HashMap::new();
299 for (region, mask) in &masks {
300 let mut selected_owners = Vec::with_capacity(mask.selected_rows());
301 selected_owners.extend(
302 mask.array()
303 .values()
304 .set_indices()
305 .map(|index| owners[index]),
306 );
307 selected.insert(
308 RegionId::new(self.table_id, *region).as_u64(),
309 selected_owners,
310 );
311 }
312 self.build_bulk_insert_requests(batch, masks, original_ipc, skip_wal)?
313 .into_iter()
314 .map(|request| {
315 let owners =
316 selected
317 .remove(&request.region_id)
318 .context(error::UnexpectedSnafu {
319 violated: "Missing partition ownership",
320 })?;
321 Ok((request, owners))
322 })
323 .collect()
324 }
325
326 pub async fn try_new(
328 partition_manager: &PartitionRuleManager,
329 table_info: &TableInfo,
330 ) -> partition::error::Result<Self> {
331 let (rule, versions) = partition_manager
332 .find_table_partition_rule(table_info)
333 .await?;
334 Ok(Self {
335 table_id: table_info.table_id(),
336 rule,
337 versions: versions
338 .into_iter()
339 .map(|(region, version)| {
340 (region, version.map(|value| PartitionExprVersion { value }))
341 })
342 .collect(),
343 })
344 }
345
346 pub(crate) fn rebuild_write_requests(
352 &self,
353 request: RegionRequest,
354 table_info: &TableInfo,
355 ) -> Result<Vec<RegionRequest>> {
356 let RegionRequest { header, body } = request;
357 let bodies = match body.context(error::UnexpectedSnafu {
358 violated: "Cannot rebuild a request without a body",
359 })? {
360 Body::Inserts(inserts) => self.rebuild_insert_requests(inserts)?,
361 Body::Deletes(deletes) => self.rebuild_delete_requests(deletes)?,
362 Body::BulkInsert(bulk) => self
363 .rebuild_bulk_insert_request(bulk, table_info, WriteAttribution::default())?
364 .into_iter()
365 .map(|(body, _)| body)
366 .collect(),
367 _ => {
368 return error::UnexpectedSnafu {
369 violated: "Only writes can be rebuilt",
370 }
371 .fail();
372 }
373 };
374 Ok(bodies
375 .into_iter()
376 .map(|body| RegionRequest {
377 header: header.clone(),
378 body: Some(body),
379 })
380 .collect())
381 }
382
383 pub fn partition_insert_requests(
385 &self,
386 rows: Rows,
387 skip_wal: bool,
388 ) -> Result<Vec<InsertRequest>> {
389 Ok(RowSplitter::new(self.rule.clone())
390 .split(rows)
391 .context(SplitInsertSnafu)?
392 .into_iter()
393 .map(|(region, rows)| InsertRequest {
394 region_id: RegionId::new(self.table_id, region).as_u64(),
395 rows: Some(rows),
396 partition_expr_version: self.version(region),
397 skip_wal,
398 })
399 .collect())
400 }
401
402 pub fn partition_delete_requests(&self, rows: Rows) -> Result<Vec<DeleteRequest>> {
404 Ok(RowSplitter::new(self.rule.clone())
405 .split(rows)
406 .context(SplitDeleteSnafu)?
407 .into_iter()
408 .map(|(region, rows)| DeleteRequest {
409 region_id: RegionId::new(self.table_id, region).as_u64(),
410 rows: Some(rows),
411 partition_expr_version: self.version(region),
412 })
413 .collect())
414 }
415
416 pub fn partition_bulk_insert_requests(
418 &self,
419 batch: RecordBatch,
420 original_ipc: Option<ArrowIpc>,
421 skip_wal: bool,
422 ) -> Result<Vec<BulkInsertRequest>> {
423 let masks = self
424 .rule
425 .split_record_batch(&batch)
426 .context(SplitInsertSnafu)?;
427 self.build_bulk_insert_requests(batch, masks, original_ipc, skip_wal)
428 }
429
430 fn build_bulk_insert_requests(
431 &self,
432 batch: RecordBatch,
433 masks: HashMap<RegionNumber, RegionMask>,
434 mut original_ipc: Option<ArrowIpc>,
435 skip_wal: bool,
436 ) -> Result<Vec<BulkInsertRequest>> {
437 let mut requests = Vec::with_capacity(masks.len());
438 for (region, mask) in masks {
439 if mask.select_none() {
440 continue;
441 }
442 let original = if mask.select_all() {
443 original_ipc.take()
444 } else {
445 None
446 };
447 let ipc = if let Some(ipc) = original {
448 ipc
449 } else {
450 let selected = if mask.select_all() {
451 batch.clone()
452 } else {
453 compute::filter_record_batch(&batch, mask.array())
454 .context(error::ComputeArrowSnafu)?
455 };
456 let (schema, data_header, payload) = flight::record_batch_to_ipc(selected)
457 .map_err(BoxedError::new)
458 .context(error::ExternalSnafu)?;
459 ArrowIpc {
460 schema,
461 data_header,
462 payload,
463 }
464 };
465 requests.push(BulkInsertRequest {
466 region_id: RegionId::new(self.table_id, region).as_u64(),
467 partition_expr_version: self.version(region),
468 aligned_schema_version: None,
470 skip_wal,
471 body: Some(bulk_insert_request::Body::ArrowIpc(ipc)),
472 });
473 }
474 Ok(requests)
475 }
476
477 fn rebuild_insert_requests(&self, inserts: InsertRequests) -> Result<Vec<Body>> {
478 let mut bodies = Vec::new();
479 for insert in inserts.requests {
480 let region_id = RegionId::from_u64(insert.region_id);
481 if self.is_partition_expr_unchanged(region_id, insert.partition_expr_version) {
482 bodies.push(Body::Inserts(InsertRequests {
483 requests: vec![insert],
484 }));
485 continue;
486 }
487 let rows = insert.rows.context(error::UnexpectedSnafu {
488 violated: "Cannot rebuild an insert without rows",
489 })?;
490 for request in self.partition_insert_requests(rows, insert.skip_wal)? {
491 bodies.push(Body::Inserts(InsertRequests {
492 requests: vec![request],
493 }));
494 }
495 }
496 Ok(bodies)
497 }
498
499 fn rebuild_delete_requests(&self, deletes: DeleteRequests) -> Result<Vec<Body>> {
500 let mut bodies = Vec::new();
501 for delete in deletes.requests {
502 let region_id = RegionId::from_u64(delete.region_id);
503 if self.is_partition_expr_unchanged(region_id, delete.partition_expr_version) {
504 bodies.push(Body::Deletes(DeleteRequests {
505 requests: vec![delete],
506 }));
507 continue;
508 }
509 let rows = delete.rows.context(error::UnexpectedSnafu {
510 violated: "Cannot rebuild a delete without rows",
511 })?;
512 for request in self.partition_delete_requests(rows)? {
513 bodies.push(Body::Deletes(DeleteRequests {
514 requests: vec![request],
515 }));
516 }
517 }
518 Ok(bodies)
519 }
520
521 fn rebuild_bulk_insert_request(
522 &self,
523 bulk: BulkInsertRequest,
524 table_info: &TableInfo,
525 attribution: WriteAttribution,
526 ) -> Result<Vec<(Body, WriteAttribution)>> {
527 let region_id = RegionId::from_u64(bulk.region_id);
528 if self.is_partition_expr_unchanged(region_id, bulk.partition_expr_version) {
529 return Ok(vec![(Body::BulkInsert(bulk), attribution)]);
530 }
531 let bulk_insert_request::Body::ArrowIpc(ipc) =
532 bulk.body.context(error::UnexpectedSnafu {
533 violated: "Cannot rebuild a bulk insert without Arrow IPC data",
534 })?;
535 let batch = FlightDecoder::try_from_schema_bytes(&ipc.schema)
536 .and_then(|mut decoder| decoder.try_decode_record_batch(&ipc.data_header, &ipc.payload))
537 .map_err(BoxedError::new)
538 .context(error::ExternalSnafu)?;
539 let routing_batch = self.restore_partition_tags(&batch, table_info)?;
540 let masks = self
541 .rule
542 .split_record_batch(routing_batch.as_ref().unwrap_or(&batch))
543 .context(SplitInsertSnafu)?;
544 let writes = if attribution.is_tracked() {
545 self.build_attributed_bulk_requests(
546 batch,
547 masks,
548 Some(ipc),
549 bulk.skip_wal,
550 attribution,
551 )?
552 } else {
553 self.build_bulk_insert_requests(batch, masks, Some(ipc), bulk.skip_wal)?
554 .into_iter()
555 .map(|request| (request, WriteAttribution::default()))
556 .collect()
557 };
558 Ok(writes
559 .into_iter()
560 .map(|(request, attribution)| (Body::BulkInsert(request), attribution))
561 .collect())
562 }
563
564 fn restore_partition_tags(
566 &self,
567 batch: &RecordBatch,
568 table_info: &TableInfo,
569 ) -> Result<Option<RecordBatch>> {
570 let meta = &table_info.meta;
571 if !table_info.is_physical_table() || meta.column_ids.len() != meta.schema.num_columns() {
572 return Ok(None);
573 }
574 let missing = self
575 .rule
576 .partition_columns()
577 .iter()
578 .filter(|name| batch.column_by_name(name).is_none())
579 .filter_map(|name| {
580 let index = meta.schema.column_index_by_name(name)?;
581 (meta.primary_key_indices.contains(&index)
582 && meta.schema.column_schemas()[index].data_type.is_string())
583 .then_some((name, meta.column_ids[index]))
584 })
585 .collect::<Vec<_>>();
586 if missing.is_empty() {
587 return Ok(None);
588 }
589 let Some(primary_key) = batch.column_by_name(consts::PRIMARY_KEY_COLUMN_NAME) else {
590 return Ok(None);
591 };
592 let primary_key =
593 primary_key
594 .as_any()
595 .downcast_ref::<BinaryArray>()
596 .context(error::UnexpectedSnafu {
597 violated: "Sparse primary key column must be Binary",
598 })?;
599 let mut builders = missing
600 .iter()
601 .map(|_| StringBuilder::new())
602 .collect::<Vec<_>>();
603 let mut cache = SparseOffsetsCache::new();
604 let mut buffer = Vec::new();
605 for row in 0..batch.num_rows() {
606 ensure!(
607 !primary_key.is_null(row),
608 error::UnexpectedSnafu {
609 violated: "Sparse primary key must not be null",
610 }
611 );
612 let mut key = SparsePrimaryKeyView::new(primary_key.value(row), &mut cache)
613 .map_err(BoxedError::new)
614 .context(error::ExternalSnafu)?;
615 for ((_, column_id), builder) in missing.iter().zip(&mut builders) {
616 let label = key
617 .label(*column_id, &mut buffer)
618 .map_err(BoxedError::new)
619 .context(error::ExternalSnafu)?;
620 builder.append_option(label);
621 }
622 }
623 let schema = batch.schema();
624 let mut fields = schema.fields().to_vec();
625 let mut columns = batch.columns().to_vec();
626 for ((name, _), mut builder) in missing.into_iter().zip(builders) {
627 fields.push(Arc::new(Field::new(name, DataType::Utf8, true)));
628 columns.push(Arc::new(builder.finish()));
629 }
630 RecordBatch::try_new(
631 Arc::new(Schema::new_with_metadata(fields, schema.metadata().clone())),
632 columns,
633 )
634 .map(Some)
635 .context(error::ComputeArrowSnafu)
636 }
637
638 fn is_partition_expr_unchanged(
644 &self,
645 region_id: RegionId,
646 version: Option<PartitionExprVersion>,
647 ) -> bool {
648 region_id.table_id() == self.table_id
649 && self.versions.get(®ion_id.region_number()) == Some(&version)
650 }
651
652 fn version(&self, region: RegionNumber) -> Option<PartitionExprVersion> {
653 self.versions.get(®ion).copied().flatten()
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use std::sync::Arc;
660
661 use api::v1::region::{
662 AlignedSchemaVersion, BulkInsertRequest, DeleteRequest, InsertRequest, RegionRequestHeader,
663 };
664 use api::v1::value::ValueData;
665 use api::v1::{ArrowIpc, PartitionExprVersion};
666 use api::v1::{ColumnDataType, ColumnSchema, Row, Rows, Value};
667 use common_error::ext::{ErrorExt, RetryHint};
668 use common_error::status_code::StatusCode;
669 use common_grpc::flight;
670 use common_meta::cache::{self, TableInfoCache};
671 use common_meta::key::TableMetadataManager;
672 use common_meta::key::table_route::TableRouteValue;
673 use common_meta::kv_backend::memory::MemoryKvBackend;
674 use common_meta::kv_backend::test_util::MockKvBackendBuilder;
675 use common_meta::rpc::router::{Region, RegionRoute};
676 use common_meta::rpc::store::RangeResponse;
677 use datatypes::arrow::array::{BooleanArray, Int32Array, StringArray};
678 use datatypes::arrow::datatypes::{DataType, Field, Schema};
679 use datatypes::arrow::record_batch::RecordBatch;
680 use datatypes::data_type::ConcreteDataType;
681 use datatypes::schema;
682 use datatypes::value::{Value as DataValue, ValueRef};
683 use mito_codec::row_converter::sparse;
684 use mito_codec::row_converter::{PrimaryKeyCodec, SparsePrimaryKeyCodec};
685 use moka::future::Cache;
686 use partition::expr;
687 use partition::manager::{PartitionRuleManager, PartitionRuleManagerRef};
688 use partition::multi_dim::MultiDimPartitionRule;
689 use store_api::metric_engine_consts;
690
691 use super::*;
692 use crate::error::Error;
693 use crate::test_util;
694 use crate::test_util::{test_new_partition_info_cache, test_new_table_route_cache};
695
696 fn build_sparse_partitioner() -> TablePartitioner {
697 TablePartitioner {
698 table_id: 1,
699 rule: Arc::new(
700 MultiDimPartitionRule::try_new(
701 vec!["zone".to_string()],
702 vec![1, 2],
703 vec![
704 expr::col("zone").lt(DataValue::String("m".into())),
705 expr::col("zone").gt_eq(DataValue::String("m".into())),
706 ],
707 true,
708 )
709 .unwrap(),
710 ),
711 versions: [
712 (1, Some(PartitionExprVersion { value: 2 })),
713 (2, Some(PartitionExprVersion { value: 2 })),
714 ]
715 .into_iter()
716 .collect(),
717 }
718 }
719
720 fn build_sparse_table_info() -> TableInfo {
721 let mut table_info = test_util::new_test_table_info(1, "physical_tags", [1, 2].into_iter());
722 table_info.meta.schema = Arc::new(schema::Schema::new(vec![
723 schema::ColumnSchema::new("host", ConcreteDataType::string_datatype(), true),
724 schema::ColumnSchema::new("zone", ConcreteDataType::string_datatype(), true),
725 ]));
726 table_info.meta.primary_key_indices = vec![0, 1];
727 table_info.meta.partition_key_indices = vec![1];
728 table_info.meta.column_ids = vec![10, 20];
729 table_info.meta.options.extra_options.insert(
730 metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY.to_string(),
731 String::new(),
732 );
733 table_info
734 }
735
736 fn build_sparse_batch() -> RecordBatch {
737 let codec = SparsePrimaryKeyCodec::schemaless();
738 let keys = [Some("east"), Some("west"), Some(""), None, None]
739 .into_iter()
740 .enumerate()
741 .map(|(index, zone)| {
742 let mut key = Vec::new();
743 let values = [
744 (
745 sparse::RESERVED_COLUMN_ID_TABLE_ID,
746 ValueRef::UInt32(42 + index as u32),
747 ),
748 (
749 sparse::RESERVED_COLUMN_ID_TSID,
750 ValueRef::UInt64(index as u64),
751 ),
752 (10, ValueRef::String("node")),
753 ];
754 codec.encode_value_refs(&values, &mut key).unwrap();
755 if let Some(zone) = zone {
756 codec
757 .encode_raw_tag_value([(20, zone.as_bytes())].into_iter(), &mut key)
758 .unwrap();
759 }
760 if index == 4 {
761 key.extend_from_slice(&20_u32.to_be_bytes());
762 key.push(0);
763 }
764 key
765 })
766 .collect::<Vec<_>>();
767 RecordBatch::try_new(
768 Arc::new(Schema::new(vec![
769 Field::new("host", DataType::Utf8, false),
770 Field::new(consts::PRIMARY_KEY_COLUMN_NAME, DataType::Binary, false),
771 ])),
772 vec![
773 Arc::new(StringArray::from(vec!["node"; keys.len()])),
774 Arc::new(BinaryArray::from_iter_values(keys.iter())),
775 ],
776 )
777 .unwrap()
778 }
779
780 #[tokio::test]
781 async fn test_partition_tags_use_physical_table_metadata() {
782 let backend = Arc::new(MemoryKvBackend::new());
783 let table_route_cache = test_new_table_route_cache(backend.clone());
784 let partition_info_cache = test_new_partition_info_cache(table_route_cache.clone());
785 let manager =
786 PartitionRuleManager::new(backend.clone(), table_route_cache, partition_info_cache);
787 let table_info = build_sparse_table_info();
788 let routes = [
789 expr::col("zone").lt(DataValue::String("m".into())),
790 expr::col("zone").gt_eq(DataValue::String("m".into())),
791 ]
792 .into_iter()
793 .enumerate()
794 .map(|(index, expr)| RegionRoute {
795 region: Region {
796 id: RegionId::new(1, index as u32 + 1),
797 partition_expr: expr.as_json_str().unwrap(),
798 ..Default::default()
799 },
800 ..Default::default()
801 })
802 .collect();
803 TableMetadataManager::new(backend)
804 .create_table_metadata(
805 table_info.clone(),
806 TableRouteValue::physical(routes),
807 Default::default(),
808 )
809 .await
810 .unwrap();
811 let batch = build_sparse_batch();
812 let partitioner = TablePartitioner::try_new(&manager, &table_info)
813 .await
814 .unwrap();
815 let restored = partitioner
816 .restore_partition_tags(&batch, &table_info)
817 .unwrap()
818 .unwrap();
819 let zones = restored
820 .column_by_name("zone")
821 .unwrap()
822 .as_any()
823 .downcast_ref::<StringArray>()
824 .unwrap();
825 assert_eq!(
826 zones.iter().collect::<Vec<_>>(),
827 vec![Some("east"), Some("west"), Some(""), None, None]
828 );
829
830 for (physical, tag, valid_ids) in [
831 (false, true, true),
832 (true, false, true),
833 (true, true, false),
834 ] {
835 let mut info = table_info.clone();
836 if !physical {
837 info.meta
838 .options
839 .extra_options
840 .remove(metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY);
841 }
842 if !tag {
843 info.meta.primary_key_indices = vec![0];
844 }
845 if !valid_ids {
846 info.meta.column_ids.clear();
847 }
848 let partitioner = TablePartitioner::try_new(&manager, &info).await.unwrap();
849 assert!(
850 partitioner
851 .restore_partition_tags(&batch, &info)
852 .unwrap()
853 .is_none()
854 );
855 assert!(
856 partitioner
857 .partition_bulk_insert_requests(batch.clone(), None, false)
858 .is_err()
859 );
860 }
861 }
862
863 #[test]
864 fn test_rebuild_sparse_bulk_with_changed_partition_column() {
865 let partitioner = build_sparse_partitioner();
866 let table_info = build_sparse_table_info();
867 let batch = build_sparse_batch();
868 let restored = partitioner
869 .restore_partition_tags(&batch, &table_info)
870 .unwrap()
871 .unwrap();
872 let zones = restored
873 .column_by_name("zone")
874 .unwrap()
875 .as_any()
876 .downcast_ref::<StringArray>()
877 .unwrap();
878 assert_eq!(
879 zones.iter().collect::<Vec<_>>(),
880 vec![Some("east"), Some("west"), Some(""), None, None]
881 );
882 let (schema, data_header, payload) = flight::record_batch_to_ipc(batch.clone()).unwrap();
883 let rebuilt = partitioner
884 .rebuild_write_requests(
885 build_request(Body::BulkInsert(BulkInsertRequest {
886 region_id: RegionId::new(1, 1).as_u64(),
887 partition_expr_version: Some(PartitionExprVersion { value: 1 }),
888 skip_wal: true,
889 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
890 schema,
891 data_header,
892 payload,
893 })),
894 ..Default::default()
895 })),
896 &table_info,
897 )
898 .unwrap();
899 assert_eq!(rebuilt.len(), 2);
900 for request in rebuilt {
901 let Some(Body::BulkInsert(bulk)) = request.body else {
902 unreachable!()
903 };
904 assert!(bulk.skip_wal);
905 assert_eq!(
906 bulk.partition_expr_version,
907 Some(PartitionExprVersion { value: 2 })
908 );
909 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = bulk.body else {
910 unreachable!()
911 };
912 let actual = FlightDecoder::try_from_schema_bytes(&ipc.schema)
913 .unwrap()
914 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
915 .unwrap();
916 let mask = if RegionId::from_u64(bulk.region_id).region_number() == 1 {
917 vec![true, false, true, true, true]
918 } else {
919 vec![false, true, false, false, false]
920 };
921 let expected = compute::filter_record_batch(&batch, &mask.into()).unwrap();
922 assert_eq!(actual, expected);
923 }
924 }
925
926 #[test]
927 fn test_restore_partition_tags_checks_only_missing_tags() {
928 let partitioner = build_sparse_partitioner();
929 let table_info = build_sparse_table_info();
930 let batch = build_sparse_batch();
931 let invalid = RecordBatch::try_new(
932 batch.schema(),
933 vec![
934 batch.column(0).clone(),
935 Arc::new(BinaryArray::from_iter_values([b"invalid"; 5])),
936 ],
937 )
938 .unwrap();
939 assert!(
940 partitioner
941 .restore_partition_tags(&invalid, &table_info)
942 .is_err()
943 );
944 let mut fields = invalid.schema().fields().to_vec();
945 fields.push(Arc::new(Field::new("zone", DataType::Utf8, true)));
946 let mut columns = invalid.columns().to_vec();
947 columns.push(Arc::new(StringArray::from(vec![Some("east"); 5])));
948 let complete = RecordBatch::try_new(Arc::new(Schema::new(fields)), columns).unwrap();
949 assert!(
950 partitioner
951 .restore_partition_tags(&complete, &table_info)
952 .unwrap()
953 .is_none()
954 );
955 let (schema, data_header, payload) = flight::record_batch_to_ipc(invalid).unwrap();
956 let request = build_request(Body::BulkInsert(BulkInsertRequest {
957 region_id: RegionId::new(1, 1).as_u64(),
958 partition_expr_version: Some(PartitionExprVersion { value: 2 }),
959 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
960 schema,
961 data_header,
962 payload,
963 })),
964 ..Default::default()
965 }));
966 let expected = request.clone();
967 assert_eq!(
968 partitioner
969 .rebuild_write_requests(request, &table_info)
970 .unwrap(),
971 vec![expected]
972 );
973 }
974
975 #[tokio::test]
976 async fn test_partition_requests_preserve_routing_errors() {
977 let table_info = test_util::new_test_table_info(1, "table_1", [1].into_iter());
978 for (backend_unavailable, expected_status, expected_hint) in [
979 (false, StatusCode::TableNotFound, RetryHint::NonRetryable),
980 (true, StatusCode::Internal, RetryHint::Retryable),
981 ] {
982 let backend = Arc::new(
983 MockKvBackendBuilder::default()
984 .range_fn(Arc::new(move |_| {
985 if backend_unavailable {
986 common_meta::error::NoLeaderSnafu { table_id: 1u32 }.fail()
987 } else {
988 Ok(RangeResponse {
989 kvs: Vec::new(),
990 more: false,
991 })
992 }
993 }))
994 .build()
995 .unwrap(),
996 );
997 let routes = Arc::new(cache::new_table_route_cache(
998 "partitioner_test_routes".to_string(),
999 Cache::new(16),
1000 backend.clone(),
1001 ));
1002 let partitions = Arc::new(partition::cache::new_partition_info_cache(
1003 "partitioner_test_partitions".to_string(),
1004 Cache::new(16),
1005 routes.clone(),
1006 ));
1007 let manager = PartitionRuleManager::new(backend, routes, partitions);
1008 let partitioner = Partitioner::new(&manager);
1009 let insert_error = partitioner
1010 .partition_insert_requests(&table_info, Rows::default(), false)
1011 .await
1012 .unwrap_err();
1013 let delete_error = partitioner
1014 .partition_delete_requests(&table_info, Rows::default())
1015 .await
1016 .unwrap_err();
1017
1018 assert!(matches!(insert_error, Error::SplitInsert { .. }));
1019 assert!(matches!(delete_error, Error::SplitDelete { .. }));
1020 for error in [insert_error, delete_error] {
1021 assert_eq!(error.status_code(), expected_status);
1022 assert_eq!(error.retry_hint(), expected_hint);
1023 }
1024 }
1025 }
1026
1027 async fn create_fixture() -> (PartitionRuleManagerRef, TableInfoCache) {
1028 let backend = Arc::new(MemoryKvBackend::new());
1029 let manager = test_util::create_partition_rule_manager(backend.clone()).await;
1030 TableMetadataManager::new(backend.clone())
1031 .create_table_metadata(
1032 test_util::new_test_table_info(42, "logical_reroute", [1, 2, 3].into_iter()),
1033 TableRouteValue::logical(1),
1034 Default::default(),
1035 )
1036 .await
1037 .unwrap();
1038 let cache = cache::new_table_info_cache(
1039 "reroute_test_table_info".to_string(),
1040 Cache::new(16),
1041 backend,
1042 );
1043 (manager, cache)
1044 }
1045
1046 fn build_request(body: Body) -> RegionRequest {
1047 RegionRequest {
1048 header: Some(RegionRequestHeader {
1049 dbname: "greptime.public".to_string(),
1050 ..Default::default()
1051 }),
1052 body: Some(body),
1053 }
1054 }
1055
1056 fn build_rows(values: &[i32]) -> Rows {
1057 Rows {
1058 schema: vec![ColumnSchema {
1059 column_name: "a".to_string(),
1060 datatype: ColumnDataType::Int32 as i32,
1061 ..Default::default()
1062 }],
1063 rows: values
1064 .iter()
1065 .map(|value| Row {
1066 values: vec![Value {
1067 value_data: Some(ValueData::I32Value(*value)),
1068 }],
1069 })
1070 .collect(),
1071 }
1072 }
1073
1074 fn build_row_request(
1075 rows: Rows,
1076 insert: bool,
1077 region: u32,
1078 version: Option<u64>,
1079 ) -> RegionRequest {
1080 let region_id = RegionId::new(1, region).as_u64();
1081 let partition_expr_version = version.map(|value| PartitionExprVersion { value });
1082 let body = if insert {
1083 Body::Inserts(InsertRequests {
1084 requests: vec![InsertRequest {
1085 region_id,
1086 partition_expr_version,
1087 rows: Some(rows),
1088 skip_wal: true,
1089 }],
1090 })
1091 } else {
1092 Body::Deletes(DeleteRequests {
1093 requests: vec![DeleteRequest {
1094 region_id,
1095 partition_expr_version,
1096 rows: Some(rows),
1097 }],
1098 })
1099 };
1100 build_request(body)
1101 }
1102
1103 fn row_request_parts(request: &RegionRequest) -> (u64, Option<u64>, &Rows) {
1104 match request.body.as_ref().unwrap() {
1105 Body::Inserts(inserts) => {
1106 assert_eq!(inserts.requests.len(), 1);
1107 let insert = &inserts.requests[0];
1108 assert!(insert.skip_wal);
1109 (
1110 insert.region_id,
1111 insert.partition_expr_version.map(|v| v.value),
1112 insert.rows.as_ref().unwrap(),
1113 )
1114 }
1115 Body::Deletes(deletes) => {
1116 assert_eq!(deletes.requests.len(), 1);
1117 let delete = &deletes.requests[0];
1118 (
1119 delete.region_id,
1120 delete.partition_expr_version.map(|v| v.value),
1121 delete.rows.as_ref().unwrap(),
1122 )
1123 }
1124 _ => panic!("expected row write"),
1125 }
1126 }
1127
1128 #[tokio::test]
1129 async fn test_rebuild_rows_updates_partitions_without_losing_rows() {
1130 let (manager, cache) = create_fixture().await;
1131 let table_info = cache.get(1).await.unwrap().unwrap();
1132 let (_, versions) = manager
1133 .find_table_partition_rule(&table_info)
1134 .await
1135 .unwrap();
1136 for insert in [true, false] {
1137 let request = build_row_request(build_rows(&[1, 20, 70]), insert, 99, Some(0));
1138 let header = request.header.clone();
1139 let rerouted = rebuild_with_cached_table(&manager, &cache, request)
1140 .await
1141 .unwrap();
1142 assert_eq!(rerouted.len(), 3);
1143 let mut actual = Vec::new();
1144 for request in rerouted {
1145 assert_eq!(request.header, header);
1146 let (id, version, rows) = row_request_parts(&request);
1147 let region_id = RegionId::from_u64(id);
1148 assert_eq!(region_id.table_id(), 1);
1149 assert_eq!(version, versions[®ion_id.region_number()]);
1150 assert_eq!(rows.schema, build_rows(&[]).schema);
1151 assert_eq!(rows.rows.len(), 1);
1152 actual.push((region_id.region_number(), rows.rows[0].clone()));
1153 }
1154 actual.sort_by_key(|(region, _)| *region);
1155 assert_eq!(
1156 actual,
1157 vec![
1158 (1, build_rows(&[70]).rows.remove(0)),
1159 (2, build_rows(&[20]).rows.remove(0)),
1160 (3, build_rows(&[1]).rows.remove(0))
1161 ]
1162 );
1163 }
1164 }
1165
1166 #[tokio::test]
1167 async fn test_unchanged_row_routes_preserve_allocations() {
1168 let (manager, cache) = create_fixture().await;
1169 let table_info = cache.get(1).await.unwrap().unwrap();
1170 let (_, versions) = manager
1171 .find_table_partition_rule(&table_info)
1172 .await
1173 .unwrap();
1174 for insert in [true, false] {
1175 let request = build_row_request(build_rows(&[1]), insert, 3, versions[&3]);
1176 let original_rows = row_request_parts(&request).2;
1177 let row_ptr = original_rows.rows.as_ptr();
1178 let value_ptr = original_rows.rows[0].values.as_ptr();
1179 let rerouted = rebuild_with_cached_table(&manager, &cache, request)
1180 .await
1181 .unwrap();
1182 assert_eq!(rerouted.len(), 1);
1183 let rows = row_request_parts(&rerouted[0]).2;
1184 assert_eq!(rows.rows.as_ptr(), row_ptr);
1185 assert_eq!(rows.rows[0].values.as_ptr(), value_ptr);
1186 assert_eq!(rows, &build_rows(&[1]));
1187 }
1188 }
1189
1190 fn build_bulk_request(region: u32, version: Option<u64>, values: Vec<i32>) -> RegionRequest {
1191 let batch = RecordBatch::try_new(
1192 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)])),
1193 vec![Arc::new(Int32Array::from(values))],
1194 )
1195 .unwrap();
1196 let (schema, data_header, payload) = flight::record_batch_to_ipc(batch).unwrap();
1197 build_request(Body::BulkInsert(BulkInsertRequest {
1198 region_id: RegionId::new(1, region).as_u64(),
1199 partition_expr_version: version.map(|value| PartitionExprVersion { value }),
1200 aligned_schema_version: Some(AlignedSchemaVersion {
1201 schema_version: 123,
1202 }),
1203 skip_wal: true,
1204 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
1205 schema,
1206 data_header,
1207 payload,
1208 })),
1209 }))
1210 }
1211
1212 #[tokio::test]
1213 async fn test_rebuild_bulk_reencodes_only_changed_partitions() {
1214 let (manager, cache) = create_fixture().await;
1215 let table_info = cache.get(1).await.unwrap().unwrap();
1216 let (_, versions) = manager
1217 .find_table_partition_rule(&table_info)
1218 .await
1219 .unwrap();
1220 let partitioner = TablePartitioner::try_new(&manager, &table_info)
1221 .await
1222 .unwrap();
1223 for tracked in [false, true] {
1224 let request = build_bulk_request(99, Some(0), vec![1, 20, 70]);
1225 let header = request.header.clone();
1226 let rerouted = partitioner
1227 .rebuild_tracked_write_requests(
1228 request,
1229 &table_info,
1230 tracked.then(|| vec![2, 0, 2]),
1231 )
1232 .unwrap();
1233 assert_eq!(rerouted.len(), 3);
1234 let mut actual = Vec::new();
1235 for (request, owners) in rerouted {
1236 assert_eq!(request.header, header);
1237 let Some(Body::BulkInsert(bulk)) = request.body else {
1238 panic!("expected bulk insert")
1239 };
1240 let region = RegionId::from_u64(bulk.region_id);
1241 assert_eq!(region.table_id(), 1);
1242 assert!(bulk.skip_wal);
1243 assert_eq!(bulk.aligned_schema_version, None);
1244 assert_eq!(
1245 bulk.partition_expr_version.map(|v| v.value),
1246 versions[®ion.region_number()]
1247 );
1248 assert_eq!(
1249 owners,
1250 tracked.then(|| vec![if region.region_number() == 2 { 0 } else { 2 }])
1251 );
1252 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = bulk.body else {
1253 panic!("expected Arrow IPC")
1254 };
1255 let batch = FlightDecoder::try_from_schema_bytes(&ipc.schema)
1256 .unwrap()
1257 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
1258 .unwrap();
1259 assert_eq!(batch.num_rows(), 1);
1260 let values = batch
1261 .column(0)
1262 .as_any()
1263 .downcast_ref::<Int32Array>()
1264 .unwrap();
1265 actual.push((region.region_number(), values.value(0)));
1266 }
1267 actual.sort_unstable();
1268 assert_eq!(actual, [(1, 70), (2, 20), (3, 1)]);
1269
1270 for (region, version, unchanged) in [(99, Some(0), false), (3, versions[&3], true)] {
1272 let request = build_bulk_request(region, version, vec![1, 2, 3]);
1273 let header = request.header.clone();
1274 let Some(Body::BulkInsert(bulk)) = request.body.as_ref() else {
1275 unreachable!()
1276 };
1277 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = &bulk.body else {
1278 unreachable!()
1279 };
1280 let pointers = (
1281 ipc.schema.as_ptr(),
1282 ipc.data_header.as_ptr(),
1283 ipc.payload.as_ptr(),
1284 );
1285 let rerouted = partitioner
1286 .rebuild_tracked_write_requests(
1287 request,
1288 &table_info,
1289 tracked.then(|| vec![2, 0, 2]),
1290 )
1291 .unwrap();
1292 assert_eq!(rerouted.len(), 1);
1293 let (request, owners) = &rerouted[0];
1294 assert_eq!(request.header, header);
1295 assert_eq!(owners, &tracked.then(|| vec![2, 0, 2]));
1296 let Some(Body::BulkInsert(bulk)) = request.body.as_ref() else {
1297 unreachable!()
1298 };
1299 assert_eq!(bulk.region_id, RegionId::new(1, 3).as_u64());
1300 assert!(bulk.skip_wal);
1301 assert_eq!(bulk.partition_expr_version.map(|v| v.value), versions[&3]);
1302 assert_eq!(
1303 bulk.aligned_schema_version,
1304 unchanged.then_some(AlignedSchemaVersion {
1305 schema_version: 123
1306 })
1307 );
1308 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = &bulk.body else {
1309 unreachable!()
1310 };
1311 assert_eq!(
1312 (
1313 ipc.schema.as_ptr(),
1314 ipc.data_header.as_ptr(),
1315 ipc.payload.as_ptr()
1316 ),
1317 pointers,
1318 "tracked={tracked}, unchanged={unchanged}"
1319 );
1320 }
1321 }
1322 }
1323
1324 #[tokio::test]
1325 async fn test_rebuild_logical_table_preserves_logical_region_ids() {
1326 let (manager, cache) = create_fixture().await;
1327 for insert in [true, false] {
1328 let mut request = build_row_request(build_rows(&[1, 20, 70]), insert, 99, Some(0));
1329 match request.body.as_mut().unwrap() {
1330 Body::Inserts(inserts) => {
1331 inserts.requests[0].region_id = RegionId::new(42, 99).as_u64()
1332 }
1333 Body::Deletes(deletes) => {
1334 deletes.requests[0].region_id = RegionId::new(42, 99).as_u64()
1335 }
1336 _ => unreachable!(),
1337 }
1338 let rerouted = rebuild_with_cached_table(&manager, &cache, request)
1339 .await
1340 .unwrap();
1341 assert_eq!(rerouted.len(), 3);
1342 let mut regions = rerouted
1343 .iter()
1344 .map(|request| {
1345 let region = RegionId::from_u64(row_request_parts(request).0);
1346 assert_eq!(region.table_id(), 42);
1347 region.region_number()
1348 })
1349 .collect::<Vec<_>>();
1350 regions.sort_unstable();
1351 assert_eq!(regions, [1, 2, 3]);
1352 }
1353 }
1354
1355 async fn rebuild_with_cached_table(
1356 manager: &PartitionRuleManager,
1357 cache: &TableInfoCache,
1358 request: RegionRequest,
1359 ) -> Result<Vec<RegionRequest>> {
1360 let region_id = match request.body.as_ref().unwrap() {
1361 Body::Inserts(inserts) => inserts.requests[0].region_id,
1362 Body::Deletes(deletes) => deletes.requests[0].region_id,
1363 Body::BulkInsert(bulk) => bulk.region_id,
1364 _ => unreachable!(),
1365 };
1366 let table_id = RegionId::from_u64(region_id).table_id();
1367 let table_info = cache.get(table_id).await.unwrap().unwrap();
1368 let partitioner = TablePartitioner::try_new(manager, &table_info)
1369 .await
1370 .context(error::InvalidPartitionSnafu)?;
1371 partitioner.rebuild_write_requests(request, &table_info)
1372 }
1373
1374 #[tokio::test]
1375 async fn test_tracked_bulk_owners_preserve_payload_alignment() {
1376 let (manager, cache) = create_fixture().await;
1377 let table_info = cache.get(1).await.unwrap().unwrap();
1378 let partitioner = TablePartitioner::try_new(&manager, &table_info)
1379 .await
1380 .unwrap();
1381 for len in [0, 1, 7, 64, 65, 257] {
1382 let batch = RecordBatch::try_new(
1383 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)])),
1384 vec![Arc::new(Int32Array::from_iter_values(0..len as i32))],
1385 )
1386 .unwrap();
1387 let owners = (0..len)
1388 .map(|i| [2, 2, 0, 1, 0, 0][i % 6])
1389 .collect::<Vec<_>>();
1390 for offset in [0, 1, 7, 65] {
1391 for pattern in 0..3 {
1392 let values = (0..offset + len)
1393 .map(|i| match pattern {
1394 0 => false,
1395 1 => true,
1396 _ => i % 3 != 0,
1397 })
1398 .collect::<Vec<_>>();
1399 let expected = (0..len).filter(|i| values[offset + i]).collect::<Vec<_>>();
1400 let mask = BooleanArray::from(values).slice(offset, len);
1401 let requests = partitioner
1402 .build_tracked_bulk_insert_requests(
1403 batch.clone(),
1404 [(1, RegionMask::from(mask))].into_iter().collect(),
1405 owners.clone(),
1406 None,
1407 false,
1408 )
1409 .unwrap();
1410 if expected.is_empty() {
1411 assert!(requests.is_empty());
1412 continue;
1413 }
1414 assert_eq!(requests.len(), 1);
1415 let (request, selected_owners) = &requests[0];
1416 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = &request.body else {
1417 panic!("expected Arrow IPC")
1418 };
1419 let payload = FlightDecoder::try_from_schema_bytes(&ipc.schema)
1420 .unwrap()
1421 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
1422 .unwrap();
1423 let actual_rows = payload
1424 .column(0)
1425 .as_any()
1426 .downcast_ref::<Int32Array>()
1427 .unwrap();
1428 assert_eq!(
1429 actual_rows
1430 .values()
1431 .iter()
1432 .map(|x| *x as usize)
1433 .collect::<Vec<_>>(),
1434 expected
1435 );
1436 assert_eq!(
1437 *selected_owners,
1438 expected.iter().map(|i| owners[*i]).collect::<Vec<_>>()
1439 );
1440 }
1441 }
1442 let wrong = partitioner.build_tracked_bulk_insert_requests(
1443 batch,
1444 HashMap::new(),
1445 vec![0; len + 1],
1446 None,
1447 false,
1448 );
1449 assert!(wrong.is_err());
1450 }
1451 }
1452}