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 use std::sync::Arc;
789 use std::time::{Duration, Instant};
790
791 use api::v1::value::ValueData;
792 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema};
793 use common_error::ext::ErrorExt;
794 use common_error::status_code::StatusCode;
795 use common_function::utils::partition_expr_version;
796 use common_query::prelude::{greptime_native_histogram, greptime_timestamp, greptime_value};
797 use common_recordbatch::RecordBatches;
798 use datatypes::arrow::array::{
799 Float64Array, TimestampMicrosecondArray, TimestampMillisecondArray,
800 };
801 use datatypes::prelude::ConcreteDataType;
802 use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema};
803 use datatypes::value::Value as PartitionValue;
804 use futures_util::FutureExt;
805 use mito2::config::MitoConfig;
806 use mito2::engine::listener::WriteWalListener;
807 use mito2::test_util::TestEnv as MitoTestEnv;
808 use partition::expr::col;
809 use store_api::metadata::ColumnMetadata;
810 use store_api::metric_engine_consts::{
811 DATA_SCHEMA_TABLE_ID_COLUMN_NAME, DATA_SCHEMA_TSID_COLUMN_NAME, METRIC_ENGINE_NAME,
812 PHYSICAL_TABLE_METADATA_KEY, PRIMARY_KEY_ENCODING,
813 };
814 use store_api::path_utils::table_dir;
815 use store_api::region_engine::{RegionEngine, RegionRole};
816 use store_api::region_request::{
817 EnterStagingRequest, PathType, RegionCloseRequest, RegionDropRequest, RegionOpenRequest,
818 RegionRequest, StagingPartitionDirective,
819 };
820 use store_api::storage::ScanRequest;
821 use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
822
823 use super::*;
824 use crate::engine::MetricEngine;
825 use crate::test_util::{self, TestEnv};
826
827 async fn scan_timestamp_values(engine: &MetricEngine, region_id: RegionId) -> Vec<(i64, f64)> {
828 let stream = engine
829 .scan_to_stream(region_id, ScanRequest::default())
830 .await
831 .unwrap();
832 let batches = RecordBatches::try_collect(stream).await.unwrap();
833 let mut rows = Vec::new();
834 for batch in batches.iter() {
835 let batch = batch.df_record_batch();
836 let timestamp_index = batch.schema().index_of(greptime_timestamp()).unwrap();
837 let value_index = batch.schema().index_of(greptime_value()).unwrap();
838 let timestamps = batch
839 .column(timestamp_index)
840 .as_any()
841 .downcast_ref::<TimestampMillisecondArray>()
842 .unwrap();
843 let values = batch
844 .column(value_index)
845 .as_any()
846 .downcast_ref::<Float64Array>()
847 .unwrap();
848 rows.extend(
849 timestamps
850 .values()
851 .iter()
852 .copied()
853 .zip(values.values().iter().copied()),
854 );
855 }
856 rows.sort_unstable_by_key(|(timestamp, _)| *timestamp);
857 rows
858 }
859
860 #[tokio::test]
861 async fn test_batch_partition_versions() {
862 check_batch_partition_versions("sparse").await;
863 check_batch_partition_versions("dense").await;
864 }
865
866 async fn check_batch_partition_versions(encoding: &str) {
867 let env = TestEnv::new().await;
868 let physical_region_id = env.default_physical_region_id();
869 let logical_region_id = env.default_logical_region_id();
870 env.create_physical_region(
871 physical_region_id,
872 &TestEnv::default_table_dir(),
873 vec![(PRIMARY_KEY_ENCODING.to_string(), encoding.to_string())],
874 )
875 .await;
876 create_logical_region_with_tags(&env, physical_region_id, logical_region_id, &["job"])
877 .await;
878 let build_requests = |versions: [Option<u64>; 3]| {
879 versions
880 .into_iter()
881 .map(|partition_expr_version| {
882 (
883 logical_region_id,
884 RegionPutRequest {
885 skip_wal: false,
886 rows: Rows {
887 schema: test_util::row_schema_with_tags(&["job"]),
888 rows: test_util::build_rows(1, 1),
889 },
890 hint: None,
891 partition_expr_version,
892 },
893 )
894 })
895 .collect::<Vec<_>>()
896 };
897 let err = env
899 .metric()
900 .inner
901 .put_regions_batch_single_physical(
902 physical_region_id,
903 build_requests([None, Some(10), Some(11)]),
904 )
905 .await
906 .unwrap_err();
907 assert!(
908 err.to_string()
909 .contains("inconsistent partition expr version")
910 );
911 assert!(
912 scan_timestamp_values(&env.metric(), logical_region_id)
913 .await
914 .is_empty()
915 );
916
917 for (versions, expected) in [
918 ([None, None, None], None),
919 ([None, Some(7), None], Some(7)),
920 ([Some(7), None, Some(7)], Some(7)),
921 ] {
922 let mut requests = build_requests(versions);
923 let engine = env.metric();
924 engine
925 .inner
926 .validate_batch_requests(physical_region_id, &mut requests)
927 .await
928 .unwrap();
929 let (merged, _) = match encoding {
930 "sparse" => engine
931 .inner
932 .merge_sparse_batch(physical_region_id, requests),
933 "dense" => engine
934 .inner
935 .merge_dense_batch(to_data_region_id(physical_region_id), requests),
936 _ => unreachable!(),
937 }
938 .unwrap();
939 assert_eq!(merged.partition_expr_version, expected);
940 }
941 }
942
943 #[tokio::test]
944 async fn test_put_skip_wal_batch_recovery() {
945 check_put_skip_wal_batch_recovery("sparse", false, false).await;
946 check_put_skip_wal_batch_recovery("sparse", true, false).await;
947 check_put_skip_wal_batch_recovery("dense", false, false).await;
948 check_put_skip_wal_batch_recovery("dense", true, false).await;
949 }
950
951 #[tokio::test]
952 async fn test_put_skip_wal_batch_close_recovery() {
953 check_put_skip_wal_batch_recovery("sparse", false, true).await;
954 check_put_skip_wal_batch_recovery("sparse", true, true).await;
955 check_put_skip_wal_batch_recovery("dense", false, true).await;
956 check_put_skip_wal_batch_recovery("dense", true, true).await;
957 }
958
959 async fn check_put_skip_wal_batch_recovery(encoding: &str, skip_wal: bool, close: bool) {
960 let mut env = TestEnv::new().await;
961 let engine = env.metric();
962 engine.inner.flush_task.stop().await.unwrap();
963 let physical_region_id = env.default_physical_region_id();
964 let logical_region_id = env.default_logical_region_id();
965 env.create_physical_region(
966 physical_region_id,
967 &TestEnv::default_table_dir(),
968 vec![(PRIMARY_KEY_ENCODING.to_string(), encoding.to_string())],
969 )
970 .await;
971 create_logical_region_with_tags(&env, physical_region_id, logical_region_id, &["job"])
972 .await;
973 let metadata_before = engine.get_metadata(logical_region_id).await.unwrap();
974
975 let requests = [skip_wal; 3]
976 .into_iter()
977 .enumerate()
978 .map(|(index, skip_wal)| {
979 let timestamp = index as i64 + 1;
980 let value = timestamp as f64 * 10.0;
981 let rows = [0, timestamp]
984 .into_iter()
985 .map(|timestamp| Row {
986 values: vec![
987 Value {
988 value_data: Some(ValueData::TimestampMillisecondValue(timestamp)),
989 },
990 Value {
991 value_data: Some(ValueData::F64Value(value)),
992 },
993 Value {
994 value_data: Some(ValueData::StringValue("tag_0".to_string())),
995 },
996 ],
997 })
998 .collect();
999 (
1000 logical_region_id,
1001 RegionPutRequest {
1002 rows: Rows {
1003 schema: test_util::row_schema_with_tags(&["job"]),
1004 rows,
1005 },
1006 hint: None,
1007 partition_expr_version: None,
1008 skip_wal,
1009 },
1010 )
1011 });
1012 let results = engine.put_regions_batch(requests).await;
1013 assert_eq!(results.len(), 1);
1014 let affected_rows = results.into_iter().next().unwrap().result.unwrap();
1015 assert_eq!(affected_rows, 6);
1016 assert_eq!(
1017 scan_timestamp_values(&engine, logical_region_id).await,
1018 vec![(0, 30.0), (1, 10.0), (2, 20.0), (3, 30.0)]
1019 );
1020
1021 for region_id in [
1023 to_data_region_id(physical_region_id),
1024 crate::utils::to_metadata_region_id(physical_region_id),
1025 ] {
1026 let stat = env.mito().region_statistic(region_id).unwrap();
1027 assert!(stat.memtable_size > 0);
1028 assert_eq!(stat.sst_num, 0);
1029 }
1030 let reopened = if close {
1031 engine
1032 .handle_request(
1033 physical_region_id,
1034 RegionRequest::Close(RegionCloseRequest::default()),
1035 )
1036 .await
1037 .unwrap();
1038 MetricEngine::try_new(env.mito(), Default::default()).unwrap()
1040 } else {
1041 env.reopen_engine(Default::default()).await;
1043 env.metric()
1044 };
1045 reopened.inner.flush_task.stop().await.unwrap();
1046 reopened
1047 .handle_request(
1048 physical_region_id,
1049 RegionRequest::Open(RegionOpenRequest {
1050 engine: METRIC_ENGINE_NAME.to_string(),
1051 table_dir: TestEnv::default_table_dir(),
1052 path_type: PathType::Bare,
1053 options: [
1054 (PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new()),
1055 (PRIMARY_KEY_ENCODING.to_string(), encoding.to_string()),
1056 ]
1057 .into_iter()
1058 .collect(),
1059 skip_wal_replay: false,
1060 checkpoint: None,
1061 requirements: Default::default(),
1062 }),
1063 )
1064 .await
1065 .unwrap();
1066 let stat = env
1067 .mito()
1068 .region_statistic(crate::utils::to_data_region_id(physical_region_id))
1069 .unwrap();
1070 assert_eq!(stat.sst_num > 0, skip_wal && close);
1071 let recovered_metadata = reopened.get_metadata(logical_region_id).await.unwrap();
1072 assert_eq!(
1073 metadata_before.column_metadatas,
1074 recovered_metadata.column_metadatas
1075 );
1076 let expected = if skip_wal && !close {
1077 vec![]
1078 } else {
1079 vec![(0, 30.0), (1, 10.0), (2, 20.0), (3, 30.0)]
1080 };
1081 assert_eq!(
1082 scan_timestamp_values(&reopened, logical_region_id).await,
1083 expected,
1084 "encoding={encoding}, skip_wal={skip_wal}, close={close}"
1085 );
1086 }
1087
1088 fn assert_merged_schema(rows: &Rows, expect_sparse: bool) {
1089 let column_names: HashSet<String> = rows
1090 .schema
1091 .iter()
1092 .map(|col| col.column_name.clone())
1093 .collect();
1094
1095 if expect_sparse {
1096 assert!(
1097 column_names.contains(PRIMARY_KEY_COLUMN_NAME),
1098 "sparse encoding should include primary key column"
1099 );
1100 assert!(
1101 !column_names.contains(DATA_SCHEMA_TABLE_ID_COLUMN_NAME),
1102 "sparse encoding should not include table id column"
1103 );
1104 assert!(
1105 !column_names.contains(DATA_SCHEMA_TSID_COLUMN_NAME),
1106 "sparse encoding should not include tsid column"
1107 );
1108 assert!(
1109 !column_names.contains("job"),
1110 "sparse encoding should not include tag columns"
1111 );
1112 assert!(
1113 !column_names.contains("instance"),
1114 "sparse encoding should not include tag columns"
1115 );
1116 } else {
1117 assert!(
1118 !column_names.contains(PRIMARY_KEY_COLUMN_NAME),
1119 "dense encoding should not include primary key column"
1120 );
1121 assert!(
1122 column_names.contains(DATA_SCHEMA_TABLE_ID_COLUMN_NAME),
1123 "dense encoding should include table id column"
1124 );
1125 assert!(
1126 column_names.contains(DATA_SCHEMA_TSID_COLUMN_NAME),
1127 "dense encoding should include tsid column"
1128 );
1129 assert!(
1130 column_names.contains("job"),
1131 "dense encoding should keep tag columns"
1132 );
1133 assert!(
1134 column_names.contains("instance"),
1135 "dense encoding should keep tag columns"
1136 );
1137 }
1138 }
1139
1140 fn job_partition_expr_json() -> String {
1141 let expr = col("job")
1142 .gt_eq(PartitionValue::String("job-0".into()))
1143 .and(col("job").lt(PartitionValue::String("job-9".into())));
1144 expr.as_json_str().unwrap()
1145 }
1146
1147 #[tokio::test]
1148 async fn test_put_and_scan_microsecond_physical_region() {
1149 let env = TestEnv::new().await;
1150 let engine = env.metric();
1151 let physical_region_id = env.default_physical_region_id();
1152 let logical_region_id = env.default_logical_region_id();
1153 env.create_physical_region_with_ts_type(
1154 physical_region_id,
1155 &TestEnv::default_table_dir(),
1156 vec![],
1157 ConcreteDataType::timestamp_microsecond_datatype(),
1158 )
1159 .await;
1160
1161 let region_create_request = test_util::create_logical_region_request_with_ts_type(
1163 &["job"],
1164 physical_region_id,
1165 &table_dir("test", logical_region_id.table_id()),
1166 ConcreteDataType::timestamp_microsecond_datatype(),
1167 );
1168 engine
1169 .handle_request(
1170 logical_region_id,
1171 RegionRequest::Create(region_create_request),
1172 )
1173 .await
1174 .unwrap();
1175
1176 let affected_rows = engine
1178 .handle_request(
1179 logical_region_id,
1180 RegionRequest::Put(RegionPutRequest {
1181 rows: Rows {
1182 schema: test_util::row_schema_with_tags_and_ts_datatype(
1183 &["job"],
1184 ColumnDataType::TimestampMicrosecond,
1185 ),
1186 rows: test_util::build_rows_with_ts_datatype(
1187 1,
1188 2,
1189 ColumnDataType::TimestampMicrosecond,
1190 ),
1191 },
1192 hint: None,
1193 partition_expr_version: None,
1194 skip_wal: false,
1195 }),
1196 )
1197 .await
1198 .unwrap();
1199 assert_eq!(affected_rows.affected_rows, 2);
1200
1201 let stream = engine
1203 .scan_to_stream(logical_region_id, ScanRequest::default())
1204 .await
1205 .unwrap();
1206 let batches = RecordBatches::try_collect(stream).await.unwrap();
1207 let mut rows = Vec::new();
1208 for batch in batches.iter() {
1209 let batch = batch.df_record_batch();
1210 let timestamps = batch
1211 .column(batch.schema().index_of(greptime_timestamp()).unwrap())
1212 .as_any()
1213 .downcast_ref::<TimestampMicrosecondArray>()
1214 .unwrap();
1215 let values = batch
1216 .column(batch.schema().index_of(greptime_value()).unwrap())
1217 .as_any()
1218 .downcast_ref::<Float64Array>()
1219 .unwrap();
1220 rows.extend(
1221 timestamps
1222 .values()
1223 .iter()
1224 .copied()
1225 .zip(values.values().iter().copied()),
1226 );
1227 }
1228 rows.sort_unstable_by_key(|(timestamp, _)| *timestamp);
1229 assert_eq!(rows, vec![(0, 0.0), (1, 1.0)]);
1230 }
1231
1232 async fn create_logical_region_with_tags(
1233 env: &TestEnv,
1234 physical_region_id: RegionId,
1235 logical_region_id: RegionId,
1236 tags: &[&str],
1237 ) {
1238 let region_create_request = test_util::create_logical_region_request(
1239 tags,
1240 physical_region_id,
1241 &table_dir("test", logical_region_id.table_id()),
1242 );
1243 env.metric()
1244 .handle_request(
1245 logical_region_id,
1246 RegionRequest::Create(region_create_request),
1247 )
1248 .await
1249 .unwrap();
1250 }
1251
1252 fn column_index(rows: &Rows, name: &str) -> usize {
1253 rows.schema
1254 .iter()
1255 .position(|col| col.column_name == name)
1256 .unwrap()
1257 }
1258
1259 fn check_batch_merge_wal_policy(
1260 env: &TestEnv,
1261 physical_region_id: RegionId,
1262 mut requests: Vec<(RegionId, RegionPutRequest)>,
1263 expect_sparse: bool,
1264 skip_wal: bool,
1265 ) {
1266 for (_, request) in &mut requests {
1267 request.skip_wal = skip_wal;
1268 }
1269 let (merged_request, affected_rows) = if expect_sparse {
1270 let (merged_request, affected_rows) = env
1271 .metric()
1272 .inner
1273 .merge_sparse_batch(physical_region_id, requests)
1274 .unwrap();
1275 let hint = merged_request
1276 .hint
1277 .as_ref()
1278 .expect("missing sparse write hint");
1279 assert_eq!(
1280 hint.primary_key_encoding,
1281 PrimaryKeyEncodingProto::Sparse as i32
1282 );
1283 (merged_request, affected_rows)
1284 } else {
1285 let (merged_request, affected_rows) = env
1286 .metric()
1287 .inner
1288 .merge_dense_batch(to_data_region_id(physical_region_id), requests)
1289 .unwrap();
1290 assert!(merged_request.hint.is_none());
1291 (merged_request, affected_rows)
1292 };
1293 assert_merged_schema(&merged_request.rows, expect_sparse);
1294 assert_eq!(merged_request.skip_wal, skip_wal);
1295 assert_eq!(affected_rows, 5);
1296 }
1297
1298 async fn run_batch_write_with_schema_variants(
1299 env: &TestEnv,
1300 physical_region_id: RegionId,
1301 options: Vec<(String, String)>,
1302 expect_sparse: bool,
1303 ) {
1304 env.create_physical_region(physical_region_id, &TestEnv::default_table_dir(), options)
1305 .await;
1306
1307 let logical_region_1 = env.default_logical_region_id();
1308 let logical_region_2 = RegionId::new(1024, 1);
1309
1310 create_logical_region_with_tags(env, physical_region_id, logical_region_1, &["job"]).await;
1311 create_logical_region_with_tags(
1312 env,
1313 physical_region_id,
1314 logical_region_2,
1315 &["job", "instance"],
1316 )
1317 .await;
1318
1319 let schema_1 = test_util::row_schema_with_tags(&["job"]);
1320 let schema_2 = test_util::row_schema_with_tags(&["job", "instance"]);
1321
1322 let data_region_id = RegionId::new(physical_region_id.table_id(), 2);
1323 let primary_key_encoding = env
1324 .metric()
1325 .inner
1326 .get_primary_key_encoding(data_region_id)
1327 .unwrap();
1328 assert_eq!(
1329 primary_key_encoding,
1330 if expect_sparse {
1331 PrimaryKeyEncoding::Sparse
1332 } else {
1333 PrimaryKeyEncoding::Dense
1334 }
1335 );
1336
1337 let build_requests = || {
1338 let rows_1 = test_util::build_rows(1, 3);
1339 let rows_2 = test_util::build_rows(2, 2);
1340
1341 vec![
1342 (
1343 logical_region_1,
1344 RegionPutRequest {
1345 skip_wal: false,
1346 rows: Rows {
1347 schema: schema_1.clone(),
1348 rows: rows_1,
1349 },
1350 hint: None,
1351 partition_expr_version: None,
1352 },
1353 ),
1354 (
1355 logical_region_2,
1356 RegionPutRequest {
1357 skip_wal: false,
1358 rows: Rows {
1359 schema: schema_2.clone(),
1360 rows: rows_2,
1361 },
1362 hint: None,
1363 partition_expr_version: None,
1364 },
1365 ),
1366 ]
1367 };
1368
1369 check_batch_merge_wal_policy(
1370 env,
1371 physical_region_id,
1372 build_requests(),
1373 expect_sparse,
1374 false,
1375 );
1376 check_batch_merge_wal_policy(
1377 env,
1378 physical_region_id,
1379 build_requests(),
1380 expect_sparse,
1381 true,
1382 );
1383
1384 for policies in [[false, true], [true, false]] {
1385 let mut mixed_requests = build_requests();
1386 for ((_, request), skip_wal) in mixed_requests.iter_mut().zip(policies) {
1387 request.skip_wal = skip_wal;
1388 }
1389 let results = env
1390 .metric()
1391 .put_regions_batch(mixed_requests.into_iter())
1392 .await;
1393 assert_eq!(results.len(), 1);
1394 assert_eq!(results[0].request_indices, vec![0, 1]);
1395 let err = results.into_iter().next().unwrap().result.unwrap_err();
1396 assert!(err.to_string().contains("inconsistent WAL policy in batch"));
1397 for logical_region_id in [logical_region_1, logical_region_2] {
1398 assert!(
1399 scan_timestamp_values(&env.metric(), logical_region_id)
1400 .await
1401 .is_empty()
1402 );
1403 }
1404 }
1405
1406 let results = env
1407 .metric()
1408 .put_regions_batch(build_requests().into_iter())
1409 .await;
1410 assert_eq!(results.len(), 1);
1411 let affected_rows = results.into_iter().next().unwrap().result.unwrap();
1412 assert_eq!(affected_rows, 5);
1413
1414 let request = ScanRequest::default();
1415 let stream = env
1416 .mito()
1417 .scan_to_stream(data_region_id, request)
1418 .await
1419 .unwrap();
1420 let batches = RecordBatches::try_collect(stream).await.unwrap();
1421
1422 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 5);
1423 }
1424
1425 #[test]
1426 fn test_sparse_batch_aligns_mixed_field_order() {
1427 let primary_key = PbColumnSchema {
1428 column_name: PRIMARY_KEY_COLUMN_NAME.to_string(),
1429 datatype: ColumnDataType::Binary as i32,
1430 semantic_type: SemanticType::Tag as _,
1431 datatype_extension: None,
1432 options: None,
1433 };
1434 let timestamp = PbColumnSchema {
1435 column_name: greptime_timestamp().to_string(),
1436 datatype: ColumnDataType::TimestampMillisecond as i32,
1437 semantic_type: SemanticType::Timestamp as _,
1438 datatype_extension: None,
1439 options: None,
1440 };
1441 let value = PbColumnSchema {
1442 column_name: greptime_value().to_string(),
1443 datatype: ColumnDataType::Float64 as i32,
1444 semantic_type: SemanticType::Field as _,
1445 datatype_extension: None,
1446 options: None,
1447 };
1448 let histogram = PbColumnSchema {
1449 column_name: greptime_native_histogram().to_string(),
1450 datatype: ColumnDataType::Struct as i32,
1451 semantic_type: SemanticType::Field as _,
1452 datatype_extension: None,
1453 options: None,
1454 };
1455
1456 let sample_rows = Rows {
1457 schema: vec![
1458 primary_key.clone(),
1459 timestamp.clone(),
1460 value.clone(),
1461 histogram.clone(),
1462 ],
1463 rows: vec![Row {
1464 values: vec![
1465 ValueData::BinaryValue(vec![1]).into(),
1466 ValueData::TimestampMillisecondValue(0).into(),
1467 ValueData::F64Value(1.0).into(),
1468 Value { value_data: None },
1469 ],
1470 }],
1471 };
1472 let histogram_rows = Rows {
1473 schema: vec![primary_key, timestamp, histogram, value],
1474 rows: vec![Row {
1475 values: vec![
1476 ValueData::BinaryValue(vec![2]).into(),
1477 ValueData::TimestampMillisecondValue(0).into(),
1478 ValueData::StructValue(api::v1::StructValue { items: vec![] }).into(),
1479 Value { value_data: None },
1480 ],
1481 }],
1482 };
1483
1484 let schema = MetricEngineInner::build_union_schema([
1485 sample_rows.schema.as_slice(),
1486 histogram_rows.schema.as_slice(),
1487 ]);
1488 let merged_rows = MetricEngineInner::align_rows_to_schema(sample_rows, &schema)
1489 .into_iter()
1490 .chain(MetricEngineInner::align_rows_to_schema(
1491 histogram_rows,
1492 &schema,
1493 ))
1494 .collect();
1495 let merged_request = Rows {
1496 schema,
1497 rows: merged_rows,
1498 };
1499
1500 let value_idx = column_index(&merged_request, greptime_value());
1501 let histogram_idx = column_index(&merged_request, greptime_native_histogram());
1502 assert!(matches!(
1503 merged_request.rows[0].values[value_idx].value_data,
1504 Some(ValueData::F64Value(_))
1505 ));
1506 assert!(
1507 merged_request.rows[0].values[histogram_idx]
1508 .value_data
1509 .is_none()
1510 );
1511 assert!(
1512 merged_request.rows[1].values[value_idx]
1513 .value_data
1514 .is_none()
1515 );
1516 assert!(matches!(
1517 merged_request.rows[1].values[histogram_idx].value_data,
1518 Some(ValueData::StructValue(_))
1519 ));
1520 }
1521
1522 #[tokio::test]
1523 async fn test_write_logical_region() {
1524 let env = TestEnv::new().await;
1525 env.init_metric_region().await;
1526
1527 let schema = test_util::row_schema_with_tags(&["job"]);
1529 let rows = test_util::build_rows(1, 5);
1530 let request = RegionRequest::Put(RegionPutRequest {
1531 skip_wal: false,
1532 rows: Rows { schema, rows },
1533 hint: None,
1534 partition_expr_version: None,
1535 });
1536
1537 let logical_region_id = env.default_logical_region_id();
1539 let result = env
1540 .metric()
1541 .handle_request(logical_region_id, request)
1542 .await
1543 .unwrap();
1544 assert_eq!(result.affected_rows, 5);
1545
1546 let physical_region_id = env.default_physical_region_id();
1548 let request = ScanRequest::default();
1549 let stream = env
1550 .metric()
1551 .scan_to_stream(physical_region_id, request)
1552 .await
1553 .unwrap();
1554 let batches = RecordBatches::try_collect(stream).await.unwrap();
1555 let expected = "\
1556+-------------------------+----------------+------------+---------------------+-------+
1557| greptime_timestamp | greptime_value | __table_id | __tsid | job |
1558+-------------------------+----------------+------------+---------------------+-------+
1559| 1970-01-01T00:00:00 | 0.0 | 3 | 2955007454552897459 | tag_0 |
1560| 1970-01-01T00:00:00.001 | 1.0 | 3 | 2955007454552897459 | tag_0 |
1561| 1970-01-01T00:00:00.002 | 2.0 | 3 | 2955007454552897459 | tag_0 |
1562| 1970-01-01T00:00:00.003 | 3.0 | 3 | 2955007454552897459 | tag_0 |
1563| 1970-01-01T00:00:00.004 | 4.0 | 3 | 2955007454552897459 | tag_0 |
1564+-------------------------+----------------+------------+---------------------+-------+";
1565 assert_eq!(expected, batches.pretty_print().unwrap(), "physical region");
1566
1567 let request = ScanRequest::default();
1569 let stream = env
1570 .metric()
1571 .scan_to_stream(logical_region_id, request)
1572 .await
1573 .unwrap();
1574 let batches = RecordBatches::try_collect(stream).await.unwrap();
1575 let expected = "\
1576+-------------------------+----------------+-------+
1577| greptime_timestamp | greptime_value | job |
1578+-------------------------+----------------+-------+
1579| 1970-01-01T00:00:00 | 0.0 | tag_0 |
1580| 1970-01-01T00:00:00.001 | 1.0 | tag_0 |
1581| 1970-01-01T00:00:00.002 | 2.0 | tag_0 |
1582| 1970-01-01T00:00:00.003 | 3.0 | tag_0 |
1583| 1970-01-01T00:00:00.004 | 4.0 | tag_0 |
1584+-------------------------+----------------+-------+";
1585 assert_eq!(expected, batches.pretty_print().unwrap(), "logical region");
1586 }
1587
1588 #[tokio::test]
1589 async fn test_write_logical_region_row_count() {
1590 let env = TestEnv::new().await;
1591 env.init_metric_region().await;
1592 let engine = env.metric();
1593
1594 let logical_region_id = env.default_logical_region_id();
1596 let columns = &["odd", "even", "Ev_En"];
1597 let alter_request = test_util::alter_logical_region_add_tag_columns(123456, columns);
1598 engine
1599 .handle_request(logical_region_id, RegionRequest::Alter(alter_request))
1600 .await
1601 .unwrap();
1602
1603 let schema = test_util::row_schema_with_tags(columns);
1605 let rows = test_util::build_rows(3, 100);
1606 let request = RegionRequest::Put(RegionPutRequest {
1607 skip_wal: false,
1608 rows: Rows { schema, rows },
1609 hint: None,
1610 partition_expr_version: None,
1611 });
1612
1613 let result = engine
1615 .handle_request(logical_region_id, request)
1616 .await
1617 .unwrap();
1618 assert_eq!(100, result.affected_rows);
1619 }
1620
1621 #[tokio::test]
1622 async fn test_write_physical_region() {
1623 let env = TestEnv::new().await;
1624 env.init_metric_region().await;
1625 let engine = env.metric();
1626
1627 let physical_region_id = env.default_physical_region_id();
1628 let schema = test_util::row_schema_with_tags(&["abc"]);
1629 let rows = test_util::build_rows(1, 100);
1630 let request = RegionRequest::Put(RegionPutRequest {
1631 skip_wal: false,
1632 rows: Rows { schema, rows },
1633 hint: None,
1634 partition_expr_version: None,
1635 });
1636
1637 engine
1638 .handle_request(physical_region_id, request)
1639 .await
1640 .unwrap_err();
1641 }
1642
1643 #[tokio::test]
1644 async fn test_write_nonexist_logical_region() {
1645 let env = TestEnv::new().await;
1646 env.init_metric_region().await;
1647 let engine = env.metric();
1648
1649 let logical_region_id = RegionId::new(175, 8345);
1650 let schema = test_util::row_schema_with_tags(&["def"]);
1651 let rows = test_util::build_rows(1, 100);
1652 let request = RegionRequest::Put(RegionPutRequest {
1653 skip_wal: false,
1654 rows: Rows { schema, rows },
1655 hint: None,
1656 partition_expr_version: None,
1657 });
1658
1659 engine
1660 .handle_request(logical_region_id, request)
1661 .await
1662 .unwrap_err();
1663 }
1664
1665 #[tokio::test]
1666 async fn test_batch_write_multiple_logical_regions() {
1667 let env = TestEnv::new().await;
1668 env.init_metric_region().await;
1669 let engine = env.metric();
1670
1671 let physical_region_id = env.default_physical_region_id();
1673 let logical_region_1 = env.default_logical_region_id();
1674 let logical_region_2 = RegionId::new(1024, 1);
1675 let logical_region_3 = RegionId::new(1024, 2);
1676
1677 env.create_logical_region(physical_region_id, logical_region_2)
1678 .await;
1679 env.create_logical_region(physical_region_id, logical_region_3)
1680 .await;
1681
1682 let schema = test_util::row_schema_with_tags(&["job"]);
1684
1685 let rows1 = test_util::build_rows(1, 3);
1690 let mut rows2 = test_util::build_rows(1, 2);
1691 let mut rows3 = test_util::build_rows(1, 5);
1692
1693 use api::v1::value::ValueData;
1695 for (i, row) in rows2.iter_mut().enumerate() {
1696 if let Some(ValueData::TimestampMillisecondValue(ts)) =
1697 row.values.get_mut(0).and_then(|v| v.value_data.as_mut())
1698 {
1699 *ts = (10 + i) as i64;
1700 }
1701 }
1702 for (i, row) in rows3.iter_mut().enumerate() {
1703 if let Some(ValueData::TimestampMillisecondValue(ts)) =
1704 row.values.get_mut(0).and_then(|v| v.value_data.as_mut())
1705 {
1706 *ts = (20 + i) as i64;
1707 }
1708 }
1709
1710 let requests = vec![
1711 (
1712 logical_region_1,
1713 RegionPutRequest {
1714 skip_wal: false,
1715 rows: Rows {
1716 schema: schema.clone(),
1717 rows: rows1,
1718 },
1719 hint: None,
1720 partition_expr_version: None,
1721 },
1722 ),
1723 (
1724 logical_region_2,
1725 RegionPutRequest {
1726 skip_wal: false,
1727 rows: Rows {
1728 schema: schema.clone(),
1729 rows: rows2,
1730 },
1731 hint: None,
1732 partition_expr_version: None,
1733 },
1734 ),
1735 (
1736 logical_region_3,
1737 RegionPutRequest {
1738 skip_wal: false,
1739 rows: Rows {
1740 schema: schema.clone(),
1741 rows: rows3,
1742 },
1743 hint: None,
1744 partition_expr_version: None,
1745 },
1746 ),
1747 ];
1748
1749 let results = engine.put_regions_batch(requests.into_iter()).await;
1751 assert_eq!(results.len(), 1);
1752 let affected_rows = results.into_iter().next().unwrap().result.unwrap();
1753 assert_eq!(affected_rows, 10);
1754
1755 let request = ScanRequest::default();
1757 let stream = env
1758 .metric()
1759 .scan_to_stream(physical_region_id, request)
1760 .await
1761 .unwrap();
1762 let batches = RecordBatches::try_collect(stream).await.unwrap();
1763
1764 assert_eq!(batches.iter().map(|b| b.num_rows()).sum::<usize>(), 10);
1766 }
1767
1768 #[tokio::test]
1769 async fn test_batch_write_with_partial_failure() {
1770 let env = TestEnv::new().await;
1771 env.init_metric_region().await;
1772
1773 let physical = env.default_physical_region_id();
1774 let logical = env.default_logical_region_id();
1775 let other_physical = RegionId::new(4096, 0);
1776 let other_logical = RegionId::new(4097, 0);
1777 env.create_physical_region(other_physical, "batch_results", vec![])
1778 .await;
1779 env.create_logical_region(other_physical, other_logical)
1780 .await;
1781 let request = |index: i64, version| {
1782 let mut rows = test_util::build_rows(1, 1);
1783 rows[0].values[0].value_data =
1784 Some(api::v1::value::ValueData::TimestampMillisecondValue(index));
1785 RegionPutRequest {
1786 rows: Rows {
1787 schema: test_util::row_schema_with_tags(&["job"]),
1788 rows,
1789 },
1790 skip_wal: false,
1791 hint: None,
1792 partition_expr_version: version,
1793 }
1794 };
1795 let mut results = env
1796 .metric()
1797 .put_regions_batch(
1798 vec![
1799 (logical, request(0, None)),
1800 (RegionId::new(9999, 0), request(1, None)),
1801 (other_logical, request(2, Some(1))),
1802 (other_logical, request(3, None)),
1803 (logical, request(4, None)),
1804 (physical, request(5, None)),
1805 ]
1806 .into_iter(),
1807 )
1808 .await;
1809 results.sort_by_key(|result| result.request_indices[0]);
1810 assert_eq!(
1811 results
1812 .iter()
1813 .map(|result| result.request_indices.clone())
1814 .collect::<Vec<_>>(),
1815 vec![vec![0, 4], vec![1], vec![2, 3], vec![5]]
1816 );
1817 assert_eq!(results[0].result.as_ref().unwrap(), &2);
1818 assert!(results[1].result.is_err());
1819 assert!(results[2].result.is_err());
1820 assert!(matches!(
1821 results[3].result,
1822 Err(crate::error::Error::ForbiddenPhysicalWrite { .. })
1823 ));
1824 for (region, expected) in [(physical, 2), (other_physical, 0)] {
1825 let stream = env
1826 .metric()
1827 .scan_to_stream(region, ScanRequest::default())
1828 .await
1829 .unwrap();
1830 let batches = RecordBatches::try_collect(stream).await.unwrap();
1831 assert_eq!(
1832 batches.iter().map(|batch| batch.num_rows()).sum::<usize>(),
1833 expected
1834 );
1835 }
1836 }
1837
1838 #[tokio::test]
1839 async fn test_batch_write_single_physical_region_forbidden() {
1840 let env = TestEnv::new().await;
1841 env.init_metric_region().await;
1842 let engine = env.metric();
1843
1844 let physical_region_id = env.default_physical_region_id();
1845 let schema = test_util::row_schema_with_tags(&["job"]);
1846 let requests = vec![(
1847 physical_region_id,
1848 RegionPutRequest {
1849 skip_wal: false,
1850 rows: Rows {
1851 schema,
1852 rows: test_util::build_rows(1, 1),
1853 },
1854 hint: None,
1855 partition_expr_version: None,
1856 },
1857 )];
1858
1859 let results = engine.put_regions_batch(requests.into_iter()).await;
1860 assert_eq!(results.len(), 1);
1861 assert_eq!(results[0].request_indices, vec![0]);
1862 let err = results.into_iter().next().unwrap().result.unwrap_err();
1863
1864 assert!(matches!(
1865 err,
1866 crate::error::Error::ForbiddenPhysicalWrite { .. }
1867 ));
1868 }
1869
1870 #[tokio::test]
1871 async fn test_batch_write_physical_region_forbidden() {
1872 let env = TestEnv::new().await;
1873 env.init_metric_region().await;
1874 let engine = env.metric();
1875
1876 let physical_region_id = env.default_physical_region_id();
1877 let logical_region_id = env.default_logical_region_id();
1878 let schema = test_util::row_schema_with_tags(&["job"]);
1879 let requests = vec![
1880 (
1881 logical_region_id,
1882 RegionPutRequest {
1883 skip_wal: false,
1884 rows: Rows {
1885 schema: schema.clone(),
1886 rows: test_util::build_rows(1, 1),
1887 },
1888 hint: None,
1889 partition_expr_version: None,
1890 },
1891 ),
1892 (
1893 physical_region_id,
1894 RegionPutRequest {
1895 skip_wal: false,
1896 rows: Rows {
1897 schema,
1898 rows: test_util::build_rows(1, 1),
1899 },
1900 hint: None,
1901 partition_expr_version: None,
1902 },
1903 ),
1904 ];
1905
1906 let mut results = engine.put_regions_batch(requests.into_iter()).await;
1907 results.sort_by_key(|group| group.request_indices[0]);
1908 assert_eq!(results.len(), 2);
1909 assert_eq!(results[0].request_indices, vec![0]);
1910 assert_eq!(results[0].result.as_ref().unwrap(), &1);
1911 assert_eq!(results[1].request_indices, vec![1]);
1912 assert!(matches!(
1913 results[1].result,
1914 Err(crate::error::Error::ForbiddenPhysicalWrite { .. })
1915 ));
1916 }
1917
1918 #[tokio::test]
1919 async fn test_batch_write_single_request_fast_path() {
1920 let env = TestEnv::new().await;
1921 env.init_metric_region().await;
1922 let engine = env.metric();
1923
1924 let logical_region_id = env.default_logical_region_id();
1925 let schema = test_util::row_schema_with_tags(&["job"]);
1926
1927 let requests = vec![(
1929 logical_region_id,
1930 RegionPutRequest {
1931 skip_wal: false,
1932 rows: Rows {
1933 schema,
1934 rows: test_util::build_rows(1, 5),
1935 },
1936 hint: None,
1937 partition_expr_version: None,
1938 },
1939 )];
1940
1941 let results = engine.put_regions_batch(requests.into_iter()).await;
1942 assert_eq!(results.len(), 1);
1943 assert_eq!(results[0].request_indices, vec![0]);
1944 let affected_rows = results.into_iter().next().unwrap().result.unwrap();
1945 assert_eq!(affected_rows, 5);
1946 }
1947
1948 #[tokio::test]
1949 async fn test_batch_write_empty_requests() {
1950 let env = TestEnv::new().await;
1951 env.init_metric_region().await;
1952 let engine = env.metric();
1953
1954 assert!(
1955 engine
1956 .put_regions_batch(vec![].into_iter())
1957 .await
1958 .is_empty()
1959 );
1960 }
1961
1962 #[tokio::test]
1963 async fn test_batch_write_sparse_encoding() {
1964 let env = TestEnv::new().await;
1965 let physical_region_id = env.default_physical_region_id();
1966
1967 run_batch_write_with_schema_variants(
1968 &env,
1969 physical_region_id,
1970 vec![(PRIMARY_KEY_ENCODING.to_string(), "sparse".to_string())],
1971 true,
1972 )
1973 .await;
1974 }
1975
1976 #[tokio::test]
1977 async fn test_batch_write_dense_encoding() {
1978 let env = TestEnv::new().await;
1979 let physical_region_id = env.default_physical_region_id();
1980
1981 run_batch_write_with_schema_variants(
1982 &env,
1983 physical_region_id,
1984 vec![(PRIMARY_KEY_ENCODING.to_string(), "dense".to_string())],
1985 false,
1986 )
1987 .await;
1988 }
1989
1990 #[tokio::test]
1991 async fn test_metric_put_rejects_bad_partition_expr_version() {
1992 let env = TestEnv::new().await;
1993 env.init_metric_region().await;
1994
1995 let logical_region_id = env.default_logical_region_id();
1996 let rows = Rows {
1997 schema: test_util::row_schema_with_tags(&["job"]),
1998 rows: test_util::build_rows(1, 3),
1999 };
2000
2001 let err = env
2002 .metric()
2003 .handle_request(
2004 logical_region_id,
2005 RegionRequest::Put(RegionPutRequest {
2006 skip_wal: false,
2007 rows,
2008 hint: None,
2009 partition_expr_version: Some(1),
2010 }),
2011 )
2012 .await
2013 .unwrap_err();
2014
2015 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2016 }
2017
2018 #[tokio::test]
2019 async fn test_metric_put_respects_staging_partition_expr_version() {
2020 let env = TestEnv::new().await;
2021 env.init_metric_region().await;
2022
2023 let logical_region_id = env.default_logical_region_id();
2024 let physical_region_id = env.default_physical_region_id();
2025 let partition_expr = job_partition_expr_json();
2026 env.metric()
2027 .handle_request(
2028 physical_region_id,
2029 RegionRequest::EnterStaging(EnterStagingRequest {
2030 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(
2031 partition_expr.clone(),
2032 ),
2033 }),
2034 )
2035 .await
2036 .unwrap();
2037
2038 let expected_version = partition_expr_version(Some(&partition_expr));
2039 let rows = Rows {
2040 schema: test_util::row_schema_with_tags(&["job"]),
2041 rows: test_util::build_rows(1, 3),
2042 };
2043
2044 let err = env
2045 .metric()
2046 .handle_request(
2047 logical_region_id,
2048 RegionRequest::Put(RegionPutRequest {
2049 skip_wal: false,
2050 rows: rows.clone(),
2051 hint: None,
2052 partition_expr_version: Some(expected_version.wrapping_add(1)),
2053 }),
2054 )
2055 .await
2056 .unwrap_err();
2057 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2058
2059 let response = env
2060 .metric()
2061 .handle_request(
2062 logical_region_id,
2063 RegionRequest::Put(RegionPutRequest {
2064 skip_wal: false,
2065 rows: rows.clone(),
2066 hint: None,
2067 partition_expr_version: None,
2068 }),
2069 )
2070 .await
2071 .unwrap();
2072 assert_eq!(response.affected_rows, 3);
2073
2074 let response = env
2075 .metric()
2076 .handle_request(
2077 logical_region_id,
2078 RegionRequest::Put(RegionPutRequest {
2079 skip_wal: false,
2080 rows,
2081 hint: None,
2082 partition_expr_version: Some(expected_version),
2083 }),
2084 )
2085 .await
2086 .unwrap();
2087 assert_eq!(response.affected_rows, 3);
2088 }
2089
2090 #[tokio::test]
2094 async fn test_verify_rows_rejects_wrong_type() {
2095 use api::v1::value::ValueData;
2096 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, SemanticType};
2097 use common_query::prelude::{greptime_timestamp, greptime_value};
2098
2099 let env = TestEnv::new().await;
2100 env.init_metric_region().await;
2101
2102 let logical_region_id = env.default_logical_region_id();
2103
2104 let schema = vec![
2107 PbColumnSchema {
2108 column_name: greptime_timestamp().to_string(),
2109 datatype: ColumnDataType::String as i32,
2110 semantic_type: SemanticType::Timestamp as _,
2111 datatype_extension: None,
2112 options: None,
2113 },
2114 PbColumnSchema {
2115 column_name: greptime_value().to_string(),
2116 datatype: ColumnDataType::Float64 as i32,
2117 semantic_type: SemanticType::Field as _,
2118 datatype_extension: None,
2119 options: None,
2120 },
2121 PbColumnSchema {
2122 column_name: "job".to_string(),
2123 datatype: ColumnDataType::String as i32,
2124 semantic_type: SemanticType::Tag as _,
2125 datatype_extension: None,
2126 options: None,
2127 },
2128 ];
2129 let rows = vec![Row {
2130 values: vec![
2131 Value {
2132 value_data: Some(ValueData::StringValue("not-a-timestamp".to_string())),
2133 },
2134 Value {
2135 value_data: Some(ValueData::F64Value(1.0)),
2136 },
2137 Value {
2138 value_data: Some(ValueData::StringValue("tag_0".to_string())),
2139 },
2140 ],
2141 }];
2142
2143 let err = env
2144 .metric()
2145 .handle_request(
2146 logical_region_id,
2147 RegionRequest::Put(RegionPutRequest {
2148 skip_wal: false,
2149 rows: Rows { schema, rows },
2150 hint: None,
2151 partition_expr_version: None,
2152 }),
2153 )
2154 .await
2155 .unwrap_err();
2156 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2157 }
2158
2159 #[tokio::test]
2163 async fn test_verify_rows_rejects_missing_time_index() {
2164 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, SemanticType};
2165 use common_query::prelude::greptime_value;
2166
2167 let env = TestEnv::new().await;
2168 env.init_metric_region().await;
2169
2170 let logical_region_id = env.default_logical_region_id();
2171
2172 let schema = vec![
2174 PbColumnSchema {
2175 column_name: greptime_value().to_string(),
2176 datatype: ColumnDataType::Float64 as i32,
2177 semantic_type: SemanticType::Field as _,
2178 datatype_extension: None,
2179 options: None,
2180 },
2181 PbColumnSchema {
2182 column_name: "job".to_string(),
2183 datatype: ColumnDataType::String as i32,
2184 semantic_type: SemanticType::Tag as _,
2185 datatype_extension: None,
2186 options: None,
2187 },
2188 ];
2189 let rows = vec![Row {
2190 values: vec![
2191 Value {
2192 value_data: Some(api::v1::value::ValueData::F64Value(1.0)),
2193 },
2194 Value {
2195 value_data: Some(api::v1::value::ValueData::StringValue("tag_0".to_string())),
2196 },
2197 ],
2198 }];
2199
2200 let err = env
2201 .metric()
2202 .handle_request(
2203 logical_region_id,
2204 RegionRequest::Put(RegionPutRequest {
2205 skip_wal: false,
2206 rows: Rows { schema, rows },
2207 hint: None,
2208 partition_expr_version: None,
2209 }),
2210 )
2211 .await
2212 .unwrap_err();
2213 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2214 }
2215
2216 #[tokio::test]
2217 async fn test_verify_rows_rejects_missing_field() {
2218 use api::v1::value::ValueData;
2219 use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, SemanticType};
2220 use common_query::prelude::greptime_timestamp;
2221
2222 let env = TestEnv::new().await;
2223 env.init_metric_region().await;
2224
2225 let logical_region_id = env.default_logical_region_id();
2226
2227 let schema = vec![
2229 PbColumnSchema {
2230 column_name: greptime_timestamp().to_string(),
2231 datatype: ColumnDataType::TimestampMillisecond as i32,
2232 semantic_type: SemanticType::Timestamp as _,
2233 datatype_extension: None,
2234 options: None,
2235 },
2236 PbColumnSchema {
2237 column_name: "job".to_string(),
2238 datatype: ColumnDataType::String as i32,
2239 semantic_type: SemanticType::Tag as _,
2240 datatype_extension: None,
2241 options: None,
2242 },
2243 ];
2244 let rows = vec![Row {
2245 values: vec![
2246 Value {
2247 value_data: Some(ValueData::TimestampMillisecondValue(0)),
2248 },
2249 Value {
2250 value_data: Some(ValueData::StringValue("tag_0".to_string())),
2251 },
2252 ],
2253 }];
2254
2255 let err = env
2256 .metric()
2257 .handle_request(
2258 logical_region_id,
2259 RegionRequest::Put(RegionPutRequest {
2260 skip_wal: false,
2261 rows: Rows { schema, rows },
2262 hint: None,
2263 partition_expr_version: None,
2264 }),
2265 )
2266 .await
2267 .unwrap_err();
2268 let message = err.to_string();
2269 assert!(
2270 message.contains("missing required field column"),
2271 "expected field-completeness rejection, got: {message}"
2272 );
2273 assert_eq!(err.status_code(), StatusCode::InvalidArguments);
2274 }
2275
2276 #[test]
2277 fn test_fill_missing_field_column_nullable_no_default() {
2278 let field_meta = ColumnMetadata {
2279 column_id: 1,
2280 semantic_type: SemanticType::Field,
2281 column_schema: ColumnSchema::new(
2282 "greptime_value".to_string(),
2283 ConcreteDataType::float64_datatype(),
2284 true, ),
2286 };
2287 let mut rows = Rows {
2288 schema: vec![PbColumnSchema {
2289 column_name: "ts".to_string(),
2290 datatype: ColumnDataType::TimestampMillisecond as i32,
2291 semantic_type: SemanticType::Timestamp as _,
2292 datatype_extension: None,
2293 options: None,
2294 }],
2295 rows: vec![Row {
2296 values: vec![Value {
2297 value_data: Some(ValueData::TimestampMillisecondValue(0)),
2298 }],
2299 }],
2300 };
2301
2302 MetricEngineInner::fill_missing_field_column(
2303 RegionId::new(1, 1),
2304 "greptime_value",
2305 &field_meta,
2306 &mut rows,
2307 )
2308 .unwrap();
2309
2310 assert_eq!(rows.schema.len(), 2);
2311 assert_eq!(rows.schema[1].column_name, "greptime_value");
2312 assert_eq!(rows.rows[0].values.len(), 2);
2313 assert!(
2314 rows.rows[0].values[1].value_data.is_none(),
2315 "missing nullable field should be filled with null"
2316 );
2317 }
2318
2319 #[test]
2320 fn test_fill_missing_field_column_rejects_impure_default() {
2321 let field_meta = ColumnMetadata {
2322 column_id: 1,
2323 semantic_type: SemanticType::Field,
2324 column_schema: ColumnSchema::new(
2325 "greptime_value".to_string(),
2326 ConcreteDataType::timestamp_millisecond_datatype(),
2327 false,
2328 )
2329 .with_default_constraint(Some(ColumnDefaultConstraint::Function("now()".to_string())))
2330 .unwrap(),
2331 };
2332 let mut rows = Rows {
2333 schema: vec![PbColumnSchema {
2334 column_name: "ts".to_string(),
2335 datatype: api::v1::ColumnDataType::TimestampMillisecond as i32,
2336 semantic_type: SemanticType::Timestamp as _,
2337 datatype_extension: None,
2338 options: None,
2339 }],
2340 rows: vec![Row {
2341 values: vec![Value {
2342 value_data: Some(ValueData::TimestampMillisecondValue(0)),
2343 }],
2344 }],
2345 };
2346
2347 let err = MetricEngineInner::fill_missing_field_column(
2348 RegionId::new(1, 1),
2349 "greptime_value",
2350 &field_meta,
2351 &mut rows,
2352 )
2353 .unwrap_err();
2354 assert!(
2355 err.to_string().contains("impure default value"),
2356 "expected impure-default rejection, got: {err}"
2357 );
2358 }
2359
2360 #[tokio::test]
2361 async fn test_logical_region_ddl_needs_leader_lease() {
2362 common_telemetry::init_default_ut_logging();
2363 let mut mito_env = MitoTestEnv::with_prefix("logical-region-lease").await;
2364 let listener = Arc::new(WriteWalListener::default());
2365 let mito = mito_env
2366 .create_engine_with(MitoConfig::default(), None, Some(listener.clone()), None)
2367 .await;
2368 mito.require_write_lease();
2369 let start = Instant::now();
2370 mito.freeze_lease_clock(start);
2371 let env = TestEnv::with_mito_engine(mito_env, mito.clone());
2372 let engine = env.metric();
2373 let physical_region_id = env.default_physical_region_id();
2375 env.create_physical_region(physical_region_id, &TestEnv::default_table_dir(), vec![])
2376 .await;
2377 let at = |n| start + n * Duration::from_secs(10);
2380 let grant = |n| {
2381 engine
2382 .set_region_role_with_lease(physical_region_id, RegionRole::Leader, at(n))
2383 .unwrap()
2384 };
2385 let expire = |n| mito.freeze_lease_clock(at(n));
2386 let logical_region_id = env.default_logical_region_id();
2387 let alter = |tag| RegionRequest::Alter(test_util::alter_logical_region_request(&[tag]));
2388 let drop_request = || {
2389 RegionRequest::Drop(RegionDropRequest {
2390 fast_path: false,
2391 force: false,
2392 partial_drop: false,
2393 })
2394 };
2395 let put = |tags: &[&str]| {
2396 let request = RegionRequest::Put(RegionPutRequest {
2397 rows: Rows {
2398 schema: test_util::row_schema_with_tags(tags),
2399 rows: test_util::build_rows(tags.len(), 5),
2400 },
2401 hint: None,
2402 partition_expr_version: None,
2403 skip_wal: false,
2404 });
2405 engine.handle_request(logical_region_id, request)
2406 };
2407 let expire_after_append = async |request, n, blocks_fills: bool| {
2411 listener.pause_next();
2412 let mut task = {
2413 let engine = engine.clone();
2414 tokio::spawn(async move { engine.handle_request(logical_region_id, request).await })
2415 };
2416 tokio::select! {
2417 () = listener.wait_appended() => {}
2418 result = &mut task => panic!("the request finished before its append: {result:?}"),
2419 }
2420 if blocks_fills {
2421 let metadata_region = &engine.inner.metadata_region;
2422 let fill = metadata_region.read_lock_logical_region(logical_region_id);
2423 assert!(fill.now_or_never().is_none());
2424 }
2425 expire(n);
2426 listener.resume();
2427 task.await.unwrap()
2428 };
2429
2430 let create = || {
2433 RegionRequest::Create(test_util::create_logical_region_request(
2434 &["job"],
2435 physical_region_id,
2436 &table_dir("test", logical_region_id.table_id()),
2437 ))
2438 };
2439 let err = engine
2440 .handle_request(logical_region_id, create())
2441 .await
2442 .unwrap_err();
2443 assert!(err.is_retryable(), "{err:?}");
2444 grant(1);
2445 engine
2446 .handle_request(logical_region_id, create())
2447 .await
2448 .unwrap();
2449 put(&["job"]).await.unwrap();
2450
2451 let err = expire_after_append(alter("tag1"), 1, false)
2453 .await
2454 .unwrap_err();
2455 assert!(err.is_retryable(), "{err:?}");
2456 let metadata = engine.get_metadata(logical_region_id).await.unwrap();
2457 assert!(metadata.column_by_name("tag1").is_some());
2458
2459 let err = engine
2460 .handle_request(logical_region_id, alter("tag2"))
2461 .await
2462 .unwrap_err();
2463 assert!(err.is_retryable(), "{err:?}");
2464 grant(2);
2465 engine
2466 .handle_request(logical_region_id, alter("tag2"))
2467 .await
2468 .unwrap();
2469 put(&["job", "tag1", "tag2"]).await.unwrap();
2470
2471 let err = expire_after_append(drop_request(), 2, true)
2473 .await
2474 .unwrap_err();
2475 assert!(err.is_retryable(), "{err:?}");
2476 grant(3);
2477 put(&["job"]).await.unwrap_err();
2478 engine
2479 .handle_request(logical_region_id, drop_request())
2480 .await
2481 .unwrap();
2482 }
2483}