1mod extract_new_columns;
16
17use std::collections::{HashMap, HashSet};
18
19use api::v1::SemanticType;
20use common_query::native_histogram::is_native_histogram_value_schema;
21use common_telemetry::info;
22use common_time::{FOREVER, Timestamp};
23use datatypes::data_type::ConcreteDataType;
24use datatypes::schema::{ColumnSchema, SkippingIndexOptions};
25use datatypes::value::Value;
26use mito2::engine::MITO_ENGINE_NAME;
27use snafu::{OptionExt, ResultExt, ensure};
28use store_api::metadata::ColumnMetadata;
29use store_api::metric_engine_consts::{
30 ALTER_PHYSICAL_EXTENSION_KEY, DATA_REGION_SUBDIR, DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
31 DATA_SCHEMA_TSID_COLUMN_NAME, LOGICAL_TABLE_METADATA_KEY, METADATA_REGION_SUBDIR,
32 METADATA_SCHEMA_KEY_COLUMN_INDEX, METADATA_SCHEMA_KEY_COLUMN_NAME,
33 METADATA_SCHEMA_TIMESTAMP_COLUMN_INDEX, METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
34 METADATA_SCHEMA_VALUE_COLUMN_INDEX, METADATA_SCHEMA_VALUE_COLUMN_NAME,
35 is_metric_engine_internal_column,
36};
37use store_api::mito_engine_options::{TTL_KEY, WAL_OPTIONS_KEY};
38use store_api::region_engine::RegionEngine;
39use store_api::region_request::{AffectedRows, PathType, RegionCreateRequest, RegionRequest};
40use store_api::storage::RegionId;
41use store_api::storage::consts::ReservedColumnId;
42
43use crate::engine::MetricEngineInner;
44use crate::engine::create::extract_new_columns::extract_new_columns;
45use crate::engine::options::{PhysicalRegionOptions, set_data_region_options};
46use crate::error::{
47 ColumnTypeMismatchSnafu, ConflictRegionOptionSnafu, CreateMitoRegionSnafu,
48 InternalColumnOccupiedSnafu, InvalidMetadataSnafu, MissingRegionOptionSnafu,
49 MultipleFieldColumnSnafu, NoFieldColumnSnafu, ParseRegionIdSnafu, PhysicalRegionNotFoundSnafu,
50 Result, SerializeColumnMetadataSnafu, UnexpectedRequestSnafu,
51};
52use crate::metrics::PHYSICAL_REGION_COUNT;
53use crate::utils::{
54 self, append_manifest_info, encode_manifest_info_to_extensions, to_data_region_id,
55 to_metadata_region_id,
56};
57
58const DEFAULT_TABLE_ID_SKIPPING_INDEX_GRANULARITY: u32 = 1024;
59const DEFAULT_TABLE_ID_SKIPPING_INDEX_FALSE_POSITIVE_RATE: f64 = 0.01;
60
61impl MetricEngineInner {
62 pub async fn create_regions(
63 &self,
64 mut requests: Vec<(RegionId, RegionCreateRequest)>,
65 extension_return_value: &mut HashMap<String, Vec<u8>>,
66 ) -> Result<AffectedRows> {
67 if requests.is_empty() {
68 return Ok(0);
69 }
70
71 for (_, request) in requests.iter() {
72 Self::verify_region_create_request(request)?;
73 }
74
75 let first_request = &requests.first().unwrap().1;
76 if first_request.is_physical_table() {
77 ensure!(
78 requests.len() == 1,
79 UnexpectedRequestSnafu {
80 reason: "Physical table must be created with single request".to_string(),
81 }
82 );
83 let (region_id, request) = requests.pop().unwrap();
84 self.create_physical_region(region_id, request, extension_return_value)
85 .await?;
86
87 return Ok(0);
88 } else if first_request
89 .options
90 .contains_key(LOGICAL_TABLE_METADATA_KEY)
91 {
92 if requests.len() == 1 {
93 let request = &requests.first().unwrap().1;
94 let physical_region_id = parse_physical_region_id(request)?;
95 let mut manifest_infos = Vec::with_capacity(1);
96 self.create_logical_regions(physical_region_id, requests, extension_return_value)
97 .await?;
98 append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos);
99 encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?;
100 } else {
101 let grouped_requests =
102 group_create_logical_region_requests_by_physical_region_id(requests)?;
103 let mut manifest_infos = Vec::with_capacity(grouped_requests.len());
104 for (physical_region_id, requests) in grouped_requests {
105 self.create_logical_regions(
106 physical_region_id,
107 requests,
108 extension_return_value,
109 )
110 .await?;
111 append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos);
112 }
113 encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?;
114 }
115 } else {
116 return MissingRegionOptionSnafu {}.fail();
117 }
118
119 Ok(0)
120 }
121
122 async fn create_physical_region(
124 &self,
125 region_id: RegionId,
126 request: RegionCreateRequest,
127 extension_return_value: &mut HashMap<String, Vec<u8>>,
128 ) -> Result<()> {
129 let physical_region_options = PhysicalRegionOptions::try_from(&request.options)?;
130 let (data_region_id, metadata_region_id) = Self::transform_region_id(region_id);
131
132 let create_metadata_region_request = self.create_request_for_metadata_region(&request);
134 self.mito
135 .handle_request(
136 metadata_region_id,
137 RegionRequest::Create(create_metadata_region_request),
138 )
139 .await
140 .with_context(|_| CreateMitoRegionSnafu {
141 region_type: METADATA_REGION_SUBDIR,
142 })?;
143
144 let create_data_region_request = self.create_request_for_data_region(&request);
146 let physical_columns = create_data_region_request
147 .column_metadatas
148 .iter()
149 .map(|metadata| (metadata.column_schema.name.clone(), metadata.clone()))
150 .collect::<HashMap<_, _>>();
151 let time_index_unit = create_data_region_request
152 .column_metadatas
153 .iter()
154 .find_map(|metadata| {
155 if metadata.semantic_type == SemanticType::Timestamp {
156 metadata
157 .column_schema
158 .data_type
159 .as_timestamp()
160 .map(|data_type| data_type.unit())
161 } else {
162 None
163 }
164 })
165 .context(UnexpectedRequestSnafu {
166 reason: "No time index column found",
167 })?;
168 let response = self
169 .mito
170 .handle_request(
171 data_region_id,
172 RegionRequest::Create(create_data_region_request),
173 )
174 .await
175 .with_context(|_| CreateMitoRegionSnafu {
176 region_type: DATA_REGION_SUBDIR,
177 })?;
178 let primary_key_encoding = self.mito.get_primary_key_encoding(data_region_id).context(
179 PhysicalRegionNotFoundSnafu {
180 region_id: data_region_id,
181 },
182 )?;
183 extension_return_value.extend(response.extensions);
184
185 info!(
186 "Created physical metric region {region_id}, primary key encoding={primary_key_encoding}, physical_region_options={physical_region_options:?}"
187 );
188 PHYSICAL_REGION_COUNT.inc();
189
190 self.state.write().unwrap().add_physical_region(
192 data_region_id,
193 physical_columns,
194 primary_key_encoding,
195 physical_region_options,
196 time_index_unit,
197 );
198
199 Ok(())
200 }
201
202 async fn create_logical_regions(
204 &self,
205 physical_region_id: RegionId,
206 requests: Vec<(RegionId, RegionCreateRequest)>,
207 extension_return_value: &mut HashMap<String, Vec<u8>>,
208 ) -> Result<()> {
209 let data_region_id = utils::to_data_region_id(physical_region_id);
210
211 let unit = self
212 .state
213 .read()
214 .unwrap()
215 .physical_region_time_index_unit(physical_region_id)
216 .context(PhysicalRegionNotFoundSnafu {
217 region_id: data_region_id,
218 })?;
219 for (_, request) in &requests {
221 let time_index_column = request
223 .column_metadatas
224 .iter()
225 .find(|col| col.semantic_type == SemanticType::Timestamp)
226 .unwrap();
227 let request_unit = time_index_column
228 .column_schema
229 .data_type
230 .as_timestamp()
231 .unwrap()
232 .unit();
233 ensure!(
234 request_unit == unit,
235 UnexpectedRequestSnafu {
236 reason: format!(
237 "Metric has differenttime unit ({:?}) than the physical region ({:?})",
238 request_unit, unit
239 ),
240 }
241 );
242 }
243
244 let requests = {
246 let state = self.state.read().unwrap();
247 let mut skipped = Vec::with_capacity(requests.len());
248 let mut kept_requests = Vec::with_capacity(requests.len());
249
250 for (region_id, request) in requests {
251 if state.is_logical_region_exist(region_id) {
252 skipped.push(region_id);
253 } else {
254 kept_requests.push((region_id, request));
255 }
256 }
257
258 if !skipped.is_empty() {
260 info!(
261 "Skipped creating logical regions {skipped:?} because they already exist",
262 skipped = skipped
263 );
264 }
265 kept_requests
266 };
267
268 let mut new_column_names = HashSet::new();
270 let mut new_columns = Vec::new();
271
272 let index_option = {
273 let state = &self.state.read().unwrap();
274 let region_state = state
275 .physical_region_states()
276 .get(&data_region_id)
277 .with_context(|| PhysicalRegionNotFoundSnafu {
278 region_id: data_region_id,
279 })?;
280 let physical_columns = region_state.physical_columns();
281
282 extract_new_columns(
283 &requests,
284 physical_columns,
285 &mut new_column_names,
286 &mut new_columns,
287 )?;
288 region_state.options().index
289 };
290
291 self.data_region
293 .add_columns(data_region_id, new_columns, index_option)
294 .await?;
295
296 let physical_columns = self.data_region.physical_columns(data_region_id).await?;
297 let physical_schema_map = physical_columns
298 .iter()
299 .map(|metadata| (metadata.column_schema.name.as_str(), metadata))
300 .collect::<HashMap<_, _>>();
301 let logical_regions = requests
302 .iter()
303 .map(|(region_id, _)| *region_id)
304 .collect::<Vec<_>>();
305 let logical_region_columns = requests.iter().map(|(region_id, request)| {
306 (
307 *region_id,
308 request
309 .column_metadatas
310 .iter()
311 .map(|metadata| {
312 let column_metadata = *physical_schema_map
314 .get(metadata.column_schema.name.as_str())
315 .unwrap();
316 (metadata.column_schema.name.as_str(), column_metadata)
317 })
318 .collect::<HashMap<_, _>>(),
319 )
320 });
321
322 let new_add_columns = new_column_names.iter().map(|name| {
323 let column_metadata = *physical_schema_map.get(name).unwrap();
325 (name.to_string(), column_metadata.clone())
326 });
327
328 extension_return_value.insert(
329 ALTER_PHYSICAL_EXTENSION_KEY.to_string(),
330 ColumnMetadata::encode_list(&physical_columns).context(SerializeColumnMetadataSnafu)?,
331 );
332
333 self.metadata_region
335 .add_logical_regions(physical_region_id, true, logical_region_columns)
336 .await?;
337
338 {
339 let mut state = self.state.write().unwrap();
340 state.add_physical_columns(data_region_id, new_add_columns);
341 state.add_logical_regions(physical_region_id, logical_regions.clone());
342 }
343 for logical_region_id in logical_regions {
344 self.metadata_region
345 .open_logical_region(logical_region_id)
346 .await;
347 }
348
349 Ok(())
350 }
351
352 fn verify_region_create_request(request: &RegionCreateRequest) -> Result<()> {
357 request.validate().context(InvalidMetadataSnafu)?;
358
359 let name_to_index = request
360 .column_metadatas
361 .iter()
362 .enumerate()
363 .map(|(idx, metadata)| (metadata.column_schema.name.clone(), idx))
364 .collect::<HashMap<String, usize>>();
365
366 let table_id_col_def = request.column_metadatas.iter().any(is_metric_name_col);
367 let tsid_col_def = request.column_metadatas.iter().any(is_tsid_col);
368
369 ensure!(
371 !name_to_index.contains_key(DATA_SCHEMA_TABLE_ID_COLUMN_NAME) || table_id_col_def,
372 InternalColumnOccupiedSnafu {
373 column: DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
374 }
375 );
376 ensure!(
377 !name_to_index.contains_key(DATA_SCHEMA_TSID_COLUMN_NAME) || tsid_col_def,
378 InternalColumnOccupiedSnafu {
379 column: DATA_SCHEMA_TSID_COLUMN_NAME,
380 }
381 );
382
383 ensure!(
385 request.is_physical_table() || request.options.contains_key(LOGICAL_TABLE_METADATA_KEY),
386 MissingRegionOptionSnafu {}
387 );
388 ensure!(
389 !(request.is_physical_table()
390 && request.options.contains_key(LOGICAL_TABLE_METADATA_KEY)),
391 ConflictRegionOptionSnafu {}
392 );
393
394 let mut field_cols = Vec::new();
396 for col in &request.column_metadatas {
397 if is_metric_engine_internal_column(&col.column_schema.name) {
399 continue;
400 }
401 match col.semantic_type {
402 SemanticType::Tag => ensure!(
403 col.column_schema.data_type == ConcreteDataType::string_datatype(),
404 ColumnTypeMismatchSnafu {
405 expect: ConcreteDataType::string_datatype(),
406 actual: col.column_schema.data_type.clone(),
407 }
408 ),
409 SemanticType::Field => {
410 field_cols.push(col);
411 }
412 SemanticType::Timestamp => {}
413 }
414 }
415 let [field_col] = field_cols.as_slice() else {
416 if field_cols.is_empty() {
417 NoFieldColumnSnafu.fail()?;
418 }
419 return MultipleFieldColumnSnafu {
420 previous: field_cols[0].column_schema.name.clone(),
421 current: field_cols[1].column_schema.name.clone(),
422 }
423 .fail();
424 };
425
426 if is_native_histogram_value_schema(
427 &field_col.column_schema.name,
428 &field_col.column_schema.data_type,
429 ) {
430 return Ok(());
431 }
432
433 ensure!(
435 field_col.column_schema.data_type == ConcreteDataType::float64_datatype(),
436 ColumnTypeMismatchSnafu {
437 expect: ConcreteDataType::float64_datatype(),
438 actual: field_col.column_schema.data_type.clone(),
439 }
440 );
441
442 Ok(())
443 }
444
445 fn transform_region_id(region_id: RegionId) -> (RegionId, RegionId) {
449 (
450 to_data_region_id(region_id),
451 to_metadata_region_id(region_id),
452 )
453 }
454
455 pub fn create_request_for_metadata_region(
459 &self,
460 request: &RegionCreateRequest,
461 ) -> RegionCreateRequest {
462 let timestamp_column_metadata = ColumnMetadata {
464 column_id: METADATA_SCHEMA_TIMESTAMP_COLUMN_INDEX as _,
465 semantic_type: SemanticType::Timestamp,
466 column_schema: ColumnSchema::new(
467 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
468 ConcreteDataType::timestamp_millisecond_datatype(),
469 false,
470 )
471 .with_default_constraint(Some(datatypes::schema::ColumnDefaultConstraint::Value(
472 Value::Timestamp(Timestamp::new_millisecond(0)),
473 )))
474 .unwrap(),
475 };
476 let key_column_metadata = ColumnMetadata {
478 column_id: METADATA_SCHEMA_KEY_COLUMN_INDEX as _,
479 semantic_type: SemanticType::Tag,
480 column_schema: ColumnSchema::new(
481 METADATA_SCHEMA_KEY_COLUMN_NAME,
482 ConcreteDataType::string_datatype(),
483 false,
484 ),
485 };
486 let value_column_metadata = ColumnMetadata {
488 column_id: METADATA_SCHEMA_VALUE_COLUMN_INDEX as _,
489 semantic_type: SemanticType::Field,
490 column_schema: ColumnSchema::new(
491 METADATA_SCHEMA_VALUE_COLUMN_NAME,
492 ConcreteDataType::string_datatype(),
493 true,
494 ),
495 };
496
497 let options = region_options_for_metadata_region(&request.options);
498 RegionCreateRequest {
499 engine: MITO_ENGINE_NAME.to_string(),
500 column_metadatas: vec![
501 timestamp_column_metadata,
502 key_column_metadata,
503 value_column_metadata,
504 ],
505 primary_key: vec![METADATA_SCHEMA_KEY_COLUMN_INDEX as _],
506 options,
507 table_dir: request.table_dir.clone(),
508 path_type: PathType::Metadata,
509 partition_expr_json: Some("".to_string()),
510 requirements: request.requirements,
511 }
512 }
513
514 pub fn create_request_for_data_region(
521 &self,
522 request: &RegionCreateRequest,
523 ) -> RegionCreateRequest {
524 let mut data_region_request = request.clone();
525 let mut primary_key = vec![ReservedColumnId::table_id(), ReservedColumnId::tsid()];
526
527 data_region_request.table_dir = request.table_dir.clone();
528 data_region_request.path_type = PathType::Data;
529
530 let table_id_col_def = request.column_metadatas.iter().any(is_metric_name_col);
531 let tsid_col_def = request.column_metadatas.iter().any(is_tsid_col);
532
533 data_region_request
535 .column_metadatas
536 .iter_mut()
537 .for_each(|metadata| {
538 if metadata.semantic_type == SemanticType::Tag
539 && !is_metric_name_col(metadata)
540 && !is_tsid_col(metadata)
541 {
542 metadata.column_schema.set_nullable();
543 primary_key.push(metadata.column_id);
544 }
545 });
546
547 if !table_id_col_def {
549 data_region_request.column_metadatas.push(table_id_col());
550 }
551 if !tsid_col_def {
552 data_region_request.column_metadatas.push(tsid_col());
553 }
554 data_region_request.primary_key = primary_key;
555
556 set_data_region_options(&mut data_region_request.options);
558
559 data_region_request
560 }
561}
562
563fn table_id_col() -> ColumnMetadata {
564 ColumnMetadata {
565 column_id: ReservedColumnId::table_id(),
566 semantic_type: SemanticType::Tag,
567 column_schema: ColumnSchema::new(
568 DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
569 ConcreteDataType::uint32_datatype(),
570 false,
571 )
572 .with_skipping_options(SkippingIndexOptions::new_unchecked(
573 DEFAULT_TABLE_ID_SKIPPING_INDEX_GRANULARITY,
574 DEFAULT_TABLE_ID_SKIPPING_INDEX_FALSE_POSITIVE_RATE,
575 datatypes::schema::SkippingIndexType::BloomFilter,
576 ))
577 .unwrap(),
578 }
579}
580
581fn tsid_col() -> ColumnMetadata {
582 ColumnMetadata {
583 column_id: ReservedColumnId::tsid(),
584 semantic_type: SemanticType::Tag,
585 column_schema: ColumnSchema::new(
586 DATA_SCHEMA_TSID_COLUMN_NAME,
587 ConcreteDataType::uint64_datatype(),
588 false,
589 )
590 .with_inverted_index(false),
591 }
592}
593
594pub(crate) fn is_metric_name_col(column: &ColumnMetadata) -> bool {
596 column.column_id == ReservedColumnId::table_id()
597 && column.semantic_type == SemanticType::Tag
598 && column.column_schema.data_type == ConcreteDataType::uint32_datatype()
599 && column.column_schema.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME
600 && !column.column_schema.is_nullable()
601}
602
603pub(crate) fn is_tsid_col(column: &ColumnMetadata) -> bool {
605 column.column_id == ReservedColumnId::tsid()
606 && column.semantic_type == SemanticType::Tag
607 && column.column_schema.data_type == ConcreteDataType::uint64_datatype()
608 && column.column_schema.name == DATA_SCHEMA_TSID_COLUMN_NAME
609 && !column.column_schema.is_nullable()
610}
611
612fn group_create_logical_region_requests_by_physical_region_id(
614 requests: Vec<(RegionId, RegionCreateRequest)>,
615) -> Result<HashMap<RegionId, Vec<(RegionId, RegionCreateRequest)>>> {
616 let mut result = HashMap::with_capacity(requests.len());
617 for (region_id, request) in requests {
618 let physical_region_id = parse_physical_region_id(&request)?;
619 result
620 .entry(physical_region_id)
621 .or_insert_with(Vec::new)
622 .push((region_id, request));
623 }
624
625 Ok(result)
626}
627
628fn parse_physical_region_id(request: &RegionCreateRequest) -> Result<RegionId> {
630 let physical_region_id_raw = request
631 .options
632 .get(LOGICAL_TABLE_METADATA_KEY)
633 .ok_or(MissingRegionOptionSnafu {}.build())?;
634
635 let physical_region_id: RegionId = physical_region_id_raw
636 .parse::<u64>()
637 .with_context(|_| ParseRegionIdSnafu {
638 raw: physical_region_id_raw,
639 })?
640 .into();
641
642 Ok(physical_region_id)
643}
644
645pub(crate) fn region_options_for_metadata_region(
647 original: &HashMap<String, String>,
648) -> HashMap<String, String> {
649 let mut metadata_region_options = HashMap::new();
650 metadata_region_options.insert(TTL_KEY.to_string(), FOREVER.to_string());
651
652 if let Some(wal_options) = original.get(WAL_OPTIONS_KEY) {
653 metadata_region_options.insert(WAL_OPTIONS_KEY.to_string(), wal_options.clone());
654 }
655
656 metadata_region_options
657}
658
659#[cfg(test)]
660mod test {
661 use common_meta::ddl::test_util::assert_column_name_and_id;
662 use common_meta::ddl::utils::{parse_column_metadatas, parse_manifest_infos_from_extensions};
663 use common_query::native_histogram::{NATIVE_HISTOGRAM_FIELD, native_histogram_value_type};
664 use common_query::prelude::{greptime_timestamp, greptime_value};
665 use store_api::metric_engine_consts::{METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY};
666 use store_api::region_request::{BatchRegionDdlRequest, RegionRequirements};
667
668 use super::*;
669 use crate::config::EngineConfig;
670 use crate::engine::MetricEngine;
671 use crate::test_util::{TestEnv, create_logical_region_request};
672
673 #[test]
674 fn test_internal_column_metadata() {
675 let table_id_col = table_id_col();
676 let tsid_col = tsid_col();
677 assert!(is_metric_name_col(&table_id_col));
678 assert!(is_tsid_col(&tsid_col));
679 }
680
681 #[test]
682 fn test_verify_region_create_request() {
683 let request = RegionCreateRequest {
685 column_metadatas: vec![
686 ColumnMetadata {
687 column_id: 0,
688 semantic_type: SemanticType::Timestamp,
689 column_schema: ColumnSchema::new(
690 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
691 ConcreteDataType::timestamp_millisecond_datatype(),
692 false,
693 ),
694 },
695 ColumnMetadata {
696 column_id: 1,
697 semantic_type: SemanticType::Tag,
698 column_schema: ColumnSchema::new(
699 DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
700 ConcreteDataType::uint32_datatype(),
701 false,
702 ),
703 },
704 ],
705 table_dir: "test_dir".to_string(),
706 path_type: PathType::Bare,
707 engine: METRIC_ENGINE_NAME.to_string(),
708 primary_key: vec![],
709 options: HashMap::new(),
710 partition_expr_json: Some("".to_string()),
711 requirements: RegionRequirements::object_storage(),
712 };
713 let result = MetricEngineInner::verify_region_create_request(&request);
714 assert!(result.is_err());
715 assert_eq!(
716 result.unwrap_err().to_string(),
717 "Internal column __table_id is reserved".to_string()
718 );
719
720 let request = RegionCreateRequest {
722 column_metadatas: vec![
723 ColumnMetadata {
724 column_id: 0,
725 semantic_type: SemanticType::Timestamp,
726 column_schema: ColumnSchema::new(
727 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
728 ConcreteDataType::timestamp_millisecond_datatype(),
729 false,
730 ),
731 },
732 ColumnMetadata {
733 column_id: 1,
734 semantic_type: SemanticType::Tag,
735 column_schema: ColumnSchema::new(
736 "column1".to_string(),
737 ConcreteDataType::string_datatype(),
738 false,
739 ),
740 },
741 ColumnMetadata {
742 column_id: 2,
743 semantic_type: SemanticType::Field,
744 column_schema: ColumnSchema::new(
745 "column2".to_string(),
746 ConcreteDataType::float64_datatype(),
747 false,
748 ),
749 },
750 table_id_col(),
751 tsid_col(),
752 ],
753 table_dir: "test_dir".to_string(),
754 path_type: PathType::Bare,
755 engine: METRIC_ENGINE_NAME.to_string(),
756 primary_key: vec![],
757 options: [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
758 .into_iter()
759 .collect(),
760 partition_expr_json: Some("".to_string()),
761 requirements: Default::default(),
762 };
763 MetricEngineInner::verify_region_create_request(&request).unwrap();
764
765 let request = RegionCreateRequest {
767 column_metadatas: vec![
768 ColumnMetadata {
769 column_id: 0,
770 semantic_type: SemanticType::Timestamp,
771 column_schema: ColumnSchema::new(
772 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
773 ConcreteDataType::timestamp_millisecond_datatype(),
774 false,
775 ),
776 },
777 ColumnMetadata {
778 column_id: 1,
779 semantic_type: SemanticType::Tag,
780 column_schema: ColumnSchema::new(
781 "column1".to_string(),
782 ConcreteDataType::string_datatype(),
783 false,
784 ),
785 },
786 ColumnMetadata {
787 column_id: 2,
788 semantic_type: SemanticType::Field,
789 column_schema: ColumnSchema::new(
790 "column2".to_string(),
791 ConcreteDataType::float64_datatype(),
792 false,
793 ),
794 },
795 ],
796 table_dir: "test_dir".to_string(),
797 path_type: PathType::Bare,
798 engine: METRIC_ENGINE_NAME.to_string(),
799 primary_key: vec![],
800 options: [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
801 .into_iter()
802 .collect(),
803 partition_expr_json: Some("".to_string()),
804 requirements: Default::default(),
805 };
806 MetricEngineInner::verify_region_create_request(&request).unwrap();
807 }
808
809 #[test]
810 fn test_verify_region_create_request_options() {
811 let mut request = RegionCreateRequest {
812 column_metadatas: vec![
813 ColumnMetadata {
814 column_id: 0,
815 semantic_type: SemanticType::Timestamp,
816 column_schema: ColumnSchema::new(
817 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
818 ConcreteDataType::timestamp_millisecond_datatype(),
819 false,
820 ),
821 },
822 ColumnMetadata {
823 column_id: 1,
824 semantic_type: SemanticType::Field,
825 column_schema: ColumnSchema::new(
826 "val".to_string(),
827 ConcreteDataType::float64_datatype(),
828 false,
829 ),
830 },
831 ],
832 table_dir: "test_dir".to_string(),
833 path_type: PathType::Bare,
834 engine: METRIC_ENGINE_NAME.to_string(),
835 primary_key: vec![],
836 options: HashMap::new(),
837 partition_expr_json: Some("".to_string()),
838 requirements: Default::default(),
839 };
840 MetricEngineInner::verify_region_create_request(&request).unwrap_err();
841
842 let mut options = HashMap::new();
843 options.insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), "value".to_string());
844 request.options.clone_from(&options);
845 MetricEngineInner::verify_region_create_request(&request).unwrap();
846
847 options.insert(LOGICAL_TABLE_METADATA_KEY.to_string(), "value".to_string());
848 request.options.clone_from(&options);
849 MetricEngineInner::verify_region_create_request(&request).unwrap_err();
850
851 options.remove(PHYSICAL_TABLE_METADATA_KEY).unwrap();
852 request.options = options;
853 MetricEngineInner::verify_region_create_request(&request).unwrap();
854 }
855
856 #[test]
857 fn test_verify_region_create_request_native_histogram_fields() {
858 let native_histogram_columns = vec![
859 ColumnMetadata {
860 column_id: 0,
861 semantic_type: SemanticType::Timestamp,
862 column_schema: ColumnSchema::new(
863 greptime_timestamp(),
864 ConcreteDataType::timestamp_millisecond_datatype(),
865 false,
866 ),
867 },
868 ColumnMetadata {
869 column_id: 1,
870 semantic_type: SemanticType::Tag,
871 column_schema: ColumnSchema::new("job", ConcreteDataType::string_datatype(), true),
872 },
873 ColumnMetadata {
874 column_id: 2,
875 semantic_type: SemanticType::Field,
876 column_schema: ColumnSchema::new(
877 NATIVE_HISTOGRAM_FIELD,
878 native_histogram_value_type().clone(),
879 true,
880 ),
881 },
882 ];
883 let request = RegionCreateRequest {
884 column_metadatas: native_histogram_columns,
885 table_dir: "test_dir".to_string(),
886 path_type: PathType::Bare,
887 engine: METRIC_ENGINE_NAME.to_string(),
888 primary_key: vec![],
889 options: [(
890 LOGICAL_TABLE_METADATA_KEY.to_string(),
891 "physical".to_string(),
892 )]
893 .into_iter()
894 .collect(),
895 partition_expr_json: Some("".to_string()),
896 requirements: Default::default(),
897 };
898 MetricEngineInner::verify_region_create_request(&request).unwrap();
899
900 let request = RegionCreateRequest {
901 column_metadatas: vec![
902 ColumnMetadata {
903 column_id: 0,
904 semantic_type: SemanticType::Timestamp,
905 column_schema: ColumnSchema::new(
906 greptime_timestamp(),
907 ConcreteDataType::timestamp_millisecond_datatype(),
908 false,
909 ),
910 },
911 ColumnMetadata {
912 column_id: 1,
913 semantic_type: SemanticType::Field,
914 column_schema: ColumnSchema::new(
915 "value_a",
916 ConcreteDataType::float64_datatype(),
917 true,
918 ),
919 },
920 ColumnMetadata {
921 column_id: 2,
922 semantic_type: SemanticType::Field,
923 column_schema: ColumnSchema::new(
924 "value_b",
925 ConcreteDataType::float64_datatype(),
926 true,
927 ),
928 },
929 ],
930 table_dir: "test_dir".to_string(),
931 path_type: PathType::Bare,
932 engine: METRIC_ENGINE_NAME.to_string(),
933 primary_key: vec![],
934 options: [(
935 LOGICAL_TABLE_METADATA_KEY.to_string(),
936 "physical".to_string(),
937 )]
938 .into_iter()
939 .collect(),
940 partition_expr_json: Some("".to_string()),
941 requirements: Default::default(),
942 };
943 assert!(MetricEngineInner::verify_region_create_request(&request).is_err());
944 }
945
946 #[tokio::test]
947 async fn test_create_request_for_physical_regions() {
948 let options: HashMap<_, _> = [
950 ("ttl".to_string(), "60m".to_string()),
951 ("skip_wal".to_string(), "true".to_string()),
952 ]
953 .into_iter()
954 .collect();
955 let request = RegionCreateRequest {
956 engine: METRIC_ENGINE_NAME.to_string(),
957 column_metadatas: vec![
958 ColumnMetadata {
959 column_id: 0,
960 semantic_type: SemanticType::Timestamp,
961 column_schema: ColumnSchema::new(
962 "timestamp",
963 ConcreteDataType::timestamp_millisecond_datatype(),
964 false,
965 ),
966 },
967 ColumnMetadata {
968 column_id: 1,
969 semantic_type: SemanticType::Tag,
970 column_schema: ColumnSchema::new(
971 "tag",
972 ConcreteDataType::string_datatype(),
973 false,
974 ),
975 },
976 ],
977 primary_key: vec![0],
978 options,
979 table_dir: "/test_dir".to_string(),
980 path_type: PathType::Bare,
981 partition_expr_json: Some("".to_string()),
982 requirements: RegionRequirements::object_storage(),
983 };
984
985 let env = TestEnv::new().await;
987 let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap();
988 let engine_inner = engine.inner;
989
990 let data_region_request = engine_inner.create_request_for_data_region(&request);
992 assert_eq!(data_region_request.table_dir, "/test_dir".to_string());
993 assert_eq!(data_region_request.path_type, PathType::Data);
994 assert_eq!(data_region_request.column_metadatas.len(), 4);
995 assert_eq!(
996 data_region_request.primary_key,
997 vec![ReservedColumnId::table_id(), ReservedColumnId::tsid(), 1]
998 );
999 assert!(data_region_request.options.contains_key("ttl"));
1000 assert_eq!(
1001 data_region_request.requirements,
1002 RegionRequirements::object_storage()
1003 );
1004
1005 let metadata_region_request = engine_inner.create_request_for_metadata_region(&request);
1007 assert_eq!(metadata_region_request.table_dir, "/test_dir".to_string());
1008 assert_eq!(metadata_region_request.path_type, PathType::Metadata);
1009 assert_eq!(
1010 metadata_region_request.options.get("ttl").unwrap(),
1011 "forever"
1012 );
1013 assert!(!metadata_region_request.options.contains_key("skip_wal"));
1014 assert_eq!(
1015 metadata_region_request.requirements,
1016 RegionRequirements::object_storage()
1017 );
1018 }
1019
1020 #[tokio::test]
1021 async fn test_create_request_for_physical_regions_with_internal_columns() {
1022 let options: HashMap<_, _> = [
1023 ("ttl".to_string(), "60m".to_string()),
1024 ("skip_wal".to_string(), "true".to_string()),
1025 ]
1026 .into_iter()
1027 .collect();
1028 let request = RegionCreateRequest {
1029 engine: METRIC_ENGINE_NAME.to_string(),
1030 column_metadatas: vec![
1031 ColumnMetadata {
1032 column_id: 0,
1033 semantic_type: SemanticType::Timestamp,
1034 column_schema: ColumnSchema::new(
1035 "timestamp",
1036 ConcreteDataType::timestamp_millisecond_datatype(),
1037 false,
1038 ),
1039 },
1040 ColumnMetadata {
1041 column_id: 1,
1042 semantic_type: SemanticType::Tag,
1043 column_schema: ColumnSchema::new(
1044 "tag",
1045 ConcreteDataType::string_datatype(),
1046 false,
1047 ),
1048 },
1049 ColumnMetadata {
1050 column_id: 2,
1051 semantic_type: SemanticType::Field,
1052 column_schema: ColumnSchema::new(
1053 "value",
1054 ConcreteDataType::float64_datatype(),
1055 false,
1056 ),
1057 },
1058 table_id_col(),
1059 tsid_col(),
1060 ],
1061 primary_key: vec![0],
1062 options,
1063 table_dir: "/test_dir".to_string(),
1064 path_type: PathType::Bare,
1065 partition_expr_json: Some("".to_string()),
1066 requirements: Default::default(),
1067 };
1068
1069 let env = TestEnv::new().await;
1070 let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap();
1071 let engine_inner = engine.inner;
1072
1073 let data_region_request = engine_inner.create_request_for_data_region(&request);
1074 assert_eq!(data_region_request.column_metadatas.len(), 5);
1075 assert_eq!(
1076 data_region_request.primary_key,
1077 vec![ReservedColumnId::table_id(), ReservedColumnId::tsid(), 1]
1078 );
1079
1080 let table_id_count = data_region_request
1081 .column_metadatas
1082 .iter()
1083 .filter(|metadata| metadata.column_schema.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
1084 .count();
1085 let tsid_count = data_region_request
1086 .column_metadatas
1087 .iter()
1088 .filter(|metadata| metadata.column_schema.name == DATA_SCHEMA_TSID_COLUMN_NAME)
1089 .count();
1090 assert_eq!(table_id_count, 1);
1091 assert_eq!(tsid_count, 1);
1092
1093 let tag_metadata = data_region_request
1094 .column_metadatas
1095 .iter()
1096 .find(|metadata| metadata.column_schema.name == "tag")
1097 .unwrap();
1098 assert!(tag_metadata.column_schema.is_nullable());
1099
1100 let table_id_metadata = data_region_request
1101 .column_metadatas
1102 .iter()
1103 .find(|metadata| metadata.column_schema.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
1104 .unwrap();
1105 assert!(is_metric_name_col(table_id_metadata));
1106
1107 let tsid_metadata = data_region_request
1108 .column_metadatas
1109 .iter()
1110 .find(|metadata| metadata.column_schema.name == DATA_SCHEMA_TSID_COLUMN_NAME)
1111 .unwrap();
1112 assert!(is_tsid_col(tsid_metadata));
1113 }
1114
1115 #[tokio::test]
1116 async fn test_create_logical_regions() {
1117 let env = TestEnv::new().await;
1118 let engine = env.metric();
1119 let physical_region_id1 = RegionId::new(1024, 0);
1120 let physical_region_id2 = RegionId::new(1024, 1);
1121 let logical_region_id1 = RegionId::new(1025, 0);
1122 let logical_region_id2 = RegionId::new(1025, 1);
1123 env.create_physical_region(physical_region_id1, "/test_dir1", vec![])
1124 .await;
1125 env.create_physical_region(physical_region_id2, "/test_dir2", vec![])
1126 .await;
1127
1128 let region_create_request1 =
1129 create_logical_region_request(&["job"], physical_region_id1, "logical1");
1130 let region_create_request2 =
1131 create_logical_region_request(&["job"], physical_region_id2, "logical2");
1132
1133 let response = engine
1134 .handle_batch_ddl_requests(BatchRegionDdlRequest::Create(vec![
1135 (logical_region_id1, region_create_request1),
1136 (logical_region_id2, region_create_request2),
1137 ]))
1138 .await
1139 .unwrap();
1140
1141 let manifest_infos = parse_manifest_infos_from_extensions(&response.extensions).unwrap();
1142 assert_eq!(manifest_infos.len(), 2);
1143 let region_ids = manifest_infos.into_iter().map(|i| i.0).collect::<Vec<_>>();
1144 assert!(region_ids.contains(&physical_region_id1));
1145 assert!(region_ids.contains(&physical_region_id2));
1146
1147 let column_metadatas =
1148 parse_column_metadatas(&response.extensions, ALTER_PHYSICAL_EXTENSION_KEY).unwrap();
1149 assert_column_name_and_id(
1150 &column_metadatas,
1151 &[
1152 (greptime_timestamp(), 0),
1153 (greptime_value(), 1),
1154 ("__table_id", ReservedColumnId::table_id()),
1155 ("__tsid", ReservedColumnId::tsid()),
1156 ("job", 2),
1157 ],
1158 );
1159 }
1160}