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