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