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;
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    /// 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_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        // 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_indices.keys().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        // The data region has the new columns, whatever happens to the metadata write.
328        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        // Writes logical regions metadata to metadata region
339        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    /// Check if
357    /// - internal columns are not occupied
358    /// - required table option is present ([PHYSICAL_TABLE_METADATA_KEY] or
359    ///   [LOGICAL_TABLE_METADATA_KEY])
360    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        // check if internal columns are not occupied or defined in the request
374        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        // check if required table option is present
388        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        // check if field columns are either a normal metric value or a native histogram.
410        let mut field_cols = Vec::new();
411        for col in &request.column_metadatas {
412            // Verified in above steps.
413            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        // make sure the normal field column is float64 type
446        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    /// Build data region id and metadata region id from the given region id.
458    ///
459    /// Return value: (data_region_id, metadata_region_id)
460    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    /// Build [RegionCreateRequest] for metadata region
468    ///
469    /// This method will append [METADATA_REGION_SUBDIR] to the given `region_dir`.
470    pub fn create_request_for_metadata_region(
471        &self,
472        request: &RegionCreateRequest,
473    ) -> RegionCreateRequest {
474        // ts TIME INDEX DEFAULT 0
475        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        // key STRING PRIMARY KEY
489        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        // val STRING
499        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    /// Convert [RegionCreateRequest] for data region.
527    ///
528    /// All tag columns in the original request will be converted to value columns.
529    /// Those columns real semantic type is stored in metadata region.
530    ///
531    /// This will also add internal columns to the request.
532    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        // change nullability for tag columns
546        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        // add internal columns if not defined in the request
560        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
569        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
606/// Returns true if the column is the metric name column.
607pub(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
615/// Returns true if the column is the tsid column.
616pub(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
624/// Groups the create logical region requests by physical region id.
625fn 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
640/// Parses the physical region id from the request.
641fn 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
657/// Creates the region options for metadata region in metric engine.
658pub(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        // internal column is occupied
696        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        // allow reserved internal columns when defined properly
733        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        // valid request
778        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        // original request
961        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        // set up
998        let env = TestEnv::new().await;
999        let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap();
1000        let engine_inner = engine.inner;
1001
1002        // check create data region request
1003        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        // check create metadata region request
1018        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}