Skip to main content

metric_engine/engine/
create.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod 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    /// Initialize a physical metric region at given region id.
123    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        // create metadata region
133        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        // create data region
145        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        // remember this table
191        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    /// Create multiple logical regions on the same physical region.
203    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        // Checks the time index unit of each request.
220        for (_, request) in &requests {
221            // Safety: verify_region_create_request() ensures that the request is valid.
222            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        // Filters out the requests that the logical region already exists
245        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            // log skipped regions
259            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        // Finds new columns to add to physical region
269        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        // TODO(weny): we dont need to pass a mutable new_columns here.
292        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                        // Safety: previous steps ensure the physical region exist
313                        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            // Safety: previous steps ensure the physical region exist
324            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        // Writes logical regions metadata to metadata region
334        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    /// Check if
353    /// - internal columns are not occupied
354    /// - required table option is present ([PHYSICAL_TABLE_METADATA_KEY] or
355    ///   [LOGICAL_TABLE_METADATA_KEY])
356    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        // check if internal columns are not occupied or defined in the request
370        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        // check if required table option is present
384        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        // check if field columns are either a normal metric value or a native histogram.
395        let mut field_cols = Vec::new();
396        for col in &request.column_metadatas {
397            // Verified in above steps.
398            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        // make sure the normal field column is float64 type
434        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    /// Build data region id and metadata region id from the given region id.
446    ///
447    /// Return value: (data_region_id, metadata_region_id)
448    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    /// Build [RegionCreateRequest] for metadata region
456    ///
457    /// This method will append [METADATA_REGION_SUBDIR] to the given `region_dir`.
458    pub fn create_request_for_metadata_region(
459        &self,
460        request: &RegionCreateRequest,
461    ) -> RegionCreateRequest {
462        // ts TIME INDEX DEFAULT 0
463        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        // key STRING PRIMARY KEY
477        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        // val STRING
487        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    /// Convert [RegionCreateRequest] for data region.
515    ///
516    /// All tag columns in the original request will be converted to value columns.
517    /// Those columns real semantic type is stored in metadata region.
518    ///
519    /// This will also add internal columns to the request.
520    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        // change nullability for tag columns
534        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        // add internal columns if not defined in the request
548        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
557        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
594/// Returns true if the column is the metric name column.
595pub(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
603/// Returns true if the column is the tsid column.
604pub(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
612/// Groups the create logical region requests by physical region id.
613fn 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
628/// Parses the physical region id from the request.
629fn 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
645/// Creates the region options for metadata region in metric engine.
646pub(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        // internal column is occupied
684        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        // allow reserved internal columns when defined properly
721        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        // valid request
766        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        // original request
949        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        // set up
986        let env = TestEnv::new().await;
987        let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap();
988        let engine_inner = engine.inner;
989
990        // check create data region request
991        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        // check create metadata region request
1006        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}