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