1use std::collections::HashSet;
16
17use api::v1::{ArrowIpc, SemanticType};
18use bytes::Bytes;
19use common_error::ext::BoxedError;
20use common_grpc::flight::{FlightEncoder, FlightMessage};
21use datatypes::arrow::record_batch::RecordBatch;
22use mito2::request::check_bulk_insert_batch;
23use snafu::{OptionExt, ResultExt, ensure};
24use store_api::codec::PrimaryKeyEncoding;
25use store_api::metadata::RegionMetadataRef;
26use store_api::region_request::{AffectedRows, RegionBulkInsertsRequest, RegionRequest};
27use store_api::storage::RegionId;
28
29use crate::batch_modifier::{TagColumnInfo, modify_batch_sparse};
30use crate::engine::MetricEngineInner;
31use crate::error;
32use crate::error::Result;
33use crate::metrics::MITO_OPERATION_ELAPSED;
34
35impl MetricEngineInner {
36 pub async fn bulk_insert_region(
49 &self,
50 region_id: RegionId,
51 request: RegionBulkInsertsRequest,
52 ) -> Result<AffectedRows> {
53 if request.payload.num_rows() == 0 {
54 return Ok(0);
55 }
56 if self.is_physical_region(region_id) {
57 let _timer = MITO_OPERATION_ELAPSED
58 .with_label_values(&["bulk_insert_physical"])
59 .start_timer();
60 return self.bulk_insert_physical_region(region_id, request).await;
61 }
62
63 let _timer = MITO_OPERATION_ELAPSED
64 .with_label_values(&["bulk_insert_logical"])
65 .start_timer();
66 self.bulk_insert_logical_region(region_id, request).await
67 }
68
69 async fn bulk_insert_physical_region(
74 &self,
75 region_id: RegionId,
76 mut request: RegionBulkInsertsRequest,
77 ) -> Result<AffectedRows> {
78 request.aligned_schema_version = None;
81 self.data_region
82 .write_data(region_id, RegionRequest::BulkInserts(request))
83 .await
84 }
85
86 async fn bulk_insert_logical_region(
88 &self,
89 region_id: RegionId,
90 request: RegionBulkInsertsRequest,
91 ) -> Result<AffectedRows> {
92 let (physical_region_id, data_region_id, primary_key_encoding) =
93 self.find_data_region_meta(region_id)?;
94
95 if primary_key_encoding != PrimaryKeyEncoding::Sparse {
96 return error::UnsupportedRegionRequestSnafu {
97 request: RegionRequest::BulkInserts(request),
98 }
99 .fail();
100 }
101
102 let batch = request.payload;
103 if batch.num_rows() == 0 {
104 return Ok(0);
105 }
106
107 let logical_metadata = self
108 .logical_region_metadata(physical_region_id, region_id)
109 .await?;
110 let (tag_columns, non_tag_indices) = self.resolve_tag_columns_from_metadata(
111 region_id,
112 data_region_id,
113 &batch,
114 &logical_metadata,
115 )?;
116 check_bulk_insert_batch(&logical_metadata, &batch)
119 .map_err(BoxedError::new)
120 .context(error::MitoWriteOperationSnafu)?;
121 let modified_batch = modify_batch_sparse(
122 batch.clone(),
123 region_id.table_id(),
124 &tag_columns,
125 &non_tag_indices,
126 )?;
127 let (schema, data_header, payload) = record_batch_to_ipc(&modified_batch)?;
128
129 let partition_expr_version = request.partition_expr_version;
130
131 let request = RegionBulkInsertsRequest {
132 skip_wal: request.skip_wal,
133 region_id: data_region_id,
134 payload: modified_batch,
135 raw_data: ArrowIpc {
136 schema,
137 data_header,
138 payload,
139 },
140 partition_expr_version,
141 aligned_schema_version: None,
142 };
143 self.data_region
144 .write_data(data_region_id, RegionRequest::BulkInserts(request))
145 .await
146 }
147
148 fn resolve_tag_columns_from_metadata(
149 &self,
150 logical_region_id: RegionId,
151 data_region_id: RegionId,
152 batch: &RecordBatch,
153 logical_metadata: &RegionMetadataRef,
154 ) -> Result<(Vec<TagColumnInfo>, Vec<usize>)> {
155 let tag_names: HashSet<&str> = logical_metadata
156 .column_metadatas
157 .iter()
158 .filter_map(|column| {
159 if column.semantic_type == SemanticType::Tag {
160 Some(column.column_schema.name.as_str())
161 } else {
162 None
163 }
164 })
165 .collect();
166
167 let mut tag_columns = Vec::new();
168 let mut non_tag_indices = Vec::new();
169 {
170 let state = self.state.read().unwrap();
171 let physical_columns = state
172 .physical_region_states()
173 .get(&data_region_id)
174 .context(error::PhysicalRegionNotFoundSnafu {
175 region_id: data_region_id,
176 })?
177 .physical_columns();
178
179 for (index, field) in batch.schema().fields().iter().enumerate() {
180 let name = field.name();
181 let column_id = physical_columns
182 .get(name)
183 .map(|info| info.column_id)
184 .with_context(|| error::ColumnNotFoundSnafu {
185 name: name.clone(),
186 region_id: logical_region_id,
187 })?;
188 if tag_names.contains(name.as_str()) {
189 tag_columns.push(TagColumnInfo {
190 name: name.clone(),
191 index,
192 column_id,
193 });
194 } else {
195 non_tag_indices.push(index);
196 }
197 }
198 }
199
200 tag_columns.sort_by(|a, b| a.name.cmp(&b.name));
201 Ok((tag_columns, non_tag_indices))
202 }
203}
204
205fn record_batch_to_ipc(record_batch: &RecordBatch) -> Result<(Bytes, Bytes, Bytes)> {
206 let mut encoder = FlightEncoder::default();
207 let schema = encoder.encode_schema(record_batch.schema().as_ref());
208 let mut iter = encoder
209 .encode(FlightMessage::RecordBatch(record_batch.clone()))
210 .into_iter();
211
212 let Some(flight_data) = iter.next() else {
213 return error::UnexpectedRequestSnafu {
214 reason: "Failed to encode empty flight data",
215 }
216 .fail();
217 };
218 ensure!(
219 iter.next().is_none(),
220 error::UnexpectedRequestSnafu {
221 reason: "Bulk insert RecordBatch with dictionary arrays is unsupported".to_string(),
222 }
223 );
224
225 Ok((
226 schema.data_header,
227 flight_data.data_header,
228 flight_data.data_body,
229 ))
230}
231
232#[cfg(test)]
233mod tests {
234 use std::assert_matches;
235 use std::sync::Arc;
236
237 use api::v1::ArrowIpc;
238 use common_error::ext::ErrorExt;
239 use common_query::prelude::{greptime_timestamp, greptime_value};
240 use common_recordbatch::RecordBatches;
241 use datafusion::parquet::basic::Encoding;
242 use datatypes::arrow::array::{Float64Array, StringArray, TimestampMillisecondArray};
243 use datatypes::arrow::datatypes::{DataType, Field, Schema as ArrowSchema, TimeUnit};
244 use datatypes::arrow::record_batch::RecordBatch;
245 use mito2::config::MitoConfig;
246 use mito2::sst::parquet::metadata::MetadataLoader;
247 use mito2::sst::parquet::reader::MetadataCacheMetrics;
248 use store_api::metric_engine_consts::{
249 METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY, PRIMARY_KEY_ENCODING,
250 };
251 use store_api::mito_engine_options::EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING;
252 use store_api::path_utils::table_dir;
253 use store_api::region_engine::RegionEngine;
254 use store_api::region_request::{
255 PathType, RegionBulkInsertsRequest, RegionCloseRequest, RegionFlushRequest,
256 RegionOpenRequest, RegionPutRequest, RegionRequest,
257 };
258 use store_api::storage::{RegionId, ScanRequest};
259
260 use super::record_batch_to_ipc;
261 use crate::batch_modifier::{TagColumnInfo, modify_batch_sparse};
262 use crate::engine::MetricEngine;
263 use crate::error::Error;
264 use crate::test_util::{self, TestEnv};
265
266 fn build_logical_batch(start: usize, rows: usize) -> RecordBatch {
267 let schema = Arc::new(ArrowSchema::new(vec![
268 Field::new(
269 greptime_timestamp(),
270 DataType::Timestamp(TimeUnit::Millisecond, None),
271 false,
272 ),
273 Field::new(greptime_value(), DataType::Float64, true),
274 Field::new("job", DataType::Utf8, true),
275 ]));
276
277 let mut ts = Vec::with_capacity(rows);
278 let mut values = Vec::with_capacity(rows);
279 let mut tags = Vec::with_capacity(rows);
280 for i in start..start + rows {
281 ts.push(i as i64);
282 values.push(i as f64);
283 tags.push("tag_0".to_string());
284 }
285
286 RecordBatch::try_new(
287 schema,
288 vec![
289 Arc::new(TimestampMillisecondArray::from(ts)),
290 Arc::new(Float64Array::from(values)),
291 Arc::new(StringArray::from(tags)),
292 ],
293 )
294 .unwrap()
295 }
296
297 fn build_bulk_request(
298 logical_region_id: RegionId,
299 batch: RecordBatch,
300 skip_wal: bool,
301 ) -> RegionRequest {
302 let (schema, data_header, payload) = record_batch_to_ipc(&batch).unwrap();
303 RegionRequest::BulkInserts(RegionBulkInsertsRequest {
304 skip_wal,
305 region_id: logical_region_id,
306 payload: batch,
307 raw_data: ArrowIpc {
308 schema,
309 data_header,
310 payload,
311 },
312 partition_expr_version: None,
313 aligned_schema_version: None,
314 })
315 }
316
317 #[tokio::test]
318 async fn test_bulk_insert_rejects_duplicate_tag() {
319 use common_error::status_code::StatusCode;
320
321 let env = TestEnv::new().await;
322 env.init_metric_region().await;
323 let logical_region_id = env.default_logical_region_id();
324
325 let batch = build_logical_batch(0, 2);
326 let mut fields = batch.schema().fields().to_vec();
327 let mut columns = batch.columns().to_vec();
328 fields.push(fields[2].clone());
329 columns.push(columns[2].clone());
330 let batch = RecordBatch::try_new(Arc::new(ArrowSchema::new(fields)), columns).unwrap();
331 let err = env
332 .metric()
333 .handle_request(
334 logical_region_id,
335 build_bulk_request(logical_region_id, batch, false),
336 )
337 .await
338 .unwrap_err();
339 assert_eq!(err.status_code(), StatusCode::InvalidArguments, "{err:?}");
340 }
341
342 #[tokio::test]
343 async fn test_bulk_insert_physical_region_rejects_malformed_primary_key() {
344 use common_error::status_code::StatusCode;
345 use datatypes::arrow::array::{ArrayRef, BinaryArray, Int64Array};
346 use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
347
348 let env = TestEnv::new().await;
349 env.init_metric_region().await;
350 let physical_region_id = env.default_physical_region_id();
351 let logical_region_id = env.default_logical_region_id();
352 let tag_columns = vec![TagColumnInfo {
353 name: "job".to_string(),
354 index: 2,
355 column_id: 2,
356 }];
357 let batch = modify_batch_sparse(
358 build_logical_batch(0, 2),
359 logical_region_id.table_id(),
360 &tag_columns,
361 &[0, 1],
362 )
363 .unwrap();
364 let primary_key_index = batch.schema().index_of(PRIMARY_KEY_COLUMN_NAME).unwrap();
365 let with_primary_key = |primary_key: Option<ArrayRef>, extra: Option<ArrayRef>| {
366 let mut fields = batch.schema().fields().to_vec();
367 let mut columns = batch.columns().to_vec();
368 fields.remove(primary_key_index);
369 columns.remove(primary_key_index);
370 for array in [primary_key, extra].into_iter().flatten() {
371 fields.push(Arc::new(Field::new(
372 PRIMARY_KEY_COLUMN_NAME,
373 array.data_type().clone(),
374 true,
375 )));
376 columns.push(array);
377 }
378 RecordBatch::try_new(Arc::new(ArrowSchema::new(fields)), columns).unwrap()
379 };
380 let encoded = batch.column(primary_key_index).clone();
381 let rejected = [
382 with_primary_key(None, None),
383 with_primary_key(Some(Arc::new(Int64Array::from(vec![1, 2]))), None),
384 with_primary_key(
385 Some(Arc::new(BinaryArray::from_opt_vec(vec![Some(b"a"), None]))),
386 None,
387 ),
388 with_primary_key(Some(encoded.clone()), Some(encoded)),
389 ];
390 for batch in rejected {
391 let err = env
392 .metric()
393 .handle_request(
394 physical_region_id,
395 build_bulk_request(physical_region_id, batch, false),
396 )
397 .await
398 .unwrap_err();
399 assert_eq!(err.status_code(), StatusCode::InvalidArguments, "{err:?}");
400 }
401 }
402
403 async fn init_dense_metric_region(env: &TestEnv) -> RegionId {
404 let physical_region_id = env.default_physical_region_id();
405 env.create_physical_region(
406 physical_region_id,
407 &TestEnv::default_table_dir(),
408 vec![(PRIMARY_KEY_ENCODING.to_string(), "dense".to_string())],
409 )
410 .await;
411
412 let logical_region_id = env.default_logical_region_id();
413 let request = test_util::create_logical_region_request(
414 &["job"],
415 physical_region_id,
416 &table_dir("test", logical_region_id.table_id()),
417 );
418 env.metric()
419 .handle_request(logical_region_id, RegionRequest::Create(request))
420 .await
421 .unwrap();
422 logical_region_id
423 }
424
425 #[tokio::test]
426 async fn test_bulk_insert_empty_batch_returns_zero() {
427 let env = TestEnv::new().await;
428 env.init_metric_region().await;
429 let logical_region_id = env.default_logical_region_id();
430
431 let batch = build_logical_batch(0, 0);
432 let request = RegionRequest::BulkInserts(RegionBulkInsertsRequest {
433 skip_wal: false,
434 region_id: logical_region_id,
435 payload: batch,
436 raw_data: ArrowIpc::default(),
437 partition_expr_version: None,
438 aligned_schema_version: None,
439 });
440 let response = env
441 .metric()
442 .handle_request(logical_region_id, request)
443 .await
444 .unwrap();
445 assert_eq!(response.affected_rows, 0);
446 }
447
448 #[tokio::test]
449 async fn test_bulk_insert_physical_region_passthrough() {
450 check_bulk_insert_physical_region_passthrough(false, false, false).await;
451 check_bulk_insert_physical_region_passthrough(true, false, false).await;
452 }
453
454 #[tokio::test]
455 async fn test_bulk_insert_physical_region_close_recovery() {
456 check_bulk_insert_physical_region_passthrough(false, true, false).await;
457 check_bulk_insert_physical_region_passthrough(true, true, false).await;
458 }
459
460 #[tokio::test]
461 async fn test_physical_bulk_partition_columns_preserve_stored_rows() {
462 for skip_wal in [false, true] {
463 let stripped =
464 check_bulk_insert_physical_region_passthrough(skip_wal, true, false).await;
465 let retained =
466 check_bulk_insert_physical_region_passthrough(skip_wal, true, true).await;
467 assert_eq!(retained, stripped, "skip_wal={skip_wal}");
468 }
469 }
470
471 async fn check_bulk_insert_physical_region_passthrough(
472 skip_wal: bool,
473 close: bool,
474 retain_partition_columns: bool,
475 ) -> String {
476 let mito_config = MitoConfig {
478 default_flat_format: true,
479 ..Default::default()
480 };
481 let mut env = TestEnv::with_mito_config("", mito_config, Default::default()).await;
482 env.init_metric_region().await;
483 env.metric().inner.flush_task.stop().await.unwrap();
484 let physical_region_id = env.default_physical_region_id();
485 let logical_region_id = env.default_logical_region_id();
486
487 let logical_batch = build_logical_batch(0, 3);
489 let logical_request = build_bulk_request(logical_region_id, logical_batch, skip_wal);
490 let response = env
491 .metric()
492 .handle_request(logical_region_id, logical_request)
493 .await
494 .unwrap();
495 assert_eq!(response.affected_rows, 3);
496
497 let tag_columns = vec![TagColumnInfo {
501 name: "job".to_string(),
502 index: 2,
503 column_id: 2, }];
505 let non_tag_indices = if retain_partition_columns {
506 vec![0, 1, 2] } else {
508 vec![0, 1] };
510 let second_batch = build_logical_batch(3, 3);
511 let physical_batch = modify_batch_sparse(
512 second_batch,
513 logical_region_id.table_id(),
514 &tag_columns,
515 &non_tag_indices,
516 )
517 .unwrap();
518 assert_eq!(
519 physical_batch.column_by_name("job").is_some(),
520 retain_partition_columns
521 );
522 let request = build_bulk_request(physical_region_id, physical_batch, skip_wal);
523 let response = env
524 .metric()
525 .handle_request(physical_region_id, request)
526 .await
527 .unwrap();
528 assert_eq!(response.affected_rows, 3);
529
530 let stream = env
532 .metric()
533 .scan_to_stream(logical_region_id, ScanRequest::default())
534 .await
535 .unwrap();
536 let batches = RecordBatches::try_collect(stream).await.unwrap();
537 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 6);
538
539 let stat = env.mito().region_statistic(physical_region_id).unwrap();
540 assert_eq!(stat.sst_num, 0);
541 let reopened = if close {
542 env.metric()
543 .handle_request(
544 physical_region_id,
545 RegionRequest::Close(RegionCloseRequest::default()),
546 )
547 .await
548 .unwrap();
549 MetricEngine::try_new(env.mito(), Default::default()).unwrap()
551 } else {
552 env.reopen_engine(Default::default()).await;
554 env.metric()
555 };
556 reopened.inner.flush_task.stop().await.unwrap();
557 reopened
558 .handle_request(
559 physical_region_id,
560 RegionRequest::Open(RegionOpenRequest {
561 engine: METRIC_ENGINE_NAME.to_string(),
562 table_dir: TestEnv::default_table_dir(),
563 path_type: PathType::Bare,
564 options: [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
565 .into_iter()
566 .collect(),
567 skip_wal_replay: false,
568 checkpoint: None,
569 requirements: Default::default(),
570 }),
571 )
572 .await
573 .unwrap();
574 let stat = env
575 .mito()
576 .region_statistic(crate::utils::to_data_region_id(physical_region_id))
577 .unwrap();
578 assert_eq!(stat.sst_num > 0, skip_wal && close);
579 let stream = reopened
580 .scan_to_stream(logical_region_id, ScanRequest::default())
581 .await
582 .unwrap();
583 let batches = RecordBatches::try_collect(stream).await.unwrap();
584 assert_eq!(
585 batches.iter().map(|b| b.num_rows()).sum::<usize>(),
586 if skip_wal && !close { 0 } else { 6 },
587 );
588 batches.pretty_print().unwrap()
589 }
590
591 #[tokio::test]
592 async fn test_bulk_insert_physical_region_empty_batch() {
593 let mito_config = MitoConfig {
595 default_flat_format: true,
596 ..Default::default()
597 };
598 let env = TestEnv::with_mito_config("", mito_config, Default::default()).await;
599 env.init_metric_region().await;
600 let physical_region_id = env.default_physical_region_id();
601
602 let batch = build_logical_batch(0, 0);
603 let request = build_bulk_request(physical_region_id, batch, false);
604 let response = env
605 .metric()
606 .handle_request(physical_region_id, request)
607 .await
608 .unwrap();
609 assert_eq!(response.affected_rows, 0);
610 }
611
612 #[tokio::test]
613 async fn test_bulk_insert_unknown_column_errors() {
614 let env = TestEnv::new().await;
615 env.init_metric_region().await;
616 let logical_region_id = env.default_logical_region_id();
617
618 let schema = Arc::new(ArrowSchema::new(vec![
619 Field::new(
620 greptime_timestamp(),
621 DataType::Timestamp(TimeUnit::Millisecond, None),
622 false,
623 ),
624 Field::new(greptime_value(), DataType::Float64, true),
625 Field::new("nonexistent_column", DataType::Utf8, true),
626 ]));
627 let batch = RecordBatch::try_new(
628 schema,
629 vec![
630 Arc::new(TimestampMillisecondArray::from(vec![0i64])),
631 Arc::new(Float64Array::from(vec![1.0])),
632 Arc::new(StringArray::from(vec!["val"])),
633 ],
634 )
635 .unwrap();
636
637 let request = build_bulk_request(logical_region_id, batch, false);
638 let err = env
639 .metric()
640 .handle_request(logical_region_id, request)
641 .await
642 .unwrap_err();
643 let Some(err) = err.as_any().downcast_ref::<Error>() else {
644 panic!("unexpected error type");
645 };
646 assert_matches!(err, Error::ColumnNotFound { .. });
647 }
648
649 #[tokio::test]
650 async fn test_bulk_insert_multiple_tag_columns() {
651 let env = TestEnv::new().await;
652 let physical_region_id = env.default_physical_region_id();
653 env.create_physical_region(physical_region_id, &TestEnv::default_table_dir(), vec![])
654 .await;
655 let logical_region_id = env.default_logical_region_id();
656 let request = test_util::create_logical_region_request(
657 &["host", "region"],
658 physical_region_id,
659 &table_dir("test", logical_region_id.table_id()),
660 );
661 env.metric()
662 .handle_request(logical_region_id, RegionRequest::Create(request))
663 .await
664 .unwrap();
665
666 let schema = Arc::new(ArrowSchema::new(vec![
667 Field::new(
668 greptime_timestamp(),
669 DataType::Timestamp(TimeUnit::Millisecond, None),
670 false,
671 ),
672 Field::new(greptime_value(), DataType::Float64, true),
673 Field::new("host", DataType::Utf8, true),
674 Field::new("region", DataType::Utf8, true),
675 ]));
676 let batch = RecordBatch::try_new(
677 schema,
678 vec![
679 Arc::new(TimestampMillisecondArray::from(vec![0i64, 1, 2])),
680 Arc::new(Float64Array::from(vec![10.0, 20.0, 30.0])),
681 Arc::new(StringArray::from(vec!["h1", "h2", "h1"])),
682 Arc::new(StringArray::from(vec!["us-east", "us-west", "eu-west"])),
683 ],
684 )
685 .unwrap();
686
687 let request = build_bulk_request(logical_region_id, batch, false);
688 let response = env
689 .metric()
690 .handle_request(logical_region_id, request)
691 .await
692 .unwrap();
693 assert_eq!(response.affected_rows, 3);
694
695 let stream = env
696 .metric()
697 .scan_to_stream(logical_region_id, ScanRequest::default())
698 .await
699 .unwrap();
700 let batches = RecordBatches::try_collect(stream).await.unwrap();
701 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 3);
702 }
703
704 #[tokio::test]
705 async fn test_bulk_insert_accumulates_rows() {
706 let env = TestEnv::new().await;
707 env.init_metric_region().await;
708 let logical_region_id = env.default_logical_region_id();
709
710 let request = build_bulk_request(logical_region_id, build_logical_batch(0, 3), false);
711 let response = env
712 .metric()
713 .handle_request(logical_region_id, request)
714 .await
715 .unwrap();
716 assert_eq!(response.affected_rows, 3);
717
718 let request = build_bulk_request(logical_region_id, build_logical_batch(3, 5), false);
719 let response = env
720 .metric()
721 .handle_request(logical_region_id, request)
722 .await
723 .unwrap();
724 assert_eq!(response.affected_rows, 5);
725
726 let stream = env
727 .metric()
728 .scan_to_stream(logical_region_id, ScanRequest::default())
729 .await
730 .unwrap();
731 let batches = RecordBatches::try_collect(stream).await.unwrap();
732 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 8);
733 }
734
735 #[tokio::test]
736 async fn test_bulk_insert_sparse_encoding() {
737 let env = TestEnv::new().await;
738 let physical_region_id = env.default_physical_region_id();
739 env.create_physical_region(
740 physical_region_id,
741 &TestEnv::default_table_dir(),
742 vec![(
743 EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING.to_string(),
744 "byte_stream_split".to_string(),
745 )],
746 )
747 .await;
748 let logical_region_id = env.default_logical_region_id();
749 env.create_logical_region(physical_region_id, logical_region_id)
750 .await;
751
752 let rows = 4;
753 let request = build_bulk_request(logical_region_id, build_logical_batch(0, rows), false);
754 let response = env
755 .metric()
756 .handle_request(logical_region_id, request)
757 .await
758 .unwrap();
759 assert_eq!(response.affected_rows, rows);
760
761 let data_region_id = crate::utils::to_data_region_id(physical_region_id);
762 env.mito()
763 .handle_request(
764 data_region_id,
765 RegionRequest::Flush(RegionFlushRequest::default()),
766 )
767 .await
768 .unwrap();
769
770 let region = env.mito().find_region(data_region_id).unwrap();
771 let entry = region
772 .manifest_sst_entries()
773 .await
774 .into_iter()
775 .find(|entry| entry.visible && entry.file_path.ends_with(".parquet"))
776 .unwrap();
777 let mut cache_metrics = MetadataCacheMetrics::default();
778 let footer = MetadataLoader::new(
779 region.access_layer().object_store().clone(),
780 &entry.file_path,
781 entry.file_size,
782 )
783 .load(&mut cache_metrics)
784 .await
785 .unwrap();
786 let field_column_index = footer
787 .file_metadata()
788 .schema_descr()
789 .columns()
790 .iter()
791 .position(|column| column.name() == greptime_value())
792 .unwrap();
793 assert!(!footer.row_groups().is_empty());
794 assert!(footer.row_groups().iter().all(|row_group| {
795 row_group
796 .column(field_column_index)
797 .encodings()
798 .any(|encoding| encoding == Encoding::BYTE_STREAM_SPLIT)
799 }));
800
801 let stream = env
802 .metric()
803 .scan_to_stream(logical_region_id, ScanRequest::default())
804 .await
805 .unwrap();
806 let batches = RecordBatches::try_collect(stream).await.unwrap();
807 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), rows);
808 }
809
810 #[tokio::test]
811 async fn test_bulk_insert_dense_encoding_rejected() {
812 let env = TestEnv::new().await;
813 let logical_region_id = init_dense_metric_region(&env).await;
814
815 let request = build_bulk_request(logical_region_id, build_logical_batch(0, 2), false);
816 let err = env
817 .metric()
818 .handle_request(logical_region_id, request)
819 .await
820 .unwrap_err();
821 let Some(err) = err.as_any().downcast_ref::<Error>() else {
822 panic!("unexpected error type");
823 };
824 assert_matches!(err, Error::UnsupportedRegionRequest { .. });
825 }
826
827 #[tokio::test]
828 async fn test_bulk_insert_matches_put() {
829 let env_put = TestEnv::new().await;
830 env_put.init_metric_region().await;
831 let logical_region_id = env_put.default_logical_region_id();
832 let schema = test_util::row_schema_with_tags(&["job"]);
833 let rows = test_util::build_rows(1, 5);
834 env_put
835 .metric()
836 .handle_request(
837 logical_region_id,
838 RegionRequest::Put(RegionPutRequest {
839 skip_wal: false,
840 rows: api::v1::Rows { schema, rows },
841 hint: None,
842 partition_expr_version: None,
843 }),
844 )
845 .await
846 .unwrap();
847 let put_stream = env_put
848 .metric()
849 .scan_to_stream(logical_region_id, ScanRequest::default())
850 .await
851 .unwrap();
852 let put_batches = RecordBatches::try_collect(put_stream).await.unwrap();
853 let put_output = put_batches.pretty_print().unwrap();
854
855 let env_bulk = TestEnv::new().await;
856 env_bulk.init_metric_region().await;
857 let request = build_bulk_request(logical_region_id, build_logical_batch(0, 5), false);
858 env_bulk
859 .metric()
860 .handle_request(logical_region_id, request)
861 .await
862 .unwrap();
863 let bulk_stream = env_bulk
864 .metric()
865 .scan_to_stream(logical_region_id, ScanRequest::default())
866 .await
867 .unwrap();
868 let bulk_batches = RecordBatches::try_collect(bulk_stream).await.unwrap();
869 let bulk_output = bulk_batches.pretty_print().unwrap();
870
871 assert_eq!(put_output, bulk_output);
872 }
873}