1mod extract_new_columns;
16
17use std::collections::HashMap;
18
19use api::v1::SemanticType;
20use common_query::native_histogram::is_native_histogram_value_type;
21use common_telemetry::info;
22use common_time::{FOREVER, Timestamp};
23use datatypes::data_type::ConcreteDataType;
24use datatypes::schema::{COMPRESSION_KEY, 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_indices = HashMap::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_indices,
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_indices.keys().map(|name| {
323 let column_metadata = *physical_schema_map.get(name).unwrap();
325 (name.to_string(), column_metadata.clone())
326 });
327 self.state
329 .write()
330 .unwrap()
331 .add_physical_columns(data_region_id, new_add_columns);
332
333 extension_return_value.insert(
334 ALTER_PHYSICAL_EXTENSION_KEY.to_string(),
335 ColumnMetadata::encode_list(&physical_columns).context(SerializeColumnMetadataSnafu)?,
336 );
337
338 self.metadata_region
340 .add_logical_regions(physical_region_id, true, logical_region_columns)
341 .await?;
342
343 self.state
344 .write()
345 .unwrap()
346 .add_logical_regions(physical_region_id, logical_regions.clone());
347 for logical_region_id in logical_regions {
348 self.metadata_region
349 .open_logical_region(logical_region_id)
350 .await;
351 }
352
353 Ok(())
354 }
355
356 fn verify_region_create_request(request: &RegionCreateRequest) -> Result<()> {
361 request.validate().context(InvalidMetadataSnafu)?;
362
363 let name_to_index = request
364 .column_metadatas
365 .iter()
366 .enumerate()
367 .map(|(idx, metadata)| (metadata.column_schema.name.clone(), idx))
368 .collect::<HashMap<String, usize>>();
369
370 let table_id_col_def = request.column_metadatas.iter().any(is_metric_name_col);
371 let tsid_col_def = request.column_metadatas.iter().any(is_tsid_col);
372
373 ensure!(
375 !name_to_index.contains_key(DATA_SCHEMA_TABLE_ID_COLUMN_NAME) || table_id_col_def,
376 InternalColumnOccupiedSnafu {
377 column: DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
378 }
379 );
380 ensure!(
381 !name_to_index.contains_key(DATA_SCHEMA_TSID_COLUMN_NAME) || tsid_col_def,
382 InternalColumnOccupiedSnafu {
383 column: DATA_SCHEMA_TSID_COLUMN_NAME,
384 }
385 );
386
387 ensure!(
389 request.is_physical_table() || request.options.contains_key(LOGICAL_TABLE_METADATA_KEY),
390 MissingRegionOptionSnafu {}
391 );
392 ensure!(
393 !(request.is_physical_table()
394 && request.options.contains_key(LOGICAL_TABLE_METADATA_KEY)),
395 ConflictRegionOptionSnafu {}
396 );
397
398 if request.options.contains_key(LOGICAL_TABLE_METADATA_KEY) {
399 for column in &request.column_metadatas {
400 ensure!(
401 !column.column_schema.metadata().contains_key(COMPRESSION_KEY),
402 UnexpectedRequestSnafu {
403 reason: "COMPRESSION is not supported on logical tables; configure it on the physical table".to_string(),
404 }
405 );
406 }
407 }
408
409 let mut field_cols = Vec::new();
411 for col in &request.column_metadatas {
412 if is_metric_engine_internal_column(&col.column_schema.name) {
414 continue;
415 }
416 match col.semantic_type {
417 SemanticType::Tag => ensure!(
418 col.column_schema.data_type == ConcreteDataType::string_datatype(),
419 ColumnTypeMismatchSnafu {
420 expect: ConcreteDataType::string_datatype(),
421 actual: col.column_schema.data_type.clone(),
422 }
423 ),
424 SemanticType::Field => {
425 field_cols.push(col);
426 }
427 SemanticType::Timestamp => {}
428 }
429 }
430 let [field_col] = field_cols.as_slice() else {
431 if field_cols.is_empty() {
432 NoFieldColumnSnafu.fail()?;
433 }
434 return MultipleFieldColumnSnafu {
435 previous: field_cols[0].column_schema.name.clone(),
436 current: field_cols[1].column_schema.name.clone(),
437 }
438 .fail();
439 };
440
441 if is_native_histogram_value_type(&field_col.column_schema.data_type) {
442 return Ok(());
443 }
444
445 ensure!(
447 field_col.column_schema.data_type == ConcreteDataType::float64_datatype(),
448 ColumnTypeMismatchSnafu {
449 expect: ConcreteDataType::float64_datatype(),
450 actual: field_col.column_schema.data_type.clone(),
451 }
452 );
453
454 Ok(())
455 }
456
457 fn transform_region_id(region_id: RegionId) -> (RegionId, RegionId) {
461 (
462 to_data_region_id(region_id),
463 to_metadata_region_id(region_id),
464 )
465 }
466
467 pub fn create_request_for_metadata_region(
471 &self,
472 request: &RegionCreateRequest,
473 ) -> RegionCreateRequest {
474 let timestamp_column_metadata = ColumnMetadata {
476 column_id: METADATA_SCHEMA_TIMESTAMP_COLUMN_INDEX as _,
477 semantic_type: SemanticType::Timestamp,
478 column_schema: ColumnSchema::new(
479 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
480 ConcreteDataType::timestamp_millisecond_datatype(),
481 false,
482 )
483 .with_default_constraint(Some(datatypes::schema::ColumnDefaultConstraint::Value(
484 Value::Timestamp(Timestamp::new_millisecond(0)),
485 )))
486 .unwrap(),
487 };
488 let key_column_metadata = ColumnMetadata {
490 column_id: METADATA_SCHEMA_KEY_COLUMN_INDEX as _,
491 semantic_type: SemanticType::Tag,
492 column_schema: ColumnSchema::new(
493 METADATA_SCHEMA_KEY_COLUMN_NAME,
494 ConcreteDataType::string_datatype(),
495 false,
496 ),
497 };
498 let value_column_metadata = ColumnMetadata {
500 column_id: METADATA_SCHEMA_VALUE_COLUMN_INDEX as _,
501 semantic_type: SemanticType::Field,
502 column_schema: ColumnSchema::new(
503 METADATA_SCHEMA_VALUE_COLUMN_NAME,
504 ConcreteDataType::string_datatype(),
505 true,
506 ),
507 };
508
509 let options = region_options_for_metadata_region(&request.options);
510 RegionCreateRequest {
511 engine: MITO_ENGINE_NAME.to_string(),
512 column_metadatas: vec![
513 timestamp_column_metadata,
514 key_column_metadata,
515 value_column_metadata,
516 ],
517 primary_key: vec![METADATA_SCHEMA_KEY_COLUMN_INDEX as _],
518 options,
519 table_dir: request.table_dir.clone(),
520 path_type: PathType::Metadata,
521 partition_expr_json: Some("".to_string()),
522 requirements: request.requirements,
523 }
524 }
525
526 pub fn create_request_for_data_region(
533 &self,
534 request: &RegionCreateRequest,
535 ) -> RegionCreateRequest {
536 let mut data_region_request = request.clone();
537 let mut primary_key = vec![ReservedColumnId::table_id(), ReservedColumnId::tsid()];
538
539 data_region_request.table_dir = request.table_dir.clone();
540 data_region_request.path_type = PathType::Data;
541
542 let table_id_col_def = request.column_metadatas.iter().any(is_metric_name_col);
543 let tsid_col_def = request.column_metadatas.iter().any(is_tsid_col);
544
545 data_region_request
547 .column_metadatas
548 .iter_mut()
549 .for_each(|metadata| {
550 if metadata.semantic_type == SemanticType::Tag
551 && !is_metric_name_col(metadata)
552 && !is_tsid_col(metadata)
553 {
554 metadata.column_schema.set_nullable();
555 primary_key.push(metadata.column_id);
556 }
557 });
558
559 if !table_id_col_def {
561 data_region_request.column_metadatas.push(table_id_col());
562 }
563 if !tsid_col_def {
564 data_region_request.column_metadatas.push(tsid_col());
565 }
566 data_region_request.primary_key = primary_key;
567
568 set_data_region_options(&mut data_region_request.options);
570
571 data_region_request
572 }
573}
574
575fn table_id_col() -> ColumnMetadata {
576 ColumnMetadata {
577 column_id: ReservedColumnId::table_id(),
578 semantic_type: SemanticType::Tag,
579 column_schema: ColumnSchema::new(
580 DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
581 ConcreteDataType::uint32_datatype(),
582 false,
583 )
584 .with_skipping_options(SkippingIndexOptions::new_unchecked(
585 DEFAULT_TABLE_ID_SKIPPING_INDEX_GRANULARITY,
586 DEFAULT_TABLE_ID_SKIPPING_INDEX_FALSE_POSITIVE_RATE,
587 datatypes::schema::SkippingIndexType::BloomFilter,
588 ))
589 .unwrap(),
590 }
591}
592
593fn tsid_col() -> ColumnMetadata {
594 ColumnMetadata {
595 column_id: ReservedColumnId::tsid(),
596 semantic_type: SemanticType::Tag,
597 column_schema: ColumnSchema::new(
598 DATA_SCHEMA_TSID_COLUMN_NAME,
599 ConcreteDataType::uint64_datatype(),
600 false,
601 )
602 .with_inverted_index(false),
603 }
604}
605
606pub(crate) fn is_metric_name_col(column: &ColumnMetadata) -> bool {
608 column.column_id == ReservedColumnId::table_id()
609 && column.semantic_type == SemanticType::Tag
610 && column.column_schema.data_type == ConcreteDataType::uint32_datatype()
611 && column.column_schema.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME
612 && !column.column_schema.is_nullable()
613}
614
615pub(crate) fn is_tsid_col(column: &ColumnMetadata) -> bool {
617 column.column_id == ReservedColumnId::tsid()
618 && column.semantic_type == SemanticType::Tag
619 && column.column_schema.data_type == ConcreteDataType::uint64_datatype()
620 && column.column_schema.name == DATA_SCHEMA_TSID_COLUMN_NAME
621 && !column.column_schema.is_nullable()
622}
623
624fn group_create_logical_region_requests_by_physical_region_id(
626 requests: Vec<(RegionId, RegionCreateRequest)>,
627) -> Result<HashMap<RegionId, Vec<(RegionId, RegionCreateRequest)>>> {
628 let mut result = HashMap::with_capacity(requests.len());
629 for (region_id, request) in requests {
630 let physical_region_id = parse_physical_region_id(&request)?;
631 result
632 .entry(physical_region_id)
633 .or_insert_with(Vec::new)
634 .push((region_id, request));
635 }
636
637 Ok(result)
638}
639
640fn parse_physical_region_id(request: &RegionCreateRequest) -> Result<RegionId> {
642 let physical_region_id_raw = request
643 .options
644 .get(LOGICAL_TABLE_METADATA_KEY)
645 .ok_or(MissingRegionOptionSnafu {}.build())?;
646
647 let physical_region_id: RegionId = physical_region_id_raw
648 .parse::<u64>()
649 .with_context(|_| ParseRegionIdSnafu {
650 raw: physical_region_id_raw,
651 })?
652 .into();
653
654 Ok(physical_region_id)
655}
656
657pub(crate) fn region_options_for_metadata_region(
659 original: &HashMap<String, String>,
660) -> HashMap<String, String> {
661 let mut metadata_region_options = HashMap::new();
662 metadata_region_options.insert(TTL_KEY.to_string(), FOREVER.to_string());
663
664 if let Some(wal_options) = original.get(WAL_OPTIONS_KEY) {
665 metadata_region_options.insert(WAL_OPTIONS_KEY.to_string(), wal_options.clone());
666 }
667
668 metadata_region_options
669}
670
671#[cfg(test)]
672mod test {
673 use common_meta::ddl::test_util::assert_column_name_and_id;
674 use common_meta::ddl::utils::{parse_column_metadatas, parse_manifest_infos_from_extensions};
675 use common_query::native_histogram::native_histogram_value_type;
676 use common_query::prelude::{greptime_native_histogram, greptime_timestamp, greptime_value};
677 use store_api::metric_engine_consts::{METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY};
678 use store_api::region_request::{BatchRegionDdlRequest, RegionRequirements};
679
680 use super::*;
681 use crate::config::EngineConfig;
682 use crate::engine::MetricEngine;
683 use crate::test_util::{TestEnv, create_logical_region_request};
684
685 #[test]
686 fn test_internal_column_metadata() {
687 let table_id_col = table_id_col();
688 let tsid_col = tsid_col();
689 assert!(is_metric_name_col(&table_id_col));
690 assert!(is_tsid_col(&tsid_col));
691 }
692
693 #[test]
694 fn test_verify_region_create_request() {
695 let request = RegionCreateRequest {
697 column_metadatas: vec![
698 ColumnMetadata {
699 column_id: 0,
700 semantic_type: SemanticType::Timestamp,
701 column_schema: ColumnSchema::new(
702 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
703 ConcreteDataType::timestamp_millisecond_datatype(),
704 false,
705 ),
706 },
707 ColumnMetadata {
708 column_id: 1,
709 semantic_type: SemanticType::Tag,
710 column_schema: ColumnSchema::new(
711 DATA_SCHEMA_TABLE_ID_COLUMN_NAME,
712 ConcreteDataType::uint32_datatype(),
713 false,
714 ),
715 },
716 ],
717 table_dir: "test_dir".to_string(),
718 path_type: PathType::Bare,
719 engine: METRIC_ENGINE_NAME.to_string(),
720 primary_key: vec![],
721 options: HashMap::new(),
722 partition_expr_json: Some("".to_string()),
723 requirements: RegionRequirements::object_storage(),
724 };
725 let result = MetricEngineInner::verify_region_create_request(&request);
726 assert!(result.is_err());
727 assert_eq!(
728 result.unwrap_err().to_string(),
729 "Internal column __table_id is reserved".to_string()
730 );
731
732 let request = RegionCreateRequest {
734 column_metadatas: vec![
735 ColumnMetadata {
736 column_id: 0,
737 semantic_type: SemanticType::Timestamp,
738 column_schema: ColumnSchema::new(
739 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
740 ConcreteDataType::timestamp_millisecond_datatype(),
741 false,
742 ),
743 },
744 ColumnMetadata {
745 column_id: 1,
746 semantic_type: SemanticType::Tag,
747 column_schema: ColumnSchema::new(
748 "column1".to_string(),
749 ConcreteDataType::string_datatype(),
750 false,
751 ),
752 },
753 ColumnMetadata {
754 column_id: 2,
755 semantic_type: SemanticType::Field,
756 column_schema: ColumnSchema::new(
757 "column2".to_string(),
758 ConcreteDataType::float64_datatype(),
759 false,
760 ),
761 },
762 table_id_col(),
763 tsid_col(),
764 ],
765 table_dir: "test_dir".to_string(),
766 path_type: PathType::Bare,
767 engine: METRIC_ENGINE_NAME.to_string(),
768 primary_key: vec![],
769 options: [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
770 .into_iter()
771 .collect(),
772 partition_expr_json: Some("".to_string()),
773 requirements: Default::default(),
774 };
775 MetricEngineInner::verify_region_create_request(&request).unwrap();
776
777 let request = RegionCreateRequest {
779 column_metadatas: vec![
780 ColumnMetadata {
781 column_id: 0,
782 semantic_type: SemanticType::Timestamp,
783 column_schema: ColumnSchema::new(
784 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
785 ConcreteDataType::timestamp_millisecond_datatype(),
786 false,
787 ),
788 },
789 ColumnMetadata {
790 column_id: 1,
791 semantic_type: SemanticType::Tag,
792 column_schema: ColumnSchema::new(
793 "column1".to_string(),
794 ConcreteDataType::string_datatype(),
795 false,
796 ),
797 },
798 ColumnMetadata {
799 column_id: 2,
800 semantic_type: SemanticType::Field,
801 column_schema: ColumnSchema::new(
802 "column2".to_string(),
803 ConcreteDataType::float64_datatype(),
804 false,
805 ),
806 },
807 ],
808 table_dir: "test_dir".to_string(),
809 path_type: PathType::Bare,
810 engine: METRIC_ENGINE_NAME.to_string(),
811 primary_key: vec![],
812 options: [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
813 .into_iter()
814 .collect(),
815 partition_expr_json: Some("".to_string()),
816 requirements: Default::default(),
817 };
818 MetricEngineInner::verify_region_create_request(&request).unwrap();
819 }
820
821 #[test]
822 fn test_verify_region_create_request_options() {
823 let mut request = RegionCreateRequest {
824 column_metadatas: vec![
825 ColumnMetadata {
826 column_id: 0,
827 semantic_type: SemanticType::Timestamp,
828 column_schema: ColumnSchema::new(
829 METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME,
830 ConcreteDataType::timestamp_millisecond_datatype(),
831 false,
832 ),
833 },
834 ColumnMetadata {
835 column_id: 1,
836 semantic_type: SemanticType::Field,
837 column_schema: ColumnSchema::new(
838 "val".to_string(),
839 ConcreteDataType::float64_datatype(),
840 false,
841 ),
842 },
843 ],
844 table_dir: "test_dir".to_string(),
845 path_type: PathType::Bare,
846 engine: METRIC_ENGINE_NAME.to_string(),
847 primary_key: vec![],
848 options: HashMap::new(),
849 partition_expr_json: Some("".to_string()),
850 requirements: Default::default(),
851 };
852 MetricEngineInner::verify_region_create_request(&request).unwrap_err();
853
854 let mut options = HashMap::new();
855 options.insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), "value".to_string());
856 request.options.clone_from(&options);
857 MetricEngineInner::verify_region_create_request(&request).unwrap();
858
859 options.insert(LOGICAL_TABLE_METADATA_KEY.to_string(), "value".to_string());
860 request.options.clone_from(&options);
861 MetricEngineInner::verify_region_create_request(&request).unwrap_err();
862
863 options.remove(PHYSICAL_TABLE_METADATA_KEY).unwrap();
864 request.options = options;
865 MetricEngineInner::verify_region_create_request(&request).unwrap();
866 }
867
868 #[test]
869 fn test_verify_region_create_request_native_histogram_fields() {
870 let native_histogram_columns = vec![
871 ColumnMetadata {
872 column_id: 0,
873 semantic_type: SemanticType::Timestamp,
874 column_schema: ColumnSchema::new(
875 greptime_timestamp(),
876 ConcreteDataType::timestamp_millisecond_datatype(),
877 false,
878 ),
879 },
880 ColumnMetadata {
881 column_id: 1,
882 semantic_type: SemanticType::Tag,
883 column_schema: ColumnSchema::new("job", ConcreteDataType::string_datatype(), true),
884 },
885 ColumnMetadata {
886 column_id: 2,
887 semantic_type: SemanticType::Field,
888 column_schema: ColumnSchema::new(
889 greptime_native_histogram(),
890 native_histogram_value_type().clone(),
891 true,
892 ),
893 },
894 ];
895 let request = RegionCreateRequest {
896 column_metadatas: native_histogram_columns,
897 table_dir: "test_dir".to_string(),
898 path_type: PathType::Bare,
899 engine: METRIC_ENGINE_NAME.to_string(),
900 primary_key: vec![],
901 options: [(
902 LOGICAL_TABLE_METADATA_KEY.to_string(),
903 "physical".to_string(),
904 )]
905 .into_iter()
906 .collect(),
907 partition_expr_json: Some("".to_string()),
908 requirements: Default::default(),
909 };
910 MetricEngineInner::verify_region_create_request(&request).unwrap();
911
912 let request = RegionCreateRequest {
913 column_metadatas: vec![
914 ColumnMetadata {
915 column_id: 0,
916 semantic_type: SemanticType::Timestamp,
917 column_schema: ColumnSchema::new(
918 greptime_timestamp(),
919 ConcreteDataType::timestamp_millisecond_datatype(),
920 false,
921 ),
922 },
923 ColumnMetadata {
924 column_id: 1,
925 semantic_type: SemanticType::Field,
926 column_schema: ColumnSchema::new(
927 "value_a",
928 ConcreteDataType::float64_datatype(),
929 true,
930 ),
931 },
932 ColumnMetadata {
933 column_id: 2,
934 semantic_type: SemanticType::Field,
935 column_schema: ColumnSchema::new(
936 "value_b",
937 ConcreteDataType::float64_datatype(),
938 true,
939 ),
940 },
941 ],
942 table_dir: "test_dir".to_string(),
943 path_type: PathType::Bare,
944 engine: METRIC_ENGINE_NAME.to_string(),
945 primary_key: vec![],
946 options: [(
947 LOGICAL_TABLE_METADATA_KEY.to_string(),
948 "physical".to_string(),
949 )]
950 .into_iter()
951 .collect(),
952 partition_expr_json: Some("".to_string()),
953 requirements: Default::default(),
954 };
955 assert!(MetricEngineInner::verify_region_create_request(&request).is_err());
956 }
957
958 #[tokio::test]
959 async fn test_create_request_for_physical_regions() {
960 let options: HashMap<_, _> = [
962 ("ttl".to_string(), "60m".to_string()),
963 ("skip_wal".to_string(), "true".to_string()),
964 ]
965 .into_iter()
966 .collect();
967 let request = RegionCreateRequest {
968 engine: METRIC_ENGINE_NAME.to_string(),
969 column_metadatas: vec![
970 ColumnMetadata {
971 column_id: 0,
972 semantic_type: SemanticType::Timestamp,
973 column_schema: ColumnSchema::new(
974 "timestamp",
975 ConcreteDataType::timestamp_millisecond_datatype(),
976 false,
977 ),
978 },
979 ColumnMetadata {
980 column_id: 1,
981 semantic_type: SemanticType::Tag,
982 column_schema: ColumnSchema::new(
983 "tag",
984 ConcreteDataType::string_datatype(),
985 false,
986 ),
987 },
988 ],
989 primary_key: vec![0],
990 options,
991 table_dir: "/test_dir".to_string(),
992 path_type: PathType::Bare,
993 partition_expr_json: Some("".to_string()),
994 requirements: RegionRequirements::object_storage(),
995 };
996
997 let env = TestEnv::new().await;
999 let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap();
1000 let engine_inner = engine.inner;
1001
1002 let data_region_request = engine_inner.create_request_for_data_region(&request);
1004 assert_eq!(data_region_request.table_dir, "/test_dir".to_string());
1005 assert_eq!(data_region_request.path_type, PathType::Data);
1006 assert_eq!(data_region_request.column_metadatas.len(), 4);
1007 assert_eq!(
1008 data_region_request.primary_key,
1009 vec![ReservedColumnId::table_id(), ReservedColumnId::tsid(), 1]
1010 );
1011 assert!(data_region_request.options.contains_key("ttl"));
1012 assert_eq!(
1013 data_region_request.requirements,
1014 RegionRequirements::object_storage()
1015 );
1016
1017 let metadata_region_request = engine_inner.create_request_for_metadata_region(&request);
1019 assert_eq!(metadata_region_request.table_dir, "/test_dir".to_string());
1020 assert_eq!(metadata_region_request.path_type, PathType::Metadata);
1021 assert_eq!(
1022 metadata_region_request.options.get("ttl").unwrap(),
1023 "forever"
1024 );
1025 assert!(!metadata_region_request.options.contains_key("skip_wal"));
1026 assert_eq!(
1027 metadata_region_request.requirements,
1028 RegionRequirements::object_storage()
1029 );
1030 }
1031
1032 #[tokio::test]
1033 async fn test_create_request_for_physical_regions_with_internal_columns() {
1034 let options: HashMap<_, _> = [
1035 ("ttl".to_string(), "60m".to_string()),
1036 ("skip_wal".to_string(), "true".to_string()),
1037 ]
1038 .into_iter()
1039 .collect();
1040 let request = RegionCreateRequest {
1041 engine: METRIC_ENGINE_NAME.to_string(),
1042 column_metadatas: vec![
1043 ColumnMetadata {
1044 column_id: 0,
1045 semantic_type: SemanticType::Timestamp,
1046 column_schema: ColumnSchema::new(
1047 "timestamp",
1048 ConcreteDataType::timestamp_millisecond_datatype(),
1049 false,
1050 ),
1051 },
1052 ColumnMetadata {
1053 column_id: 1,
1054 semantic_type: SemanticType::Tag,
1055 column_schema: ColumnSchema::new(
1056 "tag",
1057 ConcreteDataType::string_datatype(),
1058 false,
1059 ),
1060 },
1061 ColumnMetadata {
1062 column_id: 2,
1063 semantic_type: SemanticType::Field,
1064 column_schema: ColumnSchema::new(
1065 "value",
1066 ConcreteDataType::float64_datatype(),
1067 false,
1068 ),
1069 },
1070 table_id_col(),
1071 tsid_col(),
1072 ],
1073 primary_key: vec![0],
1074 options,
1075 table_dir: "/test_dir".to_string(),
1076 path_type: PathType::Bare,
1077 partition_expr_json: Some("".to_string()),
1078 requirements: Default::default(),
1079 };
1080
1081 let env = TestEnv::new().await;
1082 let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap();
1083 let engine_inner = engine.inner;
1084
1085 let data_region_request = engine_inner.create_request_for_data_region(&request);
1086 assert_eq!(data_region_request.column_metadatas.len(), 5);
1087 assert_eq!(
1088 data_region_request.primary_key,
1089 vec![ReservedColumnId::table_id(), ReservedColumnId::tsid(), 1]
1090 );
1091
1092 let table_id_count = data_region_request
1093 .column_metadatas
1094 .iter()
1095 .filter(|metadata| metadata.column_schema.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
1096 .count();
1097 let tsid_count = data_region_request
1098 .column_metadatas
1099 .iter()
1100 .filter(|metadata| metadata.column_schema.name == DATA_SCHEMA_TSID_COLUMN_NAME)
1101 .count();
1102 assert_eq!(table_id_count, 1);
1103 assert_eq!(tsid_count, 1);
1104
1105 let tag_metadata = data_region_request
1106 .column_metadatas
1107 .iter()
1108 .find(|metadata| metadata.column_schema.name == "tag")
1109 .unwrap();
1110 assert!(tag_metadata.column_schema.is_nullable());
1111
1112 let table_id_metadata = data_region_request
1113 .column_metadatas
1114 .iter()
1115 .find(|metadata| metadata.column_schema.name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
1116 .unwrap();
1117 assert!(is_metric_name_col(table_id_metadata));
1118
1119 let tsid_metadata = data_region_request
1120 .column_metadatas
1121 .iter()
1122 .find(|metadata| metadata.column_schema.name == DATA_SCHEMA_TSID_COLUMN_NAME)
1123 .unwrap();
1124 assert!(is_tsid_col(tsid_metadata));
1125 }
1126
1127 #[tokio::test]
1128 async fn test_create_logical_regions() {
1129 let env = TestEnv::new().await;
1130 let engine = env.metric();
1131 let physical_region_id1 = RegionId::new(1024, 0);
1132 let physical_region_id2 = RegionId::new(1024, 1);
1133 let logical_region_id1 = RegionId::new(1025, 0);
1134 let logical_region_id2 = RegionId::new(1025, 1);
1135 env.create_physical_region(physical_region_id1, "/test_dir1", vec![])
1136 .await;
1137 env.create_physical_region(physical_region_id2, "/test_dir2", vec![])
1138 .await;
1139
1140 let region_create_request1 =
1141 create_logical_region_request(&["job"], physical_region_id1, "logical1");
1142 let region_create_request2 =
1143 create_logical_region_request(&["job"], physical_region_id2, "logical2");
1144
1145 let response = engine
1146 .handle_batch_ddl_requests(BatchRegionDdlRequest::Create(vec![
1147 (logical_region_id1, region_create_request1),
1148 (logical_region_id2, region_create_request2),
1149 ]))
1150 .await
1151 .unwrap();
1152
1153 let manifest_infos = parse_manifest_infos_from_extensions(&response.extensions).unwrap();
1154 assert_eq!(manifest_infos.len(), 2);
1155 let region_ids = manifest_infos.into_iter().map(|i| i.0).collect::<Vec<_>>();
1156 assert!(region_ids.contains(&physical_region_id1));
1157 assert!(region_ids.contains(&physical_region_id2));
1158
1159 let column_metadatas =
1160 parse_column_metadatas(&response.extensions, ALTER_PHYSICAL_EXTENSION_KEY).unwrap();
1161 assert_column_name_and_id(
1162 &column_metadatas,
1163 &[
1164 (greptime_timestamp(), 0),
1165 (greptime_value(), 1),
1166 ("__table_id", ReservedColumnId::table_id()),
1167 ("__tsid", ReservedColumnId::tsid()),
1168 ("job", 2),
1169 ],
1170 );
1171 }
1172}