1mod extract_new_columns;
16mod validate;
17
18use std::collections::{BTreeSet, HashMap};
19
20use api::v1::SemanticType;
21use common_query::native_histogram::is_native_histogram_value_type;
22use extract_new_columns::extract_new_columns;
23use snafu::{OptionExt, ResultExt, ensure};
24use store_api::metadata::ColumnMetadata;
25use store_api::metric_engine_consts::ALTER_PHYSICAL_EXTENSION_KEY;
26use store_api::region_request::{AffectedRows, AlterKind, RegionAlterRequest};
27use store_api::storage::RegionId;
28use validate::validate_alter_region_requests;
29
30use crate::engine::MetricEngineInner;
31use crate::error::{
32 AddingFieldColumnSnafu, LogicalRegionNotFoundSnafu, PhysicalRegionNotFoundSnafu, Result,
33 SerializeColumnMetadataSnafu, UnexpectedRequestSnafu,
34};
35use crate::utils::{append_manifest_info, encode_manifest_info_to_extensions, to_data_region_id};
36
37impl MetricEngineInner {
38 pub async fn alter_regions(
39 &self,
40 mut requests: Vec<(RegionId, RegionAlterRequest)>,
41 extension_return_value: &mut HashMap<String, Vec<u8>>,
42 ) -> Result<AffectedRows> {
43 if requests.is_empty() {
44 return Ok(0);
45 }
46
47 let first_region_id = &requests.first().unwrap().0;
48 if self.is_physical_region(*first_region_id) {
49 ensure!(
50 requests.len() == 1,
51 UnexpectedRequestSnafu {
52 reason: "Physical table must be altered with single request".to_string(),
53 }
54 );
55 let (region_id, request) = requests.pop().unwrap();
56 self.alter_physical_region(region_id, request).await?;
57 } else {
58 if requests.len() == 1 {
60 let region_id = requests.first().unwrap().0;
62 let physical_region_id = self
63 .state
64 .read()
65 .unwrap()
66 .get_physical_region_id(region_id)
67 .with_context(|| LogicalRegionNotFoundSnafu { region_id })?;
68 let mut manifest_infos = Vec::with_capacity(1);
69 self.alter_logical_regions(physical_region_id, requests, extension_return_value)
70 .await?;
71 append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos);
72 encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?;
73 } else {
74 let grouped_requests =
75 self.group_logical_region_requests_by_physical_region_id(requests)?;
76 let mut manifest_infos = Vec::with_capacity(grouped_requests.len());
77 for (physical_region_id, requests) in grouped_requests {
78 self.alter_logical_regions(
79 physical_region_id,
80 requests,
81 extension_return_value,
82 )
83 .await?;
84 append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos);
85 }
86 encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?;
87 }
88 }
89 Ok(0)
90 }
91
92 fn group_logical_region_requests_by_physical_region_id(
94 &self,
95 requests: Vec<(RegionId, RegionAlterRequest)>,
96 ) -> Result<HashMap<RegionId, Vec<(RegionId, RegionAlterRequest)>>> {
97 let mut result = HashMap::with_capacity(requests.len());
98 let state = self.state.read().unwrap();
99
100 for (region_id, request) in requests {
101 let physical_region_id = state
102 .get_physical_region_id(region_id)
103 .with_context(|| LogicalRegionNotFoundSnafu { region_id })?;
104 result
105 .entry(physical_region_id)
106 .or_insert_with(Vec::new)
107 .push((region_id, request));
108 }
109
110 Ok(result)
111 }
112
113 pub async fn alter_logical_regions(
115 &self,
116 physical_region_id: RegionId,
117 requests: Vec<(RegionId, RegionAlterRequest)>,
118 extension_return_value: &mut HashMap<String, Vec<u8>>,
119 ) -> Result<AffectedRows> {
120 validate_alter_region_requests(&requests)?;
122 self.validate_logical_field_alters(physical_region_id, &requests)
123 .await?;
124
125 let mut new_column_indices = HashMap::new();
127 let mut new_columns_to_add = vec![];
128
129 let index_options = {
130 let state = &self.state.read().unwrap();
131 let region_state = state
132 .physical_region_states()
133 .get(&physical_region_id)
134 .with_context(|| PhysicalRegionNotFoundSnafu {
135 region_id: physical_region_id,
136 })?;
137 let physical_columns = region_state.physical_columns();
138
139 extract_new_columns(
140 &requests,
141 physical_columns,
142 &mut new_column_indices,
143 &mut new_columns_to_add,
144 )?;
145
146 region_state.options().index
147 };
148 let data_region_id = to_data_region_id(physical_region_id);
149
150 let region_ids = requests
153 .iter()
154 .map(|(region_id, _)| *region_id)
155 .collect::<BTreeSet<_>>();
156
157 let mut write_guards = Vec::with_capacity(region_ids.len());
158 for region_id in region_ids {
159 write_guards.push(
160 self.metadata_region
161 .write_lock_logical_region(region_id)
162 .await?,
163 );
164 }
165
166 self.data_region
167 .add_columns(data_region_id, new_columns_to_add, index_options)
168 .await?;
169
170 let physical_columns = self.data_region.physical_columns(data_region_id).await?;
171 let physical_schema_map = physical_columns
172 .iter()
173 .map(|metadata| (metadata.column_schema.name.as_str(), metadata))
174 .collect::<HashMap<_, _>>();
175
176 let logical_region_columns = requests.iter().map(|(region_id, request)| {
177 let AlterKind::AddColumns { columns } = &request.kind else {
178 unreachable!()
179 };
180 (
181 *region_id,
182 columns
183 .iter()
184 .map(|col| {
185 let column_name = col.column_metadata.column_schema.name.as_str();
186 let column_metadata = *physical_schema_map.get(column_name).unwrap();
187 (column_name, column_metadata)
188 })
189 .collect::<HashMap<_, _>>(),
190 )
191 });
192
193 let new_add_columns = new_column_indices.keys().map(|name| {
194 let column_metadata = *physical_schema_map.get(name).unwrap();
196 (name.to_string(), column_metadata.clone())
197 });
198
199 self.state
201 .write()
202 .unwrap()
203 .add_physical_columns(data_region_id, new_add_columns);
204
205 let result = self
207 .metadata_region
208 .add_logical_regions(physical_region_id, false, logical_region_columns)
209 .await;
210 self.state
213 .write()
214 .unwrap()
215 .invalid_logical_regions_cache(requests.iter().map(|(region_id, _)| *region_id));
216 result?;
217
218 extension_return_value.insert(
219 ALTER_PHYSICAL_EXTENSION_KEY.to_string(),
220 ColumnMetadata::encode_list(&physical_columns).context(SerializeColumnMetadataSnafu)?,
221 );
222
223 Ok(0)
224 }
225
226 async fn validate_logical_field_alters(
227 &self,
228 physical_region_id: RegionId,
229 requests: &[(RegionId, RegionAlterRequest)],
230 ) -> Result<()> {
231 for (region_id, request) in requests {
234 let AlterKind::AddColumns { columns } = &request.kind else {
235 unreachable!()
236 };
237 let added_fields = columns
238 .iter()
239 .filter(|col| col.column_metadata.semantic_type == SemanticType::Field)
240 .collect::<Vec<_>>();
241 let Some(&first_added_field) = added_fields.first() else {
242 continue;
243 };
244
245 let mut fields = self
246 .load_logical_columns(physical_region_id, *region_id)
247 .await?
248 .into_iter()
249 .filter(|col| col.semantic_type == SemanticType::Field)
250 .collect::<Vec<_>>();
251 fields.extend(
252 added_fields
253 .into_iter()
254 .map(|col| col.column_metadata.clone()),
255 );
256
257 ensure!(
258 fields.len() == 1
259 && is_native_histogram_value_type(&fields[0].column_schema.data_type),
260 AddingFieldColumnSnafu {
261 name: first_added_field.column_metadata.column_schema.name.clone(),
262 }
263 );
264 }
265
266 Ok(())
267 }
268
269 async fn alter_physical_region(
270 &self,
271 region_id: RegionId,
272 request: RegionAlterRequest,
273 ) -> Result<()> {
274 self.data_region
275 .alter_region_options(region_id, request)
276 .await?;
277 Ok(())
278 }
279}
280
281#[cfg(test)]
282mod test {
283 use api::v1::SemanticType;
284 use common_meta::ddl::test_util::assert_column_name_and_id;
285 use common_meta::ddl::utils::{parse_column_metadatas, parse_manifest_infos_from_extensions};
286 use common_query::prelude::{greptime_timestamp, greptime_value};
287 use store_api::metric_engine_consts::ALTER_PHYSICAL_EXTENSION_KEY;
288 use store_api::region_engine::RegionEngine;
289 use store_api::region_request::{
290 AlterKind, BatchRegionDdlRequest, RegionAlterRequest, SetRegionOption,
291 };
292 use store_api::storage::RegionId;
293 use store_api::storage::consts::ReservedColumnId;
294
295 use crate::test_util::{TestEnv, alter_logical_region_request, create_logical_region_request};
296
297 async fn check_alter_physical_skip_wal(
298 engine: &super::MetricEngineInner,
299 region_id: RegionId,
300 skip_wal: bool,
301 ) {
302 let request = RegionAlterRequest {
303 kind: AlterKind::SetRegionOptions {
304 options: vec![SetRegionOption::SkipWal(skip_wal)],
305 },
306 };
307 engine
308 .alter_physical_region(region_id, request)
309 .await
310 .unwrap();
311 }
312
313 #[tokio::test]
314 async fn test_alter_region() {
315 let env = TestEnv::new().await;
316 env.init_metric_region().await;
317 let engine = env.metric();
318 let engine_inner = engine.inner;
319
320 let physical_region_id = env.default_physical_region_id();
322 let request = alter_logical_region_request(&["tag1"]);
323
324 let result = engine_inner
325 .alter_physical_region(physical_region_id, request.clone())
326 .await;
327 assert!(result.is_err());
328 assert_eq!(
329 result.unwrap_err().to_string(),
330 "Alter request to physical region is forbidden".to_string()
331 );
332
333 check_alter_physical_skip_wal(&engine_inner, physical_region_id, true).await;
335 check_alter_physical_skip_wal(&engine_inner, physical_region_id, false).await;
336
337 let metadata_region = env.metadata_region();
339 let logical_region_id = env.default_logical_region_id();
340 let is_column_exist = metadata_region
341 .column_semantic_type(physical_region_id, logical_region_id, "tag1")
342 .await
343 .unwrap()
344 .is_some();
345 assert!(!is_column_exist);
346
347 let region_id = env.default_logical_region_id();
348 let response = env
349 .metric()
350 .handle_batch_ddl_requests(BatchRegionDdlRequest::Alter(vec![(
351 region_id,
352 request.clone(),
353 )]))
354 .await
355 .unwrap();
356 let manifest_infos = parse_manifest_infos_from_extensions(&response.extensions).unwrap();
357 assert_eq!(manifest_infos[0].0, physical_region_id);
358 assert!(manifest_infos[0].1.is_metric());
359
360 let semantic_type = metadata_region
361 .column_semantic_type(physical_region_id, logical_region_id, "tag1")
362 .await
363 .unwrap()
364 .unwrap();
365 assert_eq!(semantic_type, SemanticType::Tag);
366 let timestamp_index = metadata_region
367 .column_semantic_type(physical_region_id, logical_region_id, greptime_timestamp())
368 .await
369 .unwrap()
370 .unwrap();
371 assert_eq!(timestamp_index, SemanticType::Timestamp);
372 let column_metadatas =
373 parse_column_metadatas(&response.extensions, ALTER_PHYSICAL_EXTENSION_KEY).unwrap();
374 assert_column_name_and_id(
375 &column_metadatas,
376 &[
377 (greptime_timestamp(), 0),
378 (greptime_value(), 1),
379 ("__table_id", ReservedColumnId::table_id()),
380 ("__tsid", ReservedColumnId::tsid()),
381 ("job", 2),
382 ("tag1", 3),
383 ],
384 );
385 }
386
387 #[tokio::test]
388 async fn test_alter_logical_regions() {
389 let env = TestEnv::new().await;
390 let engine = env.metric();
391 let physical_region_id1 = RegionId::new(1024, 0);
392 let physical_region_id2 = RegionId::new(1024, 1);
393 let logical_region_id1 = RegionId::new(1025, 0);
394 let logical_region_id2 = RegionId::new(1025, 1);
395 env.create_physical_region(physical_region_id1, "/test_dir1", vec![])
396 .await;
397 env.create_physical_region(physical_region_id2, "/test_dir2", vec![])
398 .await;
399
400 let region_create_request1 = crate::test_util::create_logical_region_request(
401 &["job"],
402 physical_region_id1,
403 "logical1",
404 );
405 let region_create_request2 =
406 create_logical_region_request(&["job"], physical_region_id2, "logical2");
407 engine
408 .handle_batch_ddl_requests(BatchRegionDdlRequest::Create(vec![
409 (logical_region_id1, region_create_request1),
410 (logical_region_id2, region_create_request2),
411 ]))
412 .await
413 .unwrap();
414
415 let region_alter_request1 = alter_logical_region_request(&["tag1"]);
416 let region_alter_request2 = alter_logical_region_request(&["tag1"]);
417 let response = engine
418 .handle_batch_ddl_requests(BatchRegionDdlRequest::Alter(vec![
419 (logical_region_id1, region_alter_request1),
420 (logical_region_id2, region_alter_request2),
421 ]))
422 .await
423 .unwrap();
424
425 let manifest_infos = parse_manifest_infos_from_extensions(&response.extensions).unwrap();
426 assert_eq!(manifest_infos.len(), 2);
427 let region_ids = manifest_infos.into_iter().map(|i| i.0).collect::<Vec<_>>();
428 assert!(region_ids.contains(&physical_region_id1));
429 assert!(region_ids.contains(&physical_region_id2));
430
431 let column_metadatas =
432 parse_column_metadatas(&response.extensions, ALTER_PHYSICAL_EXTENSION_KEY).unwrap();
433 assert_column_name_and_id(
434 &column_metadatas,
435 &[
436 (greptime_timestamp(), 0),
437 (greptime_value(), 1),
438 ("__table_id", ReservedColumnId::table_id()),
439 ("__tsid", ReservedColumnId::tsid()),
440 ("job", 2),
441 ("tag1", 3),
442 ],
443 );
444 }
445}