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