1use api::v1::SemanticType;
16use common_query::native_histogram::is_native_histogram_value_type;
17use common_telemetry::{debug, info};
18use datatypes::schema::{SkippingIndexOptions, SkippingIndexType};
19use mito2::engine::MitoEngine;
20use snafu::ResultExt;
21use store_api::metadata::ColumnMetadata;
22use store_api::region_engine::RegionEngine;
23use store_api::region_request::{
24 AddColumn, AffectedRows, AlterKind, RegionAlterRequest, RegionRequest,
25};
26use store_api::storage::consts::ReservedColumnId;
27use store_api::storage::{ConcreteDataType, RegionId};
28
29use crate::engine::IndexOptions;
30use crate::error::{
31 AddingFieldColumnSnafu, ColumnTypeMismatchSnafu, ForbiddenPhysicalAlterSnafu,
32 MitoReadOperationSnafu, MitoWriteOperationSnafu, Result, SetSkippingIndexOptionSnafu,
33};
34use crate::metrics::{FORBIDDEN_OPERATION_COUNT, MITO_DDL_DURATION, PHYSICAL_COLUMN_COUNT};
35use crate::utils;
36
37pub struct DataRegion {
43 mito: MitoEngine,
44}
45
46impl DataRegion {
47 pub fn new(mito: MitoEngine) -> Self {
48 Self { mito }
49 }
50
51 pub async fn add_columns(
63 &self,
64 region_id: RegionId,
65 columns: Vec<ColumnMetadata>,
66 index_options: IndexOptions,
67 ) -> Result<()> {
68 if columns.is_empty() {
70 return Ok(());
71 }
72
73 let region_id = utils::to_data_region_id(region_id);
74
75 let num_columns = columns.len();
76 let request = self
77 .assemble_alter_request(region_id, columns, index_options)
78 .await?;
79
80 let _timer = MITO_DDL_DURATION.start_timer();
81
82 let _ = self
83 .mito
84 .handle_request(region_id, request)
85 .await
86 .context(MitoWriteOperationSnafu)?;
87
88 PHYSICAL_COLUMN_COUNT.add(num_columns as _);
89
90 Ok(())
91 }
92
93 async fn assemble_alter_request(
96 &self,
97 region_id: RegionId,
98 columns: Vec<ColumnMetadata>,
99 index_options: IndexOptions,
100 ) -> Result<RegionRequest> {
101 let region_metadata = self
103 .mito
104 .get_metadata(region_id)
105 .await
106 .context(MitoReadOperationSnafu)?;
107
108 let new_column_id_start = 1 + region_metadata
110 .column_metadatas
111 .iter()
112 .filter_map(|c| {
113 if ReservedColumnId::is_reserved(c.column_id) {
114 None
115 } else {
116 Some(c.column_id)
117 }
118 })
119 .max()
120 .unwrap_or(0);
121
122 let new_columns = columns
124 .into_iter()
125 .enumerate()
126 .map(|(delta, mut c)| {
127 match c.semantic_type {
128 SemanticType::Tag => {
129 if !c.column_schema.data_type.is_string() {
130 return ColumnTypeMismatchSnafu {
131 expect: ConcreteDataType::string_datatype(),
132 actual: c.column_schema.data_type.clone(),
133 }
134 .fail();
135 }
136 }
137 SemanticType::Field
141 if is_native_histogram_value_type(&c.column_schema.data_type) => {}
142 _ => {
143 return AddingFieldColumnSnafu {
144 name: &c.column_schema.name,
145 }
146 .fail();
147 }
148 }
149
150 c.column_id = new_column_id_start + delta as u32;
151 c.column_schema.set_nullable();
152 if c.semantic_type == SemanticType::Tag {
153 match index_options {
154 IndexOptions::None => {}
155 IndexOptions::Inverted => {
156 c.column_schema.set_inverted_index(true);
157 }
158 IndexOptions::Skipping {
159 granularity,
160 false_positive_rate,
161 } => {
162 c.column_schema
163 .set_skipping_options(
164 &SkippingIndexOptions::new(
165 granularity,
166 false_positive_rate,
167 SkippingIndexType::BloomFilter,
168 )
169 .context(SetSkippingIndexOptionSnafu)?,
170 )
171 .context(SetSkippingIndexOptionSnafu)?;
172 }
173 }
174 }
175
176 Ok(AddColumn {
177 column_metadata: c.clone(),
178 location: None,
179 })
180 })
181 .collect::<Result<_>>()?;
182
183 debug!("Adding (Column id assigned) columns {new_columns:?} to region {region_id:?}");
184 let alter_request = RegionRequest::Alter(RegionAlterRequest {
186 kind: AlterKind::AddColumns {
187 columns: new_columns,
188 },
189 });
190
191 Ok(alter_request)
192 }
193
194 pub async fn write_data(
195 &self,
196 region_id: RegionId,
197 request: RegionRequest,
198 ) -> Result<AffectedRows> {
199 let region_id = utils::to_data_region_id(region_id);
200 self.mito
201 .handle_request(region_id, request)
202 .await
203 .context(MitoWriteOperationSnafu)
204 .map(|result| result.affected_rows)
205 }
206
207 pub async fn physical_columns(
208 &self,
209 physical_region_id: RegionId,
210 ) -> Result<Vec<ColumnMetadata>> {
211 let data_region_id = utils::to_data_region_id(physical_region_id);
212 let metadata = self
213 .mito
214 .get_metadata(data_region_id)
215 .await
216 .context(MitoReadOperationSnafu)?;
217 Ok(metadata.column_metadatas.clone())
218 }
219
220 pub async fn alter_region_options(
221 &self,
222 region_id: RegionId,
223 request: RegionAlterRequest,
224 ) -> Result<AffectedRows> {
225 match request.kind {
226 AlterKind::SetRegionOptions { options: _ }
227 | AlterKind::UnsetRegionOptions { keys: _ }
228 | AlterKind::SetIndexes { options: _ }
229 | AlterKind::UnsetIndexes { options: _ }
230 | AlterKind::SyncColumns {
231 column_metadatas: _,
232 } => {
233 let region_id = utils::to_data_region_id(region_id);
234 self.mito
235 .handle_request(region_id, RegionRequest::Alter(request))
236 .await
237 .context(MitoWriteOperationSnafu)
238 .map(|result| result.affected_rows)
239 }
240 _ => {
241 info!(
242 "Metric region received alter request {request:?} on physical region {region_id:?}"
243 );
244 FORBIDDEN_OPERATION_COUNT.inc();
245
246 ForbiddenPhysicalAlterSnafu.fail()
247 }
248 }
249 }
250}
251
252#[cfg(test)]
253mod test {
254 use common_query::prelude::{greptime_timestamp, greptime_value};
255 use datatypes::prelude::ConcreteDataType;
256 use datatypes::schema::ColumnSchema;
257
258 use super::*;
259 use crate::test_util::TestEnv;
260
261 #[tokio::test]
262 async fn test_add_columns() {
263 let env = TestEnv::new().await;
264 env.init_metric_region().await;
265
266 let current_version = env
267 .mito()
268 .get_metadata(utils::to_data_region_id(env.default_physical_region_id()))
269 .await
270 .unwrap()
271 .schema_version;
272 assert_eq!(current_version, 1);
274
275 let new_columns = vec![
276 ColumnMetadata {
277 column_id: 0,
278 semantic_type: SemanticType::Tag,
279 column_schema: ColumnSchema::new(
280 "tag2",
281 ConcreteDataType::string_datatype(),
282 false,
283 ),
284 },
285 ColumnMetadata {
286 column_id: 0,
287 semantic_type: SemanticType::Tag,
288 column_schema: ColumnSchema::new(
289 "tag3",
290 ConcreteDataType::string_datatype(),
291 false,
292 ),
293 },
294 ];
295 env.data_region()
296 .add_columns(
297 env.default_physical_region_id(),
298 new_columns,
299 IndexOptions::Inverted,
300 )
301 .await
302 .unwrap();
303
304 let new_metadata = env
305 .mito()
306 .get_metadata(utils::to_data_region_id(env.default_physical_region_id()))
307 .await
308 .unwrap();
309 let column_names = new_metadata
310 .column_metadatas
311 .iter()
312 .map(|c| &c.column_schema.name)
313 .collect::<Vec<_>>();
314 let expected = vec![
315 greptime_timestamp(),
316 greptime_value(),
317 "__table_id",
318 "__tsid",
319 "job",
320 "tag2",
321 "tag3",
322 ];
323 assert_eq!(column_names, expected);
324 }
325
326 #[tokio::test]
328 async fn test_add_invalid_column() {
329 let env = TestEnv::new().await;
330 env.init_metric_region().await;
331
332 let new_columns = vec![ColumnMetadata {
333 column_id: 0,
334 semantic_type: SemanticType::Tag,
335 column_schema: ColumnSchema::new("tag2", ConcreteDataType::int64_datatype(), false),
336 }];
337 let result = env
338 .data_region()
339 .add_columns(
340 env.default_physical_region_id(),
341 new_columns,
342 IndexOptions::Inverted,
343 )
344 .await;
345 assert!(result.is_err());
346 }
347}