1use std::collections::HashMap;
16
17use api::helper::ColumnDataTypeWrapper;
18use api::v1::{
19 ColumnSchema, PrimaryKeyEncoding as PrimaryKeyEncodingProto, Row, Rows, SemanticType, Value,
20 WriteHint,
21};
22use common_telemetry::{error, info};
23use fxhash::FxHashMap;
24use snafu::{OptionExt, ResultExt, ensure};
25use store_api::codec::PrimaryKeyEncoding;
26use store_api::metadata::ColumnMetadata;
27use store_api::region_request::{
28 AffectedRows, RegionDeleteRequest, RegionPutRequest, RegionRequest,
29};
30use store_api::storage::{RegionId, TableId};
31
32use crate::engine::MetricEngineInner;
33use crate::error::{
34 ColumnNotFoundSnafu, CreateDefaultSnafu, ForbiddenPhysicalWriteSnafu, InvalidRequestSnafu,
35 LogicalRegionNotFoundSnafu, PhysicalRegionNotFoundSnafu, Result, UnexpectedRequestSnafu,
36 UnsupportedRegionRequestSnafu,
37};
38use crate::metrics::{FORBIDDEN_OPERATION_COUNT, MITO_OPERATION_ELAPSED};
39use crate::row_modifier::{RowsIter, TableIdInput};
40use crate::utils::to_data_region_id;
41
42impl MetricEngineInner {
43 pub async fn put_region(
45 &self,
46 region_id: RegionId,
47 request: RegionPutRequest,
48 ) -> Result<AffectedRows> {
49 let is_putting_physical_region =
50 self.state.read().unwrap().exist_physical_region(region_id);
51
52 if is_putting_physical_region {
53 info!(
54 "Metric region received put request {request:?} on physical region {region_id:?}"
55 );
56 FORBIDDEN_OPERATION_COUNT.inc();
57
58 ForbiddenPhysicalWriteSnafu.fail()
59 } else {
60 self.put_logical_region(region_id, request).await
61 }
62 }
63
64 pub async fn put_regions_batch(
74 &self,
75 requests: impl ExactSizeIterator<Item = (RegionId, RegionPutRequest)>,
76 ) -> Result<AffectedRows> {
77 let len = requests.len();
78
79 if len == 0 {
80 return Ok(0);
81 }
82
83 let _timer = MITO_OPERATION_ELAPSED
84 .with_label_values(&["put_batch"])
85 .start_timer();
86
87 if len == 1 {
89 let (region_id, req) = requests.into_iter().next().unwrap();
90 let is_putting_physical_region =
91 self.state.read().unwrap().exist_physical_region(region_id);
92 if is_putting_physical_region {
93 FORBIDDEN_OPERATION_COUNT.inc();
94 return ForbiddenPhysicalWriteSnafu.fail();
95 }
96
97 return self.put_logical_region(region_id, req).await;
98 }
99
100 let mut requests_per_physical: HashMap<RegionId, Vec<(RegionId, RegionPutRequest)>> =
101 HashMap::new();
102 for (region_id, request) in requests {
103 let is_putting_physical_region =
104 self.state.read().unwrap().exist_physical_region(region_id);
105 if is_putting_physical_region {
106 FORBIDDEN_OPERATION_COUNT.inc();
107 return ForbiddenPhysicalWriteSnafu.fail();
108 }
109 let physical_region_id = self.find_physical_region_id(region_id)?;
110 requests_per_physical
111 .entry(physical_region_id)
112 .or_default()
113 .push((region_id, request));
114 }
115
116 let mut total_affected_rows: AffectedRows = 0;
117 for (physical_region_id, requests) in requests_per_physical {
118 let affected_rows = self
119 .put_regions_batch_single_physical(physical_region_id, requests)
120 .await?;
121 total_affected_rows += affected_rows;
122 }
123
124 Ok(total_affected_rows)
125 }
126
127 async fn put_regions_batch_single_physical(
134 &self,
135 physical_region_id: RegionId,
136 mut requests: Vec<(RegionId, RegionPutRequest)>,
137 ) -> Result<AffectedRows> {
138 if requests.is_empty() {
139 return Ok(0);
140 }
141
142 let data_region_id = to_data_region_id(physical_region_id);
143 let primary_key_encoding = self.get_primary_key_encoding(data_region_id)?;
144
145 self.validate_batch_requests(physical_region_id, &mut requests)
149 .await?;
150
151 let (merged_request, total_affected_rows) = match primary_key_encoding {
153 PrimaryKeyEncoding::Sparse => self.merge_sparse_batch(physical_region_id, requests)?,
154 PrimaryKeyEncoding::Dense => self.merge_dense_batch(data_region_id, requests)?,
155 };
156
157 self.data_region
159 .write_data(data_region_id, RegionRequest::Put(merged_request))
160 .await?;
161
162 Ok(total_affected_rows)
163 }
164
165 fn get_primary_key_encoding(&self, data_region_id: RegionId) -> Result<PrimaryKeyEncoding> {
167 let state = self.state.read().unwrap();
168 state
169 .get_primary_key_encoding(data_region_id)
170 .context(PhysicalRegionNotFoundSnafu {
171 region_id: data_region_id,
172 })
173 }
174
175 async fn validate_batch_requests(
177 &self,
178 physical_region_id: RegionId,
179 requests: &mut [(RegionId, RegionPutRequest)],
180 ) -> Result<()> {
181 let skip_wal = requests
182 .first()
183 .is_some_and(|(_, request)| request.skip_wal);
184 ensure!(
185 requests
186 .iter()
187 .all(|(_, request)| request.skip_wal == skip_wal),
188 InvalidRequestSnafu {
189 region_id: physical_region_id,
190 reason: "inconsistent WAL policy in batch"
191 }
192 );
193 for (logical_region_id, request) in requests {
194 self.verify_rows(
195 *logical_region_id,
196 physical_region_id,
197 &mut request.rows,
198 true,
199 )
200 .await?;
201 }
202 Ok(())
203 }
204
205 fn merge_sparse_batch(
207 &self,
208 physical_region_id: RegionId,
209 requests: Vec<(RegionId, RegionPutRequest)>,
210 ) -> Result<(RegionPutRequest, AffectedRows)> {
211 let skip_wal = requests
212 .first()
213 .is_some_and(|(_, request)| request.skip_wal);
214 let total_rows: usize = requests.iter().map(|(_, req)| req.rows.rows.len()).sum();
215 let mut modified_requests = Vec::with_capacity(requests.len());
216 let mut total_affected_rows: AffectedRows = 0;
217 let mut merged_version: Option<u64> = None;
218
219 for (logical_region_id, mut request) in requests {
220 if let Some(request_version) = request.partition_expr_version {
221 if let Some(merged_version) = merged_version {
222 ensure!(
223 merged_version == request_version,
224 InvalidRequestSnafu {
225 region_id: physical_region_id,
226 reason: "inconsistent partition expr version in batch"
227 }
228 );
229 } else {
230 merged_version = Some(request_version);
231 }
232 }
233 self.modify_rows(
234 physical_region_id,
235 logical_region_id.table_id(),
236 &mut request.rows,
237 PrimaryKeyEncoding::Sparse,
238 )?;
239
240 let row_count = request.rows.rows.len();
241 total_affected_rows += row_count as AffectedRows;
242 modified_requests.push(request.rows);
243 }
244
245 let schema =
246 Self::build_union_schema(modified_requests.iter().map(|rows| rows.schema.as_slice()));
247 let mut merged_rows = Vec::with_capacity(total_rows);
248 for rows in modified_requests {
249 merged_rows.extend(Self::align_rows_to_schema(rows, &schema));
250 }
251
252 let merged_request = RegionPutRequest {
253 skip_wal,
254 rows: Rows {
255 schema,
256 rows: merged_rows,
257 },
258 hint: Some(WriteHint {
259 primary_key_encoding: PrimaryKeyEncodingProto::Sparse.into(),
260 }),
261 partition_expr_version: merged_version,
262 };
263
264 Ok((merged_request, total_affected_rows))
265 }
266
267 fn merge_dense_batch(
273 &self,
274 data_region_id: RegionId,
275 requests: Vec<(RegionId, RegionPutRequest)>,
276 ) -> Result<(RegionPutRequest, AffectedRows)> {
277 let skip_wal = requests
278 .first()
279 .is_some_and(|(_, request)| request.skip_wal);
280 let merged_schema =
282 Self::build_union_schema(requests.iter().map(|(_, req)| req.rows.schema.as_slice()));
283
284 let (merged_rows, table_ids, merged_version) =
286 Self::align_requests_to_schema(requests, &merged_schema)?;
287
288 let final_rows = {
290 let state = self.state.read().unwrap();
291 let physical_columns = state
292 .physical_region_states()
293 .get(&data_region_id)
294 .with_context(|| PhysicalRegionNotFoundSnafu {
295 region_id: data_region_id,
296 })?
297 .physical_columns();
298
299 let iter = RowsIter::new(
300 Rows {
301 schema: merged_schema,
302 rows: merged_rows,
303 },
304 physical_columns,
305 );
306
307 self.row_modifier.modify_rows(
308 iter,
309 TableIdInput::Batch(&table_ids),
310 PrimaryKeyEncoding::Dense,
311 )?
312 };
313
314 let merged_request = RegionPutRequest {
315 skip_wal,
316 rows: final_rows,
317 hint: None,
318 partition_expr_version: merged_version,
319 };
320
321 Ok((merged_request, table_ids.len() as AffectedRows))
322 }
323
324 fn build_union_schema<'a>(
325 schemas: impl IntoIterator<Item = &'a [ColumnSchema]>,
326 ) -> Vec<ColumnSchema> {
327 let mut schema = Vec::new();
328 for columns in schemas {
329 for col in columns {
330 if !schema
331 .iter()
332 .any(|existing: &ColumnSchema| existing.column_name == col.column_name)
333 {
334 schema.push(col.clone());
335 }
336 }
337 }
338 schema
339 }
340
341 fn align_requests_to_schema(
342 requests: Vec<(RegionId, RegionPutRequest)>,
343 merged_schema: &[ColumnSchema],
344 ) -> Result<(Vec<Row>, Vec<TableId>, Option<u64>)> {
345 let total_rows: usize = requests.iter().map(|(_, req)| req.rows.rows.len()).sum();
347 let mut merged_rows = Vec::with_capacity(total_rows);
348 let mut table_ids = Vec::with_capacity(total_rows);
349 let mut merged_version: Option<u64> = None;
350
351 for (logical_region_id, request) in requests {
352 if let Some(request_version) = request.partition_expr_version {
353 if let Some(merged_version) = merged_version {
354 ensure!(
355 merged_version == request_version,
356 InvalidRequestSnafu {
357 region_id: logical_region_id,
358 reason: "inconsistent partition expr version in batch"
359 }
360 );
361 } else {
362 merged_version = Some(request_version);
363 }
364 }
365 let table_id = logical_region_id.table_id();
366 let row_count = request.rows.rows.len();
367 merged_rows.extend(Self::align_rows_to_schema(request.rows, merged_schema));
368 table_ids.extend(std::iter::repeat_n(table_id, row_count));
369 }
370
371 Ok((merged_rows, table_ids, merged_version))
372 }
373
374 fn align_rows_to_schema(rows: Rows, merged_schema: &[ColumnSchema]) -> Vec<Row> {
375 let Rows { schema, rows } = rows;
376 if schema.len() == merged_schema.len()
377 && schema
378 .iter()
379 .zip(merged_schema)
380 .all(|(left, right)| left.column_name == right.column_name)
381 {
382 return rows;
383 }
384
385 let col_name_to_idx: FxHashMap<&str, usize> = schema
386 .iter()
387 .enumerate()
388 .map(|(idx, col)| (col.column_name.as_str(), idx))
389 .collect();
390 let col_mapping: Vec<Option<usize>> = merged_schema
391 .iter()
392 .map(|merged_col| {
393 col_name_to_idx
394 .get(merged_col.column_name.as_str())
395 .copied()
396 })
397 .collect();
398 let null_value = Value { value_data: None };
399
400 rows.into_iter()
401 .map(|mut row| {
402 let values = col_mapping
403 .iter()
404 .map(|opt_idx| match opt_idx {
405 Some(idx) => std::mem::take(&mut row.values[*idx]),
406 None => null_value.clone(),
407 })
408 .collect();
409 Row { values }
410 })
411 .collect()
412 }
413
414 fn find_physical_region_id(&self, logical_region_id: RegionId) -> Result<RegionId> {
416 let state = self.state.read().unwrap();
417 state
418 .logical_regions()
419 .get(&logical_region_id)
420 .copied()
421 .context(LogicalRegionNotFoundSnafu {
422 region_id: logical_region_id,
423 })
424 }
425
426 pub async fn delete_region(
428 &self,
429 region_id: RegionId,
430 request: RegionDeleteRequest,
431 ) -> Result<AffectedRows> {
432 if self.is_physical_region(region_id) {
433 info!(
434 "Metric region received delete request {request:?} on physical region {region_id:?}"
435 );
436 FORBIDDEN_OPERATION_COUNT.inc();
437
438 UnsupportedRegionRequestSnafu {
439 request: RegionRequest::Delete(request),
440 }
441 .fail()
442 } else {
443 self.delete_logical_region(region_id, request).await
444 }
445 }
446
447 async fn put_logical_region(
448 &self,
449 logical_region_id: RegionId,
450 mut request: RegionPutRequest,
451 ) -> Result<AffectedRows> {
452 let _timer = MITO_OPERATION_ELAPSED
453 .with_label_values(&["put"])
454 .start_timer();
455
456 let (physical_region_id, data_region_id, primary_key_encoding) =
457 self.find_data_region_meta(logical_region_id)?;
458
459 self.verify_rows(
460 logical_region_id,
461 physical_region_id,
462 &mut request.rows,
463 true,
464 )
465 .await?;
466
467 self.modify_rows(
470 physical_region_id,
471 logical_region_id.table_id(),
472 &mut request.rows,
473 primary_key_encoding,
474 )?;
475 if primary_key_encoding == PrimaryKeyEncoding::Sparse {
476 request.hint = Some(WriteHint {
477 primary_key_encoding: PrimaryKeyEncodingProto::Sparse.into(),
478 });
479 }
480 self.data_region
481 .write_data(data_region_id, RegionRequest::Put(request))
482 .await
483 }
484
485 async fn delete_logical_region(
486 &self,
487 logical_region_id: RegionId,
488 mut request: RegionDeleteRequest,
489 ) -> Result<AffectedRows> {
490 let _timer = MITO_OPERATION_ELAPSED
491 .with_label_values(&["delete"])
492 .start_timer();
493
494 let (physical_region_id, data_region_id, primary_key_encoding) =
495 self.find_data_region_meta(logical_region_id)?;
496
497 self.verify_rows(
498 logical_region_id,
499 physical_region_id,
500 &mut request.rows,
501 false,
502 )
503 .await?;
504
505 self.modify_rows(
508 physical_region_id,
509 logical_region_id.table_id(),
510 &mut request.rows,
511 primary_key_encoding,
512 )?;
513 if primary_key_encoding == PrimaryKeyEncoding::Sparse {
514 request.hint = Some(WriteHint {
515 primary_key_encoding: PrimaryKeyEncodingProto::Sparse.into(),
516 });
517 }
518 self.data_region
519 .write_data(data_region_id, RegionRequest::Delete(request))
520 .await
521 }
522
523 pub(crate) fn find_data_region_meta(
524 &self,
525 logical_region_id: RegionId,
526 ) -> Result<(RegionId, RegionId, PrimaryKeyEncoding)> {
527 let state = self.state.read().unwrap();
528 let physical_region_id = *state
529 .logical_regions()
530 .get(&logical_region_id)
531 .with_context(|| LogicalRegionNotFoundSnafu {
532 region_id: logical_region_id,
533 })?;
534 let data_region_id = to_data_region_id(physical_region_id);
535 let primary_key_encoding = state.get_primary_key_encoding(data_region_id).context(
536 PhysicalRegionNotFoundSnafu {
537 region_id: data_region_id,
538 },
539 )?;
540 Ok((physical_region_id, data_region_id, primary_key_encoding))
541 }
542
543 async fn verify_rows(
554 &self,
555 logical_region_id: RegionId,
556 physical_region_id: RegionId,
557 rows: &mut Rows,
558 check_fields: bool,
559 ) -> Result<()> {
560 let data_region_id = to_data_region_id(physical_region_id);
562 let (physical_columns, ts_name) = {
563 let state = self.state.read().unwrap();
564 if !state.is_logical_region_exist(logical_region_id) {
565 error!("Trying to write to an nonexistent region {logical_region_id}");
566 return LogicalRegionNotFoundSnafu {
567 region_id: logical_region_id,
568 }
569 .fail();
570 }
571
572 let physical_state = state
573 .physical_region_states()
574 .get(&data_region_id)
575 .context(PhysicalRegionNotFoundSnafu {
576 region_id: data_region_id,
577 })?;
578 (
579 physical_state.physical_columns_snapshot(),
580 physical_state.time_index_column_name().to_string(),
581 )
582 };
583
584 for col in &rows.schema {
586 let info = physical_columns
587 .get(&col.column_name)
588 .context(ColumnNotFoundSnafu {
589 name: &col.column_name,
590 region_id: logical_region_id,
591 })?;
592
593 ensure!(
594 api::helper::is_column_type_value_eq(
595 col.datatype,
596 col.datatype_extension.clone(),
597 &info.column_schema.data_type
598 ),
599 InvalidRequestSnafu {
600 region_id: logical_region_id,
601 reason: format!(
602 "column {} expect type {:?}, given: {}({})",
603 col.column_name,
604 info.column_schema.data_type,
605 api::v1::ColumnDataType::try_from(col.datatype)
606 .map(|v| v.as_str_name())
607 .unwrap_or("Unknown"),
608 col.datatype,
609 ),
610 }
611 );
612
613 ensure!(
614 api::helper::is_semantic_type_eq(col.semantic_type, info.semantic_type),
615 InvalidRequestSnafu {
616 region_id: logical_region_id,
617 reason: format!(
618 "column {} expect semantic type {:?}, given: {}({})",
619 col.column_name,
620 info.semantic_type,
621 api::v1::SemanticType::try_from(col.semantic_type)
622 .map(|v| v.as_str_name())
623 .unwrap_or("Unknown"),
624 col.semantic_type,
625 ),
626 }
627 );
628 }
629
630 ensure!(
631 rows.schema.iter().any(|col| col.column_name == ts_name),
632 InvalidRequestSnafu {
633 region_id: logical_region_id,
634 reason: format!("missing required time index column {ts_name}"),
635 }
636 );
637
638 let logical_columns = self
639 .load_logical_columns(physical_region_id, logical_region_id)
640 .await?;
641 let logical_fields = logical_columns
642 .iter()
643 .filter(|col| col.semantic_type == SemanticType::Field)
644 .map(|col| (col.column_schema.name.as_str(), col))
645 .collect::<HashMap<_, _>>();
646
647 for col in &rows.schema {
648 if api::helper::is_semantic_type_eq(col.semantic_type, SemanticType::Field) {
649 ensure!(
650 logical_fields.contains_key(col.column_name.as_str()),
651 InvalidRequestSnafu {
652 region_id: logical_region_id,
653 reason: format!(
654 "field column {} does not belong to logical region {logical_region_id}",
655 col.column_name,
656 ),
657 }
658 );
659 }
660 }
661
662 if check_fields {
663 for (field_name, field_meta) in logical_fields {
666 if !rows.schema.iter().any(|col| col.column_name == field_name) {
667 Self::fill_missing_field_column(
668 logical_region_id,
669 field_name,
670 field_meta,
671 rows,
672 )?;
673 }
674 }
675
676 for (field_name, field_meta) in physical_columns
677 .iter()
678 .filter(|(_, col)| col.semantic_type == SemanticType::Field)
679 {
680 if !rows.schema.iter().any(|col| col.column_name == *field_name) {
681 Self::fill_missing_field_column(
682 logical_region_id,
683 field_name,
684 field_meta,
685 rows,
686 )?;
687 }
688 }
689 }
690
691 Ok(())
692 }
693
694 fn fill_missing_field_column(
695 logical_region_id: RegionId,
696 field_name: &str,
697 field_meta: &ColumnMetadata,
698 rows: &mut Rows,
699 ) -> Result<()> {
700 ensure!(
703 !field_meta.column_schema.is_default_impure(),
704 UnexpectedRequestSnafu {
705 reason: format!(
706 "unexpected impure default value with region_id: {logical_region_id}, column: {field_name}, default_value: {:?}",
707 field_meta.column_schema.default_constraint(),
708 ),
709 }
710 );
711
712 let default_value = field_meta
713 .column_schema
714 .create_default()
715 .context(CreateDefaultSnafu {
716 region_id: logical_region_id,
717 column: field_name,
718 })?
719 .with_context(|| InvalidRequestSnafu {
720 region_id: logical_region_id,
721 reason: format!("missing required field column {field_name}"),
722 })?;
723 let default_value = api::helper::to_grpc_value(default_value);
724 let (datatype, datatype_extension) =
725 ColumnDataTypeWrapper::try_from(field_meta.column_schema.data_type.clone())
726 .map_err(|e| {
727 InvalidRequestSnafu {
728 region_id: logical_region_id,
729 reason: format!(
730 "no protobuf type for field column {field_name} ({:?}): {e}",
731 field_meta.column_schema.data_type
732 ),
733 }
734 .build()
735 })?
736 .to_parts();
737
738 rows.schema.push(ColumnSchema {
739 column_name: field_name.to_string(),
740 datatype: datatype as i32,
741 semantic_type: SemanticType::Field as i32,
742 datatype_extension,
743 options: None,
744 });
745
746 for row in &mut rows.rows {
747 row.values.push(default_value.clone());
748 }
749
750 Ok(())
751 }
752
753 fn modify_rows(
757 &self,
758 physical_region_id: RegionId,
759 table_id: TableId,
760 rows: &mut Rows,
761 encoding: PrimaryKeyEncoding,
762 ) -> Result<()> {
763 let input = std::mem::take(rows);
764 let iter = {
765 let state = self.state.read().unwrap();
766 let physical_columns = state
767 .physical_region_states()
768 .get(&physical_region_id)
769 .with_context(|| PhysicalRegionNotFoundSnafu {
770 region_id: physical_region_id,
771 })?
772 .physical_columns();
773 RowsIter::new(input, physical_columns)
774 };
775 let output =
776 self.row_modifier
777 .modify_rows(iter, TableIdInput::Single(table_id), encoding)?;
778 *rows = output;
779 Ok(())
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use std::collections::HashSet;
786
787 use api::v1::value::ValueData;
788 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema};
789 use common_error::ext::ErrorExt;
790 use common_error::status_code::StatusCode;
791 use common_function::utils::partition_expr_version;
792 use common_query::prelude::{greptime_native_histogram, greptime_timestamp, greptime_value};
793 use common_recordbatch::RecordBatches;
794 use datatypes::arrow::array::{Float64Array, TimestampMillisecondArray};
795 use datatypes::prelude::ConcreteDataType;
796 use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema};
797 use datatypes::value::Value as PartitionValue;
798 use partition::expr::col;
799 use store_api::metadata::ColumnMetadata;
800 use store_api::metric_engine_consts::{
801 DATA_SCHEMA_TABLE_ID_COLUMN_NAME, DATA_SCHEMA_TSID_COLUMN_NAME, METRIC_ENGINE_NAME,
802 PHYSICAL_TABLE_METADATA_KEY, PRIMARY_KEY_ENCODING,
803 };
804 use store_api::path_utils::table_dir;
805 use store_api::region_engine::RegionEngine;
806 use store_api::region_request::{
807 EnterStagingRequest, PathType, RegionCloseRequest, RegionOpenRequest, RegionRequest,
808 StagingPartitionDirective,
809 };
810 use store_api::storage::ScanRequest;
811 use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
812
813 use super::*;
814 use crate::engine::MetricEngine;
815 use crate::test_util::{self, TestEnv};
816
817 async fn scan_timestamp_values(engine: &MetricEngine, region_id: RegionId) -> Vec<(i64, f64)> {
818 let stream = engine
819 .scan_to_stream(region_id, ScanRequest::default())
820 .await
821 .unwrap();
822 let batches = RecordBatches::try_collect(stream).await.unwrap();
823 let mut rows = Vec::new();
824 for batch in batches.iter() {
825 let batch = batch.df_record_batch();
826 let timestamp_index = batch.schema().index_of(greptime_timestamp()).unwrap();
827 let value_index = batch.schema().index_of(greptime_value()).unwrap();
828 let timestamps = batch
829 .column(timestamp_index)
830 .as_any()
831 .downcast_ref::<TimestampMillisecondArray>()
832 .unwrap();
833 let values = batch
834 .column(value_index)
835 .as_any()
836 .downcast_ref::<Float64Array>()
837 .unwrap();
838 rows.extend(
839 timestamps
840 .values()
841 .iter()
842 .copied()
843 .zip(values.values().iter().copied()),
844 );
845 }
846 rows.sort_unstable_by_key(|(timestamp, _)| *timestamp);
847 rows
848 }
849
850 #[tokio::test]
851 async fn test_batch_partition_versions() {
852 check_batch_partition_versions("sparse").await;
853 check_batch_partition_versions("dense").await;
854 }
855
856 async fn check_batch_partition_versions(encoding: &str) {
857 let env = TestEnv::new().await;
858 let physical_region_id = env.default_physical_region_id();
859 let logical_region_id = env.default_logical_region_id();
860 env.create_physical_region(
861 physical_region_id,
862 &TestEnv::default_table_dir(),
863 vec![(PRIMARY_KEY_ENCODING.to_string(), encoding.to_string())],
864 )
865 .await;
866 create_logical_region_with_tags(&env, physical_region_id, logical_region_id, &["job"])
867 .await;
868 let build_requests = |versions: [Option<u64>; 3]| {
869 versions
870 .into_iter()
871 .map(|partition_expr_version| {
872 (
873 logical_region_id,
874 RegionPutRequest {
875 skip_wal: false,
876 rows: Rows {
877 schema: test_util::row_schema_with_tags(&["job"]),
878 rows: test_util::build_rows(1, 1),
879 },
880 hint: None,
881 partition_expr_version,
882 },
883 )
884 })
885 .collect::<Vec<_>>()
886 };
887 let err = env
889 .metric()
890 .inner
891 .put_regions_batch_single_physical(
892 physical_region_id,
893 build_requests([None, Some(10), Some(11)]),
894 )
895 .await
896 .unwrap_err();
897 assert!(
898 err.to_string()
899 .contains("inconsistent partition expr version")
900 );
901 assert!(
902 scan_timestamp_values(&env.metric(), logical_region_id)
903 .await
904 .is_empty()
905 );
906
907 for (versions, expected) in [
908 ([None, None, None], None),
909 ([None, Some(7), None], Some(7)),
910 ([Some(7), None, Some(7)], Some(7)),
911 ] {
912 let mut requests = build_requests(versions);
913 let engine = env.metric();
914 engine
915 .inner
916 .validate_batch_requests(physical_region_id, &mut requests)
917 .await
918 .unwrap();
919 let (merged, _) = match encoding {
920 "sparse" => engine
921 .inner
922 .merge_sparse_batch(physical_region_id, requests),
923 "dense" => engine
924 .inner
925 .merge_dense_batch(to_data_region_id(physical_region_id), requests),
926 _ => unreachable!(),
927 }
928 .unwrap();
929 assert_eq!(merged.partition_expr_version, expected);
930 }
931 }
932
933 #[tokio::test]
934 async fn test_put_skip_wal_batch_recovery() {
935 check_put_skip_wal_batch_recovery("sparse", false).await;
936 check_put_skip_wal_batch_recovery("sparse", true).await;
937 check_put_skip_wal_batch_recovery("dense", false).await;
938 check_put_skip_wal_batch_recovery("dense", true).await;
939 }
940
941 async fn check_put_skip_wal_batch_recovery(encoding: &str, skip_wal: bool) {
942 let env = TestEnv::new().await;
943 let engine = env.metric();
944 engine.inner.flush_task.stop().await.unwrap();
945 let physical_region_id = env.default_physical_region_id();
946 let logical_region_id = env.default_logical_region_id();
947 env.create_physical_region(
948 physical_region_id,
949 &TestEnv::default_table_dir(),
950 vec![(PRIMARY_KEY_ENCODING.to_string(), encoding.to_string())],
951 )
952 .await;
953 create_logical_region_with_tags(&env, physical_region_id, logical_region_id, &["job"])
954 .await;
955 let metadata_before = engine.get_metadata(logical_region_id).await.unwrap();
956
957 let requests = [skip_wal; 3]
958 .into_iter()
959 .enumerate()
960 .map(|(index, skip_wal)| {
961 let timestamp = index as i64 + 1;
962 let value = timestamp as f64 * 10.0;
963 let rows = [0, timestamp]
966 .into_iter()
967 .map(|timestamp| Row {
968 values: vec![
969 Value {
970 value_data: Some(ValueData::TimestampMillisecondValue(timestamp)),
971 },
972 Value {
973 value_data: Some(ValueData::F64Value(value)),
974 },
975 Value {
976 value_data: Some(ValueData::StringValue("tag_0".to_string())),
977 },
978 ],
979 })
980 .collect();
981 (
982 logical_region_id,
983 RegionPutRequest {
984 rows: Rows {
985 schema: test_util::row_schema_with_tags(&["job"]),
986 rows,
987 },
988 hint: None,
989 partition_expr_version: None,
990 skip_wal,
991 },
992 )
993 });
994 let affected_rows = engine.inner.put_regions_batch(requests).await.unwrap();
995 assert_eq!(affected_rows, 6);
996 assert_eq!(
997 scan_timestamp_values(&engine, logical_region_id).await,
998 vec![(0, 30.0), (1, 10.0), (2, 20.0), (3, 30.0)]
999 );
1000
1001 for region_id in [
1003 to_data_region_id(physical_region_id),
1004 crate::utils::to_metadata_region_id(physical_region_id),
1005 ] {
1006 let stat = env.mito().region_statistic(region_id).unwrap();
1007 assert!(stat.memtable_size > 0);
1008 assert_eq!(stat.sst_num, 0);
1009 }
1010 engine
1011 .handle_request(
1012 physical_region_id,
1013 RegionRequest::Close(RegionCloseRequest {
1014 flush_on_close: false,
1015 }),
1016 )
1017 .await
1018 .unwrap();
1019
1020 let reopened = MetricEngine::try_new(env.mito(), Default::default()).unwrap();
1022 reopened.inner.flush_task.stop().await.unwrap();
1023 reopened
1024 .handle_request(
1025 physical_region_id,
1026 RegionRequest::Open(RegionOpenRequest {
1027 engine: METRIC_ENGINE_NAME.to_string(),
1028 table_dir: TestEnv::default_table_dir(),
1029 path_type: PathType::Bare,
1030 options: [
1031 (PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new()),
1032 (PRIMARY_KEY_ENCODING.to_string(), encoding.to_string()),
1033 ]
1034 .into_iter()
1035 .collect(),
1036 skip_wal_replay: false,
1037 checkpoint: None,
1038 requirements: Default::default(),
1039 }),
1040 )
1041 .await
1042 .unwrap();
1043 let recovered_metadata = reopened.get_metadata(logical_region_id).await.unwrap();
1044 assert_eq!(
1045 metadata_before.column_metadatas,
1046 recovered_metadata.column_metadatas
1047 );
1048 let expected = if skip_wal {
1049 vec![]
1050 } else {
1051 vec![(0, 30.0), (1, 10.0), (2, 20.0), (3, 30.0)]
1052 };
1053 assert_eq!(
1054 scan_timestamp_values(&reopened, logical_region_id).await,
1055 expected,
1056 "encoding={encoding}, skip_wal={skip_wal}"
1057 );
1058 }
1059
1060 fn assert_merged_schema(rows: &Rows, expect_sparse: bool) {
1061 let column_names: HashSet<String> = rows
1062 .schema
1063 .iter()
1064 .map(|col| col.column_name.clone())
1065 .collect();
1066
1067 if expect_sparse {
1068 assert!(
1069 column_names.contains(PRIMARY_KEY_COLUMN_NAME),
1070 "sparse encoding should include primary key column"
1071 );
1072 assert!(
1073 !column_names.contains(DATA_SCHEMA_TABLE_ID_COLUMN_NAME),
1074 "sparse encoding should not include table id column"
1075 );
1076 assert!(
1077 !column_names.contains(DATA_SCHEMA_TSID_COLUMN_NAME),
1078 "sparse encoding should not include tsid column"
1079 );
1080 assert!(
1081 !column_names.contains("job"),
1082 "sparse encoding should not include tag columns"
1083 );
1084 assert!(
1085 !column_names.contains("instance"),
1086 "sparse encoding should not include tag columns"
1087 );
1088 } else {
1089 assert!(
1090 !column_names.contains(PRIMARY_KEY_COLUMN_NAME),
1091 "dense encoding should not include primary key column"
1092 );
1093 assert!(
1094 column_names.contains(DATA_SCHEMA_TABLE_ID_COLUMN_NAME),
1095 "dense encoding should include table id column"
1096 );
1097 assert!(
1098 column_names.contains(DATA_SCHEMA_TSID_COLUMN_NAME),
1099 "dense encoding should include tsid column"
1100 );
1101 assert!(
1102 column_names.contains("job"),
1103 "dense encoding should keep tag columns"
1104 );
1105 assert!(
1106 column_names.contains("instance"),
1107 "dense encoding should keep tag columns"
1108 );
1109 }
1110 }
1111
1112 fn job_partition_expr_json() -> String {
1113 let expr = col("job")
1114 .gt_eq(PartitionValue::String("job-0".into()))
1115 .and(col("job").lt(PartitionValue::String("job-9".into())));
1116 expr.as_json_str().unwrap()
1117 }
1118
1119 async fn create_logical_region_with_tags(
1120 env: &TestEnv,
1121 physical_region_id: RegionId,
1122 logical_region_id: RegionId,
1123 tags: &[&str],
1124 ) {
1125 let region_create_request = test_util::create_logical_region_request(
1126 tags,
1127 physical_region_id,
1128 &table_dir("test", logical_region_id.table_id()),
1129 );
1130 env.metric()
1131 .handle_request(
1132 logical_region_id,
1133 RegionRequest::Create(region_create_request),
1134 )
1135 .await
1136 .unwrap();
1137 }
1138
1139 fn column_index(rows: &Rows, name: &str) -> usize {
1140 rows.schema
1141 .iter()
1142 .position(|col| col.column_name == name)
1143 .unwrap()
1144 }
1145
1146 fn check_batch_merge_wal_policy(
1147 env: &TestEnv,
1148 physical_region_id: RegionId,
1149 mut requests: Vec<(RegionId, RegionPutRequest)>,
1150 expect_sparse: bool,
1151 skip_wal: bool,
1152 ) {
1153 for (_, request) in &mut requests {
1154 request.skip_wal = skip_wal;
1155 }
1156 let (merged_request, affected_rows) = if expect_sparse {
1157 let (merged_request, affected_rows) = env
1158 .metric()
1159 .inner
1160 .merge_sparse_batch(physical_region_id, requests)
1161 .unwrap();
1162 let hint = merged_request
1163 .hint
1164 .as_ref()
1165 .expect("missing sparse write hint");
1166 assert_eq!(
1167 hint.primary_key_encoding,
1168 PrimaryKeyEncodingProto::Sparse as i32
1169 );
1170 (merged_request, affected_rows)
1171 } else {
1172 let (merged_request, affected_rows) = env
1173 .metric()
1174 .inner
1175 .merge_dense_batch(to_data_region_id(physical_region_id), requests)
1176 .unwrap();
1177 assert!(merged_request.hint.is_none());
1178 (merged_request, affected_rows)
1179 };
1180 assert_merged_schema(&merged_request.rows, expect_sparse);
1181 assert_eq!(merged_request.skip_wal, skip_wal);
1182 assert_eq!(affected_rows, 5);
1183 }
1184
1185 async fn run_batch_write_with_schema_variants(
1186 env: &TestEnv,
1187 physical_region_id: RegionId,
1188 options: Vec<(String, String)>,
1189 expect_sparse: bool,
1190 ) {
1191 env.create_physical_region(physical_region_id, &TestEnv::default_table_dir(), options)
1192 .await;
1193
1194 let logical_region_1 = env.default_logical_region_id();
1195 let logical_region_2 = RegionId::new(1024, 1);
1196
1197 create_logical_region_with_tags(env, physical_region_id, logical_region_1, &["job"]).await;
1198 create_logical_region_with_tags(
1199 env,
1200 physical_region_id,
1201 logical_region_2,
1202 &["job", "instance"],
1203 )
1204 .await;
1205
1206 let schema_1 = test_util::row_schema_with_tags(&["job"]);
1207 let schema_2 = test_util::row_schema_with_tags(&["job", "instance"]);
1208
1209 let data_region_id = RegionId::new(physical_region_id.table_id(), 2);
1210 let primary_key_encoding = env
1211 .metric()
1212 .inner
1213 .get_primary_key_encoding(data_region_id)
1214 .unwrap();
1215 assert_eq!(
1216 primary_key_encoding,
1217 if expect_sparse {
1218 PrimaryKeyEncoding::Sparse
1219 } else {
1220 PrimaryKeyEncoding::Dense
1221 }
1222 );
1223
1224 let build_requests = || {
1225 let rows_1 = test_util::build_rows(1, 3);
1226 let rows_2 = test_util::build_rows(2, 2);
1227
1228 vec![
1229 (
1230 logical_region_1,
1231 RegionPutRequest {
1232 skip_wal: false,
1233 rows: Rows {
1234 schema: schema_1.clone(),
1235 rows: rows_1,
1236 },
1237 hint: None,
1238 partition_expr_version: None,
1239 },
1240 ),
1241 (
1242 logical_region_2,
1243 RegionPutRequest {
1244 skip_wal: false,
1245 rows: Rows {
1246 schema: schema_2.clone(),
1247 rows: rows_2,
1248 },
1249 hint: None,
1250 partition_expr_version: None,
1251 },
1252 ),
1253 ]
1254 };
1255
1256 check_batch_merge_wal_policy(
1257 env,
1258 physical_region_id,
1259 build_requests(),
1260 expect_sparse,
1261 false,
1262 );
1263 check_batch_merge_wal_policy(
1264 env,
1265 physical_region_id,
1266 build_requests(),
1267 expect_sparse,
1268 true,
1269 );
1270
1271 for policies in [[false, true], [true, false]] {
1272 let mut mixed_requests = build_requests();
1273 for ((_, request), skip_wal) in mixed_requests.iter_mut().zip(policies) {
1274 request.skip_wal = skip_wal;
1275 }
1276 let err = env
1277 .metric()
1278 .inner
1279 .put_regions_batch(mixed_requests.into_iter())
1280 .await
1281 .unwrap_err();
1282 assert!(err.to_string().contains("inconsistent WAL policy in batch"));
1283 for logical_region_id in [logical_region_1, logical_region_2] {
1284 assert!(
1285 scan_timestamp_values(&env.metric(), logical_region_id)
1286 .await
1287 .is_empty()
1288 );
1289 }
1290 }
1291
1292 let affected_rows = env
1293 .metric()
1294 .inner
1295 .put_regions_batch(build_requests().into_iter())
1296 .await
1297 .unwrap();
1298 assert_eq!(affected_rows, 5);
1299
1300 let request = ScanRequest::default();
1301 let stream = env
1302 .mito()
1303 .scan_to_stream(data_region_id, request)
1304 .await
1305 .unwrap();
1306 let batches = RecordBatches::try_collect(stream).await.unwrap();
1307
1308 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 5);
1309 }
1310
1311 #[test]
1312 fn test_sparse_batch_aligns_mixed_field_order() {
1313 let primary_key = PbColumnSchema {
1314 column_name: PRIMARY_KEY_COLUMN_NAME.to_string(),
1315 datatype: ColumnDataType::Binary as i32,
1316 semantic_type: SemanticType::Tag as _,
1317 datatype_extension: None,
1318 options: None,
1319 };
1320 let timestamp = PbColumnSchema {
1321 column_name: greptime_timestamp().to_string(),
1322 datatype: ColumnDataType::TimestampMillisecond as i32,
1323 semantic_type: SemanticType::Timestamp as _,
1324 datatype_extension: None,
1325 options: None,
1326 };
1327 let value = PbColumnSchema {
1328 column_name: greptime_value().to_string(),
1329 datatype: ColumnDataType::Float64 as i32,
1330 semantic_type: SemanticType::Field as _,
1331 datatype_extension: None,
1332 options: None,
1333 };
1334 let histogram = PbColumnSchema {
1335 column_name: greptime_native_histogram().to_string(),
1336 datatype: ColumnDataType::Struct as i32,
1337 semantic_type: SemanticType::Field as _,
1338 datatype_extension: None,
1339 options: None,
1340 };
1341
1342 let sample_rows = Rows {
1343 schema: vec![
1344 primary_key.clone(),
1345 timestamp.clone(),
1346 value.clone(),
1347 histogram.clone(),
1348 ],
1349 rows: vec![Row {
1350 values: vec![
1351 ValueData::BinaryValue(vec![1]).into(),
1352 ValueData::TimestampMillisecondValue(0).into(),
1353 ValueData::F64Value(1.0).into(),
1354 Value { value_data: None },
1355 ],
1356 }],
1357 };
1358 let histogram_rows = Rows {
1359 schema: vec![primary_key, timestamp, histogram, value],
1360 rows: vec![Row {
1361 values: vec![
1362 ValueData::BinaryValue(vec![2]).into(),
1363 ValueData::TimestampMillisecondValue(0).into(),
1364 ValueData::StructValue(api::v1::StructValue { items: vec![] }).into(),
1365 Value { value_data: None },
1366 ],
1367 }],
1368 };
1369
1370 let schema = MetricEngineInner::build_union_schema([
1371 sample_rows.schema.as_slice(),
1372 histogram_rows.schema.as_slice(),
1373 ]);
1374 let merged_rows = MetricEngineInner::align_rows_to_schema(sample_rows, &schema)
1375 .into_iter()
1376 .chain(MetricEngineInner::align_rows_to_schema(
1377 histogram_rows,
1378 &schema,
1379 ))
1380 .collect();
1381 let merged_request = Rows {
1382 schema,
1383 rows: merged_rows,
1384 };
1385
1386 let value_idx = column_index(&merged_request, greptime_value());
1387 let histogram_idx = column_index(&merged_request, greptime_native_histogram());
1388 assert!(matches!(
1389 merged_request.rows[0].values[value_idx].value_data,
1390 Some(ValueData::F64Value(_))
1391 ));
1392 assert!(
1393 merged_request.rows[0].values[histogram_idx]
1394 .value_data
1395 .is_none()
1396 );
1397 assert!(
1398 merged_request.rows[1].values[value_idx]
1399 .value_data
1400 .is_none()
1401 );
1402 assert!(matches!(
1403 merged_request.rows[1].values[histogram_idx].value_data,
1404 Some(ValueData::StructValue(_))
1405 ));
1406 }
1407
1408 #[tokio::test]
1409 async fn test_write_logical_region() {
1410 let env = TestEnv::new().await;
1411 env.init_metric_region().await;
1412
1413 let schema = test_util::row_schema_with_tags(&["job"]);
1415 let rows = test_util::build_rows(1, 5);
1416 let request = RegionRequest::Put(RegionPutRequest {
1417 skip_wal: false,
1418 rows: Rows { schema, rows },
1419 hint: None,
1420 partition_expr_version: None,
1421 });
1422
1423 let logical_region_id = env.default_logical_region_id();
1425 let result = env
1426 .metric()
1427 .handle_request(logical_region_id, request)
1428 .await
1429 .unwrap();
1430 assert_eq!(result.affected_rows, 5);
1431
1432 let physical_region_id = env.default_physical_region_id();
1434 let request = ScanRequest::default();
1435 let stream = env
1436 .metric()
1437 .scan_to_stream(physical_region_id, request)
1438 .await
1439 .unwrap();
1440 let batches = RecordBatches::try_collect(stream).await.unwrap();
1441 let expected = "\
1442+-------------------------+----------------+------------+---------------------+-------+
1443| greptime_timestamp | greptime_value | __table_id | __tsid | job |
1444+-------------------------+----------------+------------+---------------------+-------+
1445| 1970-01-01T00:00:00 | 0.0 | 3 | 2955007454552897459 | tag_0 |
1446| 1970-01-01T00:00:00.001 | 1.0 | 3 | 2955007454552897459 | tag_0 |
1447| 1970-01-01T00:00:00.002 | 2.0 | 3 | 2955007454552897459 | tag_0 |
1448| 1970-01-01T00:00:00.003 | 3.0 | 3 | 2955007454552897459 | tag_0 |
1449| 1970-01-01T00:00:00.004 | 4.0 | 3 | 2955007454552897459 | tag_0 |
1450+-------------------------+----------------+------------+---------------------+-------+";
1451 assert_eq!(expected, batches.pretty_print().unwrap(), "physical region");
1452
1453 let request = ScanRequest::default();
1455 let stream = env
1456 .metric()
1457 .scan_to_stream(logical_region_id, request)
1458 .await
1459 .unwrap();
1460 let batches = RecordBatches::try_collect(stream).await.unwrap();
1461 let expected = "\
1462+-------------------------+----------------+-------+
1463| greptime_timestamp | greptime_value | job |
1464+-------------------------+----------------+-------+
1465| 1970-01-01T00:00:00 | 0.0 | tag_0 |
1466| 1970-01-01T00:00:00.001 | 1.0 | tag_0 |
1467| 1970-01-01T00:00:00.002 | 2.0 | tag_0 |
1468| 1970-01-01T00:00:00.003 | 3.0 | tag_0 |
1469| 1970-01-01T00:00:00.004 | 4.0 | tag_0 |
1470+-------------------------+----------------+-------+";
1471 assert_eq!(expected, batches.pretty_print().unwrap(), "logical region");
1472 }
1473
1474 #[tokio::test]
1475 async fn test_write_logical_region_row_count() {
1476 let env = TestEnv::new().await;
1477 env.init_metric_region().await;
1478 let engine = env.metric();
1479
1480 let logical_region_id = env.default_logical_region_id();
1482 let columns = &["odd", "even", "Ev_En"];
1483 let alter_request = test_util::alter_logical_region_add_tag_columns(123456, columns);
1484 engine
1485 .handle_request(logical_region_id, RegionRequest::Alter(alter_request))
1486 .await
1487 .unwrap();
1488
1489 let schema = test_util::row_schema_with_tags(columns);
1491 let rows = test_util::build_rows(3, 100);
1492 let request = RegionRequest::Put(RegionPutRequest {
1493 skip_wal: false,
1494 rows: Rows { schema, rows },
1495 hint: None,
1496 partition_expr_version: None,
1497 });
1498
1499 let result = engine
1501 .handle_request(logical_region_id, request)
1502 .await
1503 .unwrap();
1504 assert_eq!(100, result.affected_rows);
1505 }
1506
1507 #[tokio::test]
1508 async fn test_write_physical_region() {
1509 let env = TestEnv::new().await;
1510 env.init_metric_region().await;
1511 let engine = env.metric();
1512
1513 let physical_region_id = env.default_physical_region_id();
1514 let schema = test_util::row_schema_with_tags(&["abc"]);
1515 let rows = test_util::build_rows(1, 100);
1516 let request = RegionRequest::Put(RegionPutRequest {
1517 skip_wal: false,
1518 rows: Rows { schema, rows },
1519 hint: None,
1520 partition_expr_version: None,
1521 });
1522
1523 engine
1524 .handle_request(physical_region_id, request)
1525 .await
1526 .unwrap_err();
1527 }
1528
1529 #[tokio::test]
1530 async fn test_write_nonexist_logical_region() {
1531 let env = TestEnv::new().await;
1532 env.init_metric_region().await;
1533 let engine = env.metric();
1534
1535 let logical_region_id = RegionId::new(175, 8345);
1536 let schema = test_util::row_schema_with_tags(&["def"]);
1537 let rows = test_util::build_rows(1, 100);
1538 let request = RegionRequest::Put(RegionPutRequest {
1539 skip_wal: false,
1540 rows: Rows { schema, rows },
1541 hint: None,
1542 partition_expr_version: None,
1543 });
1544
1545 engine
1546 .handle_request(logical_region_id, request)
1547 .await
1548 .unwrap_err();
1549 }
1550
1551 #[tokio::test]
1552 async fn test_batch_write_multiple_logical_regions() {
1553 let env = TestEnv::new().await;
1554 env.init_metric_region().await;
1555 let engine = env.metric();
1556
1557 let physical_region_id = env.default_physical_region_id();
1559 let logical_region_1 = env.default_logical_region_id();
1560 let logical_region_2 = RegionId::new(1024, 1);
1561 let logical_region_3 = RegionId::new(1024, 2);
1562
1563 env.create_logical_region(physical_region_id, logical_region_2)
1564 .await;
1565 env.create_logical_region(physical_region_id, logical_region_3)
1566 .await;
1567
1568 let schema = test_util::row_schema_with_tags(&["job"]);
1570
1571 let rows1 = test_util::build_rows(1, 3);
1576 let mut rows2 = test_util::build_rows(1, 2);
1577 let mut rows3 = test_util::build_rows(1, 5);
1578
1579 use api::v1::value::ValueData;
1581 for (i, row) in rows2.iter_mut().enumerate() {
1582 if let Some(ValueData::TimestampMillisecondValue(ts)) =
1583 row.values.get_mut(0).and_then(|v| v.value_data.as_mut())
1584 {
1585 *ts = (10 + i) as i64;
1586 }
1587 }
1588 for (i, row) in rows3.iter_mut().enumerate() {
1589 if let Some(ValueData::TimestampMillisecondValue(ts)) =
1590 row.values.get_mut(0).and_then(|v| v.value_data.as_mut())
1591 {
1592 *ts = (20 + i) as i64;
1593 }
1594 }
1595
1596 let requests = vec![
1597 (
1598 logical_region_1,
1599 RegionPutRequest {
1600 skip_wal: false,
1601 rows: Rows {
1602 schema: schema.clone(),
1603 rows: rows1,
1604 },
1605 hint: None,
1606 partition_expr_version: None,
1607 },
1608 ),
1609 (
1610 logical_region_2,
1611 RegionPutRequest {
1612 skip_wal: false,
1613 rows: Rows {
1614 schema: schema.clone(),
1615 rows: rows2,
1616 },
1617 hint: None,
1618 partition_expr_version: None,
1619 },
1620 ),
1621 (
1622 logical_region_3,
1623 RegionPutRequest {
1624 skip_wal: false,
1625 rows: Rows {
1626 schema: schema.clone(),
1627 rows: rows3,
1628 },
1629 hint: None,
1630 partition_expr_version: None,
1631 },
1632 ),
1633 ];
1634
1635 let affected_rows = engine
1637 .inner
1638 .put_regions_batch(requests.into_iter())
1639 .await
1640 .unwrap();
1641 assert_eq!(affected_rows, 10);
1642
1643 let request = ScanRequest::default();
1645 let stream = env
1646 .metric()
1647 .scan_to_stream(physical_region_id, request)
1648 .await
1649 .unwrap();
1650 let batches = RecordBatches::try_collect(stream).await.unwrap();
1651
1652 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 10);
1654 }
1655
1656 #[tokio::test]
1657 async fn test_batch_write_with_partial_failure() {
1658 let env = TestEnv::new().await;
1659 env.init_metric_region().await;
1660 let engine = env.metric();
1661
1662 let physical_region_id = env.default_physical_region_id();
1663 let logical_region_1 = env.default_logical_region_id();
1664 let logical_region_2 = RegionId::new(1024, 1);
1665 let nonexistent_region = RegionId::new(9999, 9999);
1666
1667 env.create_logical_region(physical_region_id, logical_region_2)
1668 .await;
1669
1670 let schema = test_util::row_schema_with_tags(&["job"]);
1672 let requests = vec![
1673 (
1674 logical_region_1,
1675 RegionPutRequest {
1676 skip_wal: false,
1677 rows: Rows {
1678 schema: schema.clone(),
1679 rows: test_util::build_rows(1, 3),
1680 },
1681 hint: None,
1682 partition_expr_version: None,
1683 },
1684 ),
1685 (
1686 nonexistent_region,
1687 RegionPutRequest {
1688 skip_wal: false,
1689 rows: Rows {
1690 schema: schema.clone(),
1691 rows: test_util::build_rows(1, 2),
1692 },
1693 hint: None,
1694 partition_expr_version: None,
1695 },
1696 ),
1697 (
1698 logical_region_2,
1699 RegionPutRequest {
1700 skip_wal: false,
1701 rows: Rows {
1702 schema: schema.clone(),
1703 rows: test_util::build_rows(1, 5),
1704 },
1705 hint: None,
1706 partition_expr_version: None,
1707 },
1708 ),
1709 ];
1710
1711 let result = engine.inner.put_regions_batch(requests.into_iter()).await;
1713 assert!(result.is_err());
1714
1715 let request = ScanRequest::default();
1718 let stream = env
1719 .metric()
1720 .scan_to_stream(physical_region_id, request)
1721 .await
1722 .unwrap();
1723 let batches = RecordBatches::try_collect(stream).await.unwrap();
1724
1725 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 0);
1726 }
1727
1728 #[tokio::test]
1729 async fn test_batch_write_single_physical_region_forbidden() {
1730 let env = TestEnv::new().await;
1731 env.init_metric_region().await;
1732 let engine = env.metric();
1733
1734 let physical_region_id = env.default_physical_region_id();
1735 let schema = test_util::row_schema_with_tags(&["job"]);
1736 let requests = vec![(
1737 physical_region_id,
1738 RegionPutRequest {
1739 skip_wal: false,
1740 rows: Rows {
1741 schema,
1742 rows: test_util::build_rows(1, 1),
1743 },
1744 hint: None,
1745 partition_expr_version: None,
1746 },
1747 )];
1748
1749 let err = engine
1750 .inner
1751 .put_regions_batch(requests.into_iter())
1752 .await
1753 .unwrap_err();
1754
1755 assert!(matches!(
1756 err,
1757 crate::error::Error::ForbiddenPhysicalWrite { .. }
1758 ));
1759 }
1760
1761 #[tokio::test]
1762 async fn test_batch_write_physical_region_forbidden() {
1763 let env = TestEnv::new().await;
1764 env.init_metric_region().await;
1765 let engine = env.metric();
1766
1767 let physical_region_id = env.default_physical_region_id();
1768 let logical_region_id = env.default_logical_region_id();
1769 let schema = test_util::row_schema_with_tags(&["job"]);
1770 let requests = vec![
1771 (
1772 logical_region_id,
1773 RegionPutRequest {
1774 skip_wal: false,
1775 rows: Rows {
1776 schema: schema.clone(),
1777 rows: test_util::build_rows(1, 1),
1778 },
1779 hint: None,
1780 partition_expr_version: None,
1781 },
1782 ),
1783 (
1784 physical_region_id,
1785 RegionPutRequest {
1786 skip_wal: false,
1787 rows: Rows {
1788 schema,
1789 rows: test_util::build_rows(1, 1),
1790 },
1791 hint: None,
1792 partition_expr_version: None,
1793 },
1794 ),
1795 ];
1796
1797 let err = engine
1798 .inner
1799 .put_regions_batch(requests.into_iter())
1800 .await
1801 .unwrap_err();
1802
1803 assert!(matches!(
1804 err,
1805 crate::error::Error::ForbiddenPhysicalWrite { .. }
1806 ));
1807 }
1808
1809 #[tokio::test]
1810 async fn test_batch_write_single_request_fast_path() {
1811 let env = TestEnv::new().await;
1812 env.init_metric_region().await;
1813 let engine = env.metric();
1814
1815 let logical_region_id = env.default_logical_region_id();
1816 let schema = test_util::row_schema_with_tags(&["job"]);
1817
1818 let requests = vec![(
1820 logical_region_id,
1821 RegionPutRequest {
1822 skip_wal: false,
1823 rows: Rows {
1824 schema,
1825 rows: test_util::build_rows(1, 5),
1826 },
1827 hint: None,
1828 partition_expr_version: None,
1829 },
1830 )];
1831
1832 let affected_rows = engine
1833 .inner
1834 .put_regions_batch(requests.into_iter())
1835 .await
1836 .unwrap();
1837 assert_eq!(affected_rows, 5);
1838 }
1839
1840 #[tokio::test]
1841 async fn test_batch_write_empty_requests() {
1842 let env = TestEnv::new().await;
1843 env.init_metric_region().await;
1844 let engine = env.metric();
1845
1846 let requests = vec![];
1848 let affected_rows = engine
1849 .inner
1850 .put_regions_batch(requests.into_iter())
1851 .await
1852 .unwrap();
1853
1854 assert_eq!(affected_rows, 0);
1855 }
1856
1857 #[tokio::test]
1858 async fn test_batch_write_sparse_encoding() {
1859 let env = TestEnv::new().await;
1860 let physical_region_id = env.default_physical_region_id();
1861
1862 run_batch_write_with_schema_variants(
1863 &env,
1864 physical_region_id,
1865 vec![(PRIMARY_KEY_ENCODING.to_string(), "sparse".to_string())],
1866 true,
1867 )
1868 .await;
1869 }
1870
1871 #[tokio::test]
1872 async fn test_batch_write_dense_encoding() {
1873 let env = TestEnv::new().await;
1874 let physical_region_id = env.default_physical_region_id();
1875
1876 run_batch_write_with_schema_variants(
1877 &env,
1878 physical_region_id,
1879 vec![(PRIMARY_KEY_ENCODING.to_string(), "dense".to_string())],
1880 false,
1881 )
1882 .await;
1883 }
1884
1885 #[tokio::test]
1886 async fn test_metric_put_rejects_bad_partition_expr_version() {
1887 let env = TestEnv::new().await;
1888 env.init_metric_region().await;
1889
1890 let logical_region_id = env.default_logical_region_id();
1891 let rows = Rows {
1892 schema: test_util::row_schema_with_tags(&["job"]),
1893 rows: test_util::build_rows(1, 3),
1894 };
1895
1896 let err = env
1897 .metric()
1898 .handle_request(
1899 logical_region_id,
1900 RegionRequest::Put(RegionPutRequest {
1901 skip_wal: false,
1902 rows,
1903 hint: None,
1904 partition_expr_version: Some(1),
1905 }),
1906 )
1907 .await
1908 .unwrap_err();
1909
1910 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
1911 }
1912
1913 #[tokio::test]
1914 async fn test_metric_put_respects_staging_partition_expr_version() {
1915 let env = TestEnv::new().await;
1916 env.init_metric_region().await;
1917
1918 let logical_region_id = env.default_logical_region_id();
1919 let physical_region_id = env.default_physical_region_id();
1920 let partition_expr = job_partition_expr_json();
1921 env.metric()
1922 .handle_request(
1923 physical_region_id,
1924 RegionRequest::EnterStaging(EnterStagingRequest {
1925 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(
1926 partition_expr.clone(),
1927 ),
1928 }),
1929 )
1930 .await
1931 .unwrap();
1932
1933 let expected_version = partition_expr_version(Some(&partition_expr));
1934 let rows = Rows {
1935 schema: test_util::row_schema_with_tags(&["job"]),
1936 rows: test_util::build_rows(1, 3),
1937 };
1938
1939 let err = env
1940 .metric()
1941 .handle_request(
1942 logical_region_id,
1943 RegionRequest::Put(RegionPutRequest {
1944 skip_wal: false,
1945 rows: rows.clone(),
1946 hint: None,
1947 partition_expr_version: Some(expected_version.wrapping_add(1)),
1948 }),
1949 )
1950 .await
1951 .unwrap_err();
1952 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
1953
1954 let response = env
1955 .metric()
1956 .handle_request(
1957 logical_region_id,
1958 RegionRequest::Put(RegionPutRequest {
1959 skip_wal: false,
1960 rows: rows.clone(),
1961 hint: None,
1962 partition_expr_version: None,
1963 }),
1964 )
1965 .await
1966 .unwrap();
1967 assert_eq!(response.affected_rows, 3);
1968
1969 let response = env
1970 .metric()
1971 .handle_request(
1972 logical_region_id,
1973 RegionRequest::Put(RegionPutRequest {
1974 skip_wal: false,
1975 rows,
1976 hint: None,
1977 partition_expr_version: Some(expected_version),
1978 }),
1979 )
1980 .await
1981 .unwrap();
1982 assert_eq!(response.affected_rows, 3);
1983 }
1984
1985 #[tokio::test]
1989 async fn test_verify_rows_rejects_wrong_type() {
1990 use api::v1::value::ValueData;
1991 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, SemanticType};
1992 use common_query::prelude::{greptime_timestamp, greptime_value};
1993
1994 let env = TestEnv::new().await;
1995 env.init_metric_region().await;
1996
1997 let logical_region_id = env.default_logical_region_id();
1998
1999 let schema = vec![
2002 PbColumnSchema {
2003 column_name: greptime_timestamp().to_string(),
2004 datatype: ColumnDataType::String as i32,
2005 semantic_type: SemanticType::Timestamp as _,
2006 datatype_extension: None,
2007 options: None,
2008 },
2009 PbColumnSchema {
2010 column_name: greptime_value().to_string(),
2011 datatype: ColumnDataType::Float64 as i32,
2012 semantic_type: SemanticType::Field as _,
2013 datatype_extension: None,
2014 options: None,
2015 },
2016 PbColumnSchema {
2017 column_name: "job".to_string(),
2018 datatype: ColumnDataType::String as i32,
2019 semantic_type: SemanticType::Tag as _,
2020 datatype_extension: None,
2021 options: None,
2022 },
2023 ];
2024 let rows = vec![Row {
2025 values: vec![
2026 Value {
2027 value_data: Some(ValueData::StringValue("not-a-timestamp".to_string())),
2028 },
2029 Value {
2030 value_data: Some(ValueData::F64Value(1.0)),
2031 },
2032 Value {
2033 value_data: Some(ValueData::StringValue("tag_0".to_string())),
2034 },
2035 ],
2036 }];
2037
2038 let err = env
2039 .metric()
2040 .handle_request(
2041 logical_region_id,
2042 RegionRequest::Put(RegionPutRequest {
2043 skip_wal: false,
2044 rows: Rows { schema, rows },
2045 hint: None,
2046 partition_expr_version: None,
2047 }),
2048 )
2049 .await
2050 .unwrap_err();
2051 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2052 }
2053
2054 #[tokio::test]
2058 async fn test_verify_rows_rejects_missing_time_index() {
2059 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, SemanticType};
2060 use common_query::prelude::greptime_value;
2061
2062 let env = TestEnv::new().await;
2063 env.init_metric_region().await;
2064
2065 let logical_region_id = env.default_logical_region_id();
2066
2067 let schema = vec![
2069 PbColumnSchema {
2070 column_name: greptime_value().to_string(),
2071 datatype: ColumnDataType::Float64 as i32,
2072 semantic_type: SemanticType::Field as _,
2073 datatype_extension: None,
2074 options: None,
2075 },
2076 PbColumnSchema {
2077 column_name: "job".to_string(),
2078 datatype: ColumnDataType::String as i32,
2079 semantic_type: SemanticType::Tag as _,
2080 datatype_extension: None,
2081 options: None,
2082 },
2083 ];
2084 let rows = vec![Row {
2085 values: vec![
2086 Value {
2087 value_data: Some(api::v1::value::ValueData::F64Value(1.0)),
2088 },
2089 Value {
2090 value_data: Some(api::v1::value::ValueData::StringValue("tag_0".to_string())),
2091 },
2092 ],
2093 }];
2094
2095 let err = env
2096 .metric()
2097 .handle_request(
2098 logical_region_id,
2099 RegionRequest::Put(RegionPutRequest {
2100 skip_wal: false,
2101 rows: Rows { schema, rows },
2102 hint: None,
2103 partition_expr_version: None,
2104 }),
2105 )
2106 .await
2107 .unwrap_err();
2108 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2109 }
2110
2111 #[tokio::test]
2112 async fn test_verify_rows_rejects_missing_field() {
2113 use api::v1::value::ValueData;
2114 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, SemanticType};
2115 use common_query::prelude::greptime_timestamp;
2116
2117 let env = TestEnv::new().await;
2118 env.init_metric_region().await;
2119
2120 let logical_region_id = env.default_logical_region_id();
2121
2122 let schema = vec![
2124 PbColumnSchema {
2125 column_name: greptime_timestamp().to_string(),
2126 datatype: ColumnDataType::TimestampMillisecond as i32,
2127 semantic_type: SemanticType::Timestamp as _,
2128 datatype_extension: None,
2129 options: None,
2130 },
2131 PbColumnSchema {
2132 column_name: "job".to_string(),
2133 datatype: ColumnDataType::String as i32,
2134 semantic_type: SemanticType::Tag as _,
2135 datatype_extension: None,
2136 options: None,
2137 },
2138 ];
2139 let rows = vec![Row {
2140 values: vec![
2141 Value {
2142 value_data: Some(ValueData::TimestampMillisecondValue(0)),
2143 },
2144 Value {
2145 value_data: Some(ValueData::StringValue("tag_0".to_string())),
2146 },
2147 ],
2148 }];
2149
2150 let err = env
2151 .metric()
2152 .handle_request(
2153 logical_region_id,
2154 RegionRequest::Put(RegionPutRequest {
2155 skip_wal: false,
2156 rows: Rows { schema, rows },
2157 hint: None,
2158 partition_expr_version: None,
2159 }),
2160 )
2161 .await
2162 .unwrap_err();
2163 let message = err.to_string();
2164 assert!(
2165 message.contains("missing required field column"),
2166 "expected field-completeness rejection, got: {message}"
2167 );
2168 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2169 }
2170
2171 #[test]
2172 fn test_fill_missing_field_column_nullable_no_default() {
2173 let field_meta = ColumnMetadata {
2174 column_id: 1,
2175 semantic_type: SemanticType::Field,
2176 column_schema: ColumnSchema::new(
2177 "greptime_value".to_string(),
2178 ConcreteDataType::float64_datatype(),
2179 true, ),
2181 };
2182 let mut rows = Rows {
2183 schema: vec![PbColumnSchema {
2184 column_name: "ts".to_string(),
2185 datatype: ColumnDataType::TimestampMillisecond as i32,
2186 semantic_type: SemanticType::Timestamp as _,
2187 datatype_extension: None,
2188 options: None,
2189 }],
2190 rows: vec![Row {
2191 values: vec![Value {
2192 value_data: Some(ValueData::TimestampMillisecondValue(0)),
2193 }],
2194 }],
2195 };
2196
2197 MetricEngineInner::fill_missing_field_column(
2198 RegionId::new(1, 1),
2199 "greptime_value",
2200 &field_meta,
2201 &mut rows,
2202 )
2203 .unwrap();
2204
2205 assert_eq!(rows.schema.len(), 2);
2206 assert_eq!(rows.schema[1].column_name, "greptime_value");
2207 assert_eq!(rows.rows[0].values.len(), 2);
2208 assert!(
2209 rows.rows[0].values[1].value_data.is_none(),
2210 "missing nullable field should be filled with null"
2211 );
2212 }
2213
2214 #[test]
2215 fn test_fill_missing_field_column_rejects_impure_default() {
2216 let field_meta = ColumnMetadata {
2217 column_id: 1,
2218 semantic_type: SemanticType::Field,
2219 column_schema: ColumnSchema::new(
2220 "greptime_value".to_string(),
2221 ConcreteDataType::timestamp_millisecond_datatype(),
2222 false,
2223 )
2224 .with_default_constraint(Some(ColumnDefaultConstraint::Function("now()".to_string())))
2225 .unwrap(),
2226 };
2227 let mut rows = Rows {
2228 schema: vec![PbColumnSchema {
2229 column_name: "ts".to_string(),
2230 datatype: api::v1::ColumnDataType::TimestampMillisecond as i32,
2231 semantic_type: SemanticType::Timestamp as _,
2232 datatype_extension: None,
2233 options: None,
2234 }],
2235 rows: vec![Row {
2236 values: vec![Value {
2237 value_data: Some(ValueData::TimestampMillisecondValue(0)),
2238 }],
2239 }],
2240 };
2241
2242 let err = MetricEngineInner::fill_missing_field_column(
2243 RegionId::new(1, 1),
2244 "greptime_value",
2245 &field_meta,
2246 &mut rows,
2247 )
2248 .unwrap_err();
2249 assert!(
2250 err.to_string().contains("impure default value"),
2251 "expected impure-default rejection, got: {err}"
2252 );
2253 }
2254}