1use std::collections::HashMap;
16use std::fmt::{self, Display};
17use std::time::Duration;
18
19use api::helper::{ColumnDataTypeWrapper, from_pb_time_ranges, from_pb_time_unit};
20use api::v1::add_column_location::LocationType;
21use api::v1::column_def::{
22 as_fulltext_option_analyzer, as_fulltext_option_backend, as_skipping_index_type,
23};
24use api::v1::region::bulk_insert_request::Body;
25use api::v1::region::{
26 AlterRequest, AlterRequests, BuildIndexRequest, BulkInsertRequest,
27 CleanUpRequest as PbCleanUpRequest, CloseRequest, CompactRequest, CreateRequest,
28 CreateRequests, DeleteRequests, DropRequest, DropRequests, FlushRequest, InsertRequests,
29 OpenRequest, TruncateRequest, alter_request, build_index_request, compact_request,
30 region_request, truncate_request,
31};
32use api::v1::{
33 self, Analyzer, ArrowIpc, FulltextBackend as PbFulltextBackend, Option as PbOption, Rows,
34 SemanticType, SkippingIndexType as PbSkippingIndexType, WriteHint,
35};
36use arrow_schema::extension::ExtensionType;
37pub use common_base::AffectedRows;
38use common_base::readable_size::ReadableSize;
39use common_grpc::flight::FlightDecoder;
40use common_recordbatch::DfRecordBatch;
41use common_time::range::TimestampRange;
42use common_time::{TimeToLive, Timestamp};
43use datatypes::error::time_index_not_widening_error;
44use datatypes::extension::json::Json2ExtensionType;
45use datatypes::json::{JsonSettings, JsonTypeHint};
46use datatypes::prelude::ConcreteDataType;
47use datatypes::schema::{FulltextOptions, SkippingIndexOptions};
48use num_enum::TryFromPrimitive;
49use serde::{Deserialize, Deserializer, Serialize, Serializer};
50use snafu::{OptionExt, ResultExt, ensure};
51use strum::{AsRefStr, IntoStaticStr};
52
53use crate::logstore::entry;
54use crate::metadata::{
55 ColumnMetadata, ConvertTimeRangesSnafu, DecodeProtoSnafu, FlightCodecSnafu,
56 InvalidIndexOptionSnafu, InvalidRawRegionRequestSnafu, InvalidRegionRequestSnafu,
57 InvalidSetRegionOptionRequestSnafu, InvalidUnsetRegionOptionRequestSnafu, MetadataError,
58 RegionMetadata, Result, UnexpectedSnafu,
59};
60use crate::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
61use crate::metrics;
62use crate::mito_engine_options::{
63 APPEND_MODE_KEY, AUTO_FLUSH_INTERVAL_KEY, MAX_ROW_GROUP_ROW_COUNT,
64 MAX_ROW_GROUP_ROW_COUNT_LIMIT, PRESERVE_ROW_SEQUENCE, SKIP_WAL_KEY, SST_FORMAT_KEY, TTL_KEY,
65 TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER, TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM,
66 TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER, TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM,
67 TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW, TWCS_TRIGGER_FILE_NUM, WRITE_BUFFER_SIZE_KEY,
68};
69use crate::path_utils::table_dir;
70use crate::storage::{ColumnId, RegionId};
71
72#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
74#[repr(u8)]
75pub enum PathType {
76 Bare,
80 Data,
84 Metadata,
88}
89
90#[derive(Debug, IntoStaticStr)]
91pub enum BatchRegionDdlRequest {
92 Create(Vec<(RegionId, RegionCreateRequest)>),
93 Drop(Vec<(RegionId, RegionDropRequest)>),
94 Alter(Vec<(RegionId, RegionAlterRequest)>),
95}
96
97impl BatchRegionDdlRequest {
98 pub fn try_from_request_body(body: region_request::Body) -> Result<Option<Self>> {
100 match body {
101 region_request::Body::Creates(creates) => {
102 let requests = creates
103 .requests
104 .into_iter()
105 .map(parse_region_create)
106 .collect::<Result<Vec<_>>>()?;
107 Ok(Some(Self::Create(requests)))
108 }
109 region_request::Body::Drops(drops) => {
110 let requests = drops
111 .requests
112 .into_iter()
113 .map(parse_region_drop)
114 .collect::<Result<Vec<_>>>()?;
115 Ok(Some(Self::Drop(requests)))
116 }
117 region_request::Body::Alters(alters) => {
118 let requests = alters
119 .requests
120 .into_iter()
121 .map(parse_region_alter)
122 .collect::<Result<Vec<_>>>()?;
123 Ok(Some(Self::Alter(requests)))
124 }
125 _ => Ok(None),
126 }
127 }
128
129 pub fn request_type(&self) -> &'static str {
130 self.into()
131 }
132
133 pub fn into_region_requests(self) -> Vec<(RegionId, RegionRequest)> {
134 match self {
135 Self::Create(requests) => requests
136 .into_iter()
137 .map(|(region_id, request)| (region_id, RegionRequest::Create(request)))
138 .collect(),
139 Self::Drop(requests) => requests
140 .into_iter()
141 .map(|(region_id, request)| (region_id, RegionRequest::Drop(request)))
142 .collect(),
143 Self::Alter(requests) => requests
144 .into_iter()
145 .map(|(region_id, request)| (region_id, RegionRequest::Alter(request)))
146 .collect(),
147 }
148 }
149}
150
151#[derive(Debug, IntoStaticStr)]
152pub enum RegionRequest {
153 Put(RegionPutRequest),
154 Delete(RegionDeleteRequest),
155 Create(RegionCreateRequest),
156 Drop(RegionDropRequest),
157 Open(RegionOpenRequest),
158 CleanUp(RegionCleanUpRequest),
159 Close(RegionCloseRequest),
160 Alter(RegionAlterRequest),
161 Flush(RegionFlushRequest),
162 Compact(RegionCompactRequest),
163 BuildIndex(RegionBuildIndexRequest),
164 Truncate(RegionTruncateRequest),
165 Catchup(RegionCatchupRequest),
166 BulkInserts(RegionBulkInsertsRequest),
167 EnterStaging(EnterStagingRequest),
168 ApplyStagingManifest(ApplyStagingManifestRequest),
169}
170
171impl RegionRequest {
172 pub fn try_from_request_body(body: region_request::Body) -> Result<Vec<(RegionId, Self)>> {
175 match body {
176 region_request::Body::Inserts(inserts) => make_region_puts(inserts),
177 region_request::Body::Deletes(deletes) => make_region_deletes(deletes),
178 region_request::Body::Create(create) => make_region_create(create),
179 region_request::Body::Drop(drop) => make_region_drop(drop),
180 region_request::Body::Open(open) => make_region_open(open),
181 region_request::Body::CleanUp(clean_up) => make_region_clean_up(clean_up),
182 region_request::Body::Close(close) => make_region_close(close),
183 region_request::Body::Alter(alter) => make_region_alter(alter),
184 region_request::Body::Flush(flush) => make_region_flush(flush),
185 region_request::Body::Compact(compact) => make_region_compact(compact),
186 region_request::Body::BuildIndex(index) => make_region_build_index(index),
187 region_request::Body::Truncate(truncate) => make_region_truncate(truncate),
188 region_request::Body::Creates(creates) => make_region_creates(creates),
189 region_request::Body::Drops(drops) => make_region_drops(drops),
190 region_request::Body::Alters(alters) => make_region_alters(alters),
191 region_request::Body::BulkInsert(bulk) => make_region_bulk_inserts(bulk),
192 region_request::Body::Sync(_) => UnexpectedSnafu {
193 reason: "Sync request should be handled separately by RegionServer",
194 }
195 .fail(),
196 region_request::Body::ListMetadata(_) => UnexpectedSnafu {
197 reason: "ListMetadata request should be handled separately by RegionServer",
198 }
199 .fail(),
200 region_request::Body::RemoteDynFilter(_) => UnexpectedSnafu {
201 reason: "RemoteDynFilter request should be handled separately by RegionServer",
202 }
203 .fail(),
204 region_request::Body::ApplyStagingManifest(apply) => {
205 make_region_apply_staging_manifest(apply)
206 }
207 }
208 }
209
210 pub fn request_type(&self) -> &'static str {
212 self.into()
213 }
214}
215
216fn make_region_puts(inserts: InsertRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
217 let requests = inserts
218 .requests
219 .into_iter()
220 .filter_map(|r| {
221 let region_id = r.region_id.into();
222 r.rows.map(|rows| {
223 (
224 region_id,
225 RegionRequest::Put(RegionPutRequest {
226 rows,
227 hint: None,
228 skip_wal: r.skip_wal,
229 partition_expr_version: r.partition_expr_version.map(|v| v.value),
230 }),
231 )
232 })
233 })
234 .collect();
235 Ok(requests)
236}
237
238fn make_region_deletes(deletes: DeleteRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
239 let requests = deletes
240 .requests
241 .into_iter()
242 .filter_map(|r| {
243 let region_id = r.region_id.into();
244 r.rows.map(|rows| {
245 (
246 region_id,
247 RegionRequest::Delete(RegionDeleteRequest {
248 rows,
249 hint: None,
250 partition_expr_version: r.partition_expr_version.map(|v| v.value),
251 }),
252 )
253 })
254 })
255 .collect();
256 Ok(requests)
257}
258
259fn parse_region_create(create: CreateRequest) -> Result<(RegionId, RegionCreateRequest)> {
260 let column_metadatas = create
261 .column_defs
262 .into_iter()
263 .map(ColumnMetadata::try_from_column_def)
264 .collect::<Result<Vec<_>>>()?;
265 let region_id = RegionId::from(create.region_id);
266 let table_dir = table_dir(&create.path, region_id.table_id());
267 let partition_expr_json = create.partition.as_ref().map(|p| p.expression.clone());
268 Ok((
269 region_id,
270 RegionCreateRequest {
271 engine: create.engine,
272 column_metadatas,
273 primary_key: create.primary_key,
274 options: create.options,
275 table_dir,
276 path_type: PathType::Bare,
277 partition_expr_json,
278 requirements: create
279 .requirements
280 .map(RegionRequirements::from)
281 .unwrap_or_default(),
282 },
283 ))
284}
285
286fn make_region_create(create: CreateRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
287 let (region_id, request) = parse_region_create(create)?;
288 Ok(vec![(region_id, RegionRequest::Create(request))])
289}
290
291fn make_region_creates(creates: CreateRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
292 let mut requests = Vec::with_capacity(creates.requests.len());
293 for create in creates.requests {
294 requests.extend(make_region_create(create)?);
295 }
296 Ok(requests)
297}
298
299fn parse_region_drop(drop: DropRequest) -> Result<(RegionId, RegionDropRequest)> {
300 let region_id = drop.region_id.into();
301 Ok((
302 region_id,
303 RegionDropRequest {
304 fast_path: drop.fast_path,
305 force: drop.force,
306 partial_drop: drop.partial_drop,
307 },
308 ))
309}
310
311fn make_region_drop(drop: DropRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
312 let (region_id, request) = parse_region_drop(drop)?;
313 Ok(vec![(region_id, RegionRequest::Drop(request))])
314}
315
316fn make_region_drops(drops: DropRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
317 let mut requests = Vec::with_capacity(drops.requests.len());
318 for drop in drops.requests {
319 requests.extend(make_region_drop(drop)?);
320 }
321 Ok(requests)
322}
323
324fn make_region_open(open: OpenRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
325 let region_id = RegionId::from(open.region_id);
326 let table_dir = table_dir(&open.path, region_id.table_id());
327 Ok(vec![(
328 region_id,
329 RegionRequest::Open(RegionOpenRequest {
330 engine: open.engine,
331 table_dir,
332 path_type: PathType::Bare,
333 options: open.options,
334 skip_wal_replay: false,
335 checkpoint: None,
336 requirements: Default::default(),
337 }),
338 )])
339}
340
341fn make_region_clean_up(clean_up: PbCleanUpRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
342 let region_id = RegionId::from(clean_up.region_id);
343 let table_dir = table_dir(&clean_up.path, region_id.table_id());
344 Ok(vec![(
345 region_id,
346 RegionRequest::CleanUp(RegionCleanUpRequest {
347 engine: clean_up.engine,
348 table_dir,
349 path_type: PathType::Bare,
350 options: clean_up.options,
351 }),
352 )])
353}
354
355fn make_region_close(close: CloseRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
356 let region_id = close.region_id.into();
357 Ok(vec![(
358 region_id,
359 RegionRequest::Close(RegionCloseRequest {
360 flush_on_close: close.flush_on_close,
361 }),
362 )])
363}
364
365fn parse_region_alter(alter: AlterRequest) -> Result<(RegionId, RegionAlterRequest)> {
366 let region_id = alter.region_id.into();
367 let request = RegionAlterRequest::try_from(alter)?;
368 Ok((region_id, request))
369}
370
371fn make_region_alter(alter: AlterRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
372 let (region_id, request) = parse_region_alter(alter)?;
373 Ok(vec![(region_id, RegionRequest::Alter(request))])
374}
375
376fn make_region_alters(alters: AlterRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
377 let mut requests = Vec::with_capacity(alters.requests.len());
378 for alter in alters.requests {
379 requests.extend(make_region_alter(alter)?);
380 }
381 Ok(requests)
382}
383
384fn make_region_flush(flush: FlushRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
385 let region_id = flush.region_id.into();
386 Ok(vec![(
387 region_id,
388 RegionRequest::Flush(RegionFlushRequest::default()),
389 )])
390}
391
392fn make_region_compact(compact: CompactRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
393 let region_id = compact.region_id.into();
394 let options = compact
395 .options
396 .unwrap_or(compact_request::Options::Regular(Default::default()));
397 let parallelism = if compact.parallelism == 0 {
399 None
400 } else {
401 Some(compact.parallelism)
402 };
403 let time_range = compact
404 .time_range
405 .map(|range| {
406 let time_unit = v1::TimeUnit::try_from(range.time_unit).map_err(|_| {
407 InvalidRegionRequestSnafu {
408 region_id,
409 err: format!("invalid compaction time unit: {}", range.time_unit),
410 }
411 .build()
412 })?;
413 let time_unit = from_pb_time_unit(time_unit);
414 let start = Timestamp::new(range.start, time_unit);
415 let end = Timestamp::new(range.end, time_unit);
416 ensure!(
417 start < end,
418 InvalidRegionRequestSnafu {
419 region_id,
420 err: format!(
421 "compaction start time must be earlier than end time: {} >= {}",
422 range.start, range.end
423 ),
424 }
425 );
426 TimestampRange::new(start, end).with_context(|| InvalidRegionRequestSnafu {
427 region_id,
428 err: "invalid compaction time range".to_string(),
429 })
430 })
431 .transpose()?;
432 Ok(vec![(
433 region_id,
434 RegionRequest::Compact(RegionCompactRequest {
435 options,
436 parallelism,
437 time_range,
438 }),
439 )])
440}
441
442fn make_region_build_index(index: BuildIndexRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
443 let region_id = index.region_id.into();
444 Ok(vec![(
445 region_id,
446 RegionRequest::BuildIndex(RegionBuildIndexRequest {
447 options: index.options,
448 }),
449 )])
450}
451
452fn make_region_truncate(truncate: TruncateRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
453 let region_id = truncate.region_id.into();
454 match truncate.kind {
455 None => InvalidRawRegionRequestSnafu {
456 err: "missing kind in TruncateRequest".to_string(),
457 }
458 .fail(),
459 Some(truncate_request::Kind::All(_)) => Ok(vec![(
460 region_id,
461 RegionRequest::Truncate(RegionTruncateRequest::All),
462 )]),
463 Some(truncate_request::Kind::TimeRanges(time_ranges)) => {
464 let time_ranges = from_pb_time_ranges(time_ranges).context(ConvertTimeRangesSnafu)?;
465
466 Ok(vec![(
467 region_id,
468 RegionRequest::Truncate(RegionTruncateRequest::ByTimeRanges { time_ranges }),
469 )])
470 }
471 Some(truncate_request::Kind::Unflushed(_)) => Ok(vec![(
472 region_id,
473 RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
474 )]),
475 }
476}
477
478fn make_region_bulk_inserts(request: BulkInsertRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
480 let region_id = request.region_id.into();
481 let skip_wal = request.skip_wal;
482 let partition_expr_version = request.partition_expr_version.map(|v| v.value);
483 let aligned_schema_version = request.aligned_schema_version.map(|v| v.schema_version);
484 let Some(Body::ArrowIpc(request)) = request.body else {
485 return Ok(vec![]);
486 };
487
488 let decoder_timer = metrics::CONVERT_REGION_BULK_REQUEST
489 .with_label_values(&["decode"])
490 .start_timer();
491 let mut decoder =
492 FlightDecoder::try_from_schema_bytes(&request.schema).context(FlightCodecSnafu)?;
493 let payload = decoder
494 .try_decode_record_batch(&request.data_header, &request.payload)
495 .context(FlightCodecSnafu)?;
496 decoder_timer.observe_duration();
497 Ok(vec![(
498 region_id,
499 RegionRequest::BulkInserts(RegionBulkInsertsRequest {
500 region_id,
501 payload,
502 raw_data: request,
503 skip_wal,
504 partition_expr_version,
505 aligned_schema_version,
506 }),
507 )])
508}
509
510fn make_region_apply_staging_manifest(
511 api::v1::region::ApplyStagingManifestRequest {
512 region_id,
513 partition_expr,
514 central_region_id,
515 manifest_path,
516 }: api::v1::region::ApplyStagingManifestRequest,
517) -> Result<Vec<(RegionId, RegionRequest)>> {
518 let region_id = region_id.into();
519 Ok(vec![(
520 region_id,
521 RegionRequest::ApplyStagingManifest(ApplyStagingManifestRequest {
522 partition_expr,
523 central_region_id: central_region_id.into(),
524 manifest_path,
525 }),
526 )])
527}
528
529#[derive(Debug)]
531pub struct RegionPutRequest {
532 pub rows: Rows,
534 pub hint: Option<WriteHint>,
536 pub skip_wal: bool,
539 pub partition_expr_version: Option<u64>,
541}
542
543#[derive(Debug)]
545pub struct RegionDeleteRequest {
546 pub rows: Rows,
550 pub hint: Option<WriteHint>,
552 pub partition_expr_version: Option<u64>,
554}
555
556#[derive(Debug, Clone)]
557pub struct RegionCreateRequest {
558 pub engine: String,
560 pub column_metadatas: Vec<ColumnMetadata>,
562 pub primary_key: Vec<ColumnId>,
564 pub options: HashMap<String, String>,
566 pub table_dir: String,
568 pub path_type: PathType,
570 pub partition_expr_json: Option<String>,
573 pub requirements: RegionRequirements,
575}
576
577impl RegionCreateRequest {
578 pub fn validate(&self) -> Result<()> {
580 ensure!(
582 self.column_metadatas
583 .iter()
584 .any(|x| x.semantic_type == SemanticType::Timestamp),
585 InvalidRegionRequestSnafu {
586 region_id: RegionId::new(0, 0),
587 err: "missing timestamp column in create region request".to_string(),
588 }
589 );
590
591 let mut column_id_to_indices = HashMap::with_capacity(self.column_metadatas.len());
593 for (i, c) in self.column_metadatas.iter().enumerate() {
594 if let Some(previous) = column_id_to_indices.insert(c.column_id, i) {
595 return InvalidRegionRequestSnafu {
596 region_id: RegionId::new(0, 0),
597 err: format!(
598 "duplicate column id {} (at position {} and {}) in create region request",
599 c.column_id, previous, i
600 ),
601 }
602 .fail();
603 }
604 }
605
606 for column_id in &self.primary_key {
608 ensure!(
609 column_id_to_indices.contains_key(column_id),
610 InvalidRegionRequestSnafu {
611 region_id: RegionId::new(0, 0),
612 err: format!(
613 "missing primary key column {} in create region request",
614 column_id
615 ),
616 }
617 );
618 }
619
620 Ok(())
621 }
622
623 pub fn is_physical_table(&self) -> bool {
625 self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
626 }
627}
628
629#[derive(Debug, Clone)]
630pub struct RegionDropRequest {
631 pub fast_path: bool,
634
635 pub force: bool,
638
639 pub partial_drop: bool,
642}
643
644#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
646#[serde(default)]
647pub struct RegionRequirements {
648 pub object_storage: bool,
650}
651
652impl RegionRequirements {
653 pub fn empty() -> Self {
655 Self::default()
656 }
657
658 pub fn object_storage() -> Self {
660 Self {
661 object_storage: true,
662 }
663 }
664}
665
666impl From<api::v1::region::RegionRequirements> for RegionRequirements {
667 fn from(value: api::v1::region::RegionRequirements) -> Self {
668 Self {
669 object_storage: value.object_storage,
670 }
671 }
672}
673
674impl From<RegionRequirements> for api::v1::region::RegionRequirements {
675 fn from(value: RegionRequirements) -> Self {
676 Self {
677 object_storage: value.object_storage,
678 }
679 }
680}
681
682#[derive(Debug, Clone)]
684pub struct RegionOpenRequest {
685 pub engine: String,
687 pub table_dir: String,
689 pub path_type: PathType,
691 pub options: HashMap<String, String>,
693 pub skip_wal_replay: bool,
695 pub checkpoint: Option<ReplayCheckpoint>,
697 pub requirements: RegionRequirements,
699}
700
701#[derive(Debug, Clone, Copy, PartialEq, Eq)]
702pub struct ReplayCheckpoint {
703 pub entry_id: u64,
704 pub metadata_entry_id: Option<u64>,
705}
706
707impl RegionOpenRequest {
708 pub fn is_physical_table(&self) -> bool {
710 self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
711 }
712}
713
714#[derive(Debug, Clone)]
716pub struct RegionCleanUpRequest {
717 pub engine: String,
719 pub table_dir: String,
721 pub path_type: PathType,
723 pub options: HashMap<String, String>,
725}
726
727impl RegionCleanUpRequest {
728 pub fn is_physical_table(&self) -> bool {
730 self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
731 }
732}
733
734#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
736pub struct RegionCloseRequest {
737 pub flush_on_close: bool,
739}
740
741#[derive(Debug, PartialEq, Eq, Clone)]
743pub struct RegionAlterRequest {
744 pub kind: AlterKind,
746}
747
748impl RegionAlterRequest {
749 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
751 self.kind.validate(metadata)?;
752
753 Ok(())
754 }
755
756 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
760 debug_assert!(self.validate(metadata).is_ok());
761 self.kind.need_alter(metadata)
762 }
763}
764
765impl TryFrom<AlterRequest> for RegionAlterRequest {
766 type Error = MetadataError;
767
768 fn try_from(value: AlterRequest) -> Result<Self> {
769 let kind = value.kind.context(InvalidRawRegionRequestSnafu {
770 err: "missing kind in AlterRequest",
771 })?;
772
773 let kind = AlterKind::try_from(kind)?;
774 Ok(RegionAlterRequest { kind })
775 }
776}
777
778#[derive(Debug, PartialEq, Eq, Clone, AsRefStr)]
780pub enum AlterKind {
781 AddColumns {
783 columns: Vec<AddColumn>,
785 },
786 DropColumns {
788 names: Vec<String>,
790 },
791 ModifyColumnTypes {
796 columns: Vec<ModifyColumnType>,
798 },
799 SetJsonSettings {
801 column_name: String,
803 settings: JsonSettings,
805 },
806 SetRegionOptions { options: Vec<SetRegionOption> },
808 UnsetRegionOptions { keys: Vec<UnsetRegionOption> },
810 SetIndexes { options: Vec<SetIndexOption> },
812 UnsetIndexes { options: Vec<UnsetIndexOption> },
814 DropDefaults {
816 names: Vec<String>,
818 },
819 SetDefaults {
821 columns: Vec<SetDefault>,
823 },
824 SyncColumns {
826 column_metadatas: Vec<ColumnMetadata>,
827 },
828}
829#[derive(Debug, PartialEq, Eq, Clone)]
830pub struct SetDefault {
831 pub name: String,
832 pub default_constraint: Vec<u8>,
833}
834
835#[derive(Debug, PartialEq, Eq, Clone)]
836pub enum SetIndexOption {
837 Fulltext {
838 column_name: String,
839 options: FulltextOptions,
840 },
841 Inverted {
842 column_name: String,
843 },
844 Skipping {
845 column_name: String,
846 options: SkippingIndexOptions,
847 },
848}
849
850impl SetIndexOption {
851 pub fn column_name(&self) -> &String {
853 match self {
854 SetIndexOption::Fulltext { column_name, .. } => column_name,
855 SetIndexOption::Inverted { column_name } => column_name,
856 SetIndexOption::Skipping { column_name, .. } => column_name,
857 }
858 }
859
860 pub fn is_fulltext(&self) -> bool {
862 match self {
863 SetIndexOption::Fulltext { .. } => true,
864 SetIndexOption::Inverted { .. } => false,
865 SetIndexOption::Skipping { .. } => false,
866 }
867 }
868}
869
870impl TryFrom<v1::SetIndex> for SetIndexOption {
871 type Error = MetadataError;
872
873 fn try_from(value: v1::SetIndex) -> Result<Self> {
874 let option = value.options.context(InvalidRawRegionRequestSnafu {
875 err: "missing options in SetIndex",
876 })?;
877
878 let opt = match option {
879 v1::set_index::Options::Fulltext(x) => SetIndexOption::Fulltext {
880 column_name: x.column_name.clone(),
881 options: FulltextOptions::new(
882 x.enable,
883 as_fulltext_option_analyzer(
884 Analyzer::try_from(x.analyzer).context(DecodeProtoSnafu)?,
885 ),
886 x.case_sensitive,
887 as_fulltext_option_backend(
888 PbFulltextBackend::try_from(x.backend).context(DecodeProtoSnafu)?,
889 ),
890 x.granularity as u32,
891 x.false_positive_rate,
892 )
893 .context(InvalidIndexOptionSnafu)?,
894 },
895 v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
896 column_name: i.column_name,
897 },
898 v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
899 column_name: s.column_name,
900 options: SkippingIndexOptions::new(
901 s.granularity as u32,
902 s.false_positive_rate,
903 as_skipping_index_type(
904 PbSkippingIndexType::try_from(s.skipping_index_type)
905 .context(DecodeProtoSnafu)?,
906 ),
907 )
908 .context(InvalidIndexOptionSnafu)?,
909 },
910 };
911
912 Ok(opt)
913 }
914}
915
916#[derive(Debug, PartialEq, Eq, Clone)]
917pub enum UnsetIndexOption {
918 Fulltext { column_name: String },
919 Inverted { column_name: String },
920 Skipping { column_name: String },
921}
922
923impl UnsetIndexOption {
924 pub fn column_name(&self) -> &String {
925 match self {
926 UnsetIndexOption::Fulltext { column_name } => column_name,
927 UnsetIndexOption::Inverted { column_name } => column_name,
928 UnsetIndexOption::Skipping { column_name } => column_name,
929 }
930 }
931
932 pub fn is_fulltext(&self) -> bool {
933 match self {
934 UnsetIndexOption::Fulltext { .. } => true,
935 UnsetIndexOption::Inverted { .. } => false,
936 UnsetIndexOption::Skipping { .. } => false,
937 }
938 }
939}
940
941impl TryFrom<v1::UnsetIndex> for UnsetIndexOption {
942 type Error = MetadataError;
943
944 fn try_from(value: v1::UnsetIndex) -> Result<Self> {
945 let option = value.options.context(InvalidRawRegionRequestSnafu {
946 err: "missing options in UnsetIndex",
947 })?;
948
949 let opt = match option {
950 v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
951 column_name: f.column_name,
952 },
953 v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
954 column_name: i.column_name,
955 },
956 v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
957 column_name: s.column_name,
958 },
959 };
960
961 Ok(opt)
962 }
963}
964
965impl AlterKind {
966 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
970 match self {
971 AlterKind::AddColumns { columns } => {
972 for col_to_add in columns {
973 col_to_add.validate(metadata)?;
974 }
975 }
976 AlterKind::DropColumns { names } => {
977 for name in names {
978 Self::validate_column_to_drop(name, metadata)?;
979 }
980 }
981 AlterKind::ModifyColumnTypes { columns } => {
982 for col_to_change in columns {
983 col_to_change.validate(metadata)?;
984 }
985 }
986 AlterKind::SetJsonSettings { column_name, .. } => {
987 Self::validate_set_json_settings(column_name, metadata)?
988 }
989 AlterKind::SetRegionOptions { .. } => {}
990 AlterKind::UnsetRegionOptions { .. } => {}
991 AlterKind::SetIndexes { options } => {
992 for option in options {
993 Self::validate_column_alter_index_option(
994 option.column_name(),
995 metadata,
996 option.is_fulltext(),
997 )?;
998 }
999 }
1000 AlterKind::UnsetIndexes { options } => {
1001 for option in options {
1002 Self::validate_column_alter_index_option(
1003 option.column_name(),
1004 metadata,
1005 option.is_fulltext(),
1006 )?;
1007 }
1008 }
1009 AlterKind::DropDefaults { names } => {
1010 names
1011 .iter()
1012 .try_for_each(|name| Self::validate_column_existence(name, metadata))?;
1013 }
1014 AlterKind::SetDefaults { columns } => {
1015 columns
1016 .iter()
1017 .try_for_each(|col| Self::validate_column_existence(&col.name, metadata))?;
1018 }
1019 AlterKind::SyncColumns { column_metadatas } => {
1020 let new_primary_keys = column_metadatas
1021 .iter()
1022 .filter(|c| c.semantic_type == SemanticType::Tag)
1023 .map(|c| (c.column_schema.name.as_str(), c.column_id))
1024 .collect::<HashMap<_, _>>();
1025
1026 let old_primary_keys = metadata
1027 .column_metadatas
1028 .iter()
1029 .filter(|c| c.semantic_type == SemanticType::Tag)
1030 .map(|c| (c.column_schema.name.as_str(), c.column_id));
1031
1032 for (name, id) in old_primary_keys {
1033 let primary_key =
1034 new_primary_keys
1035 .get(name)
1036 .with_context(|| InvalidRegionRequestSnafu {
1037 region_id: metadata.region_id,
1038 err: format!("column {} is not a primary key", name),
1039 })?;
1040
1041 ensure!(
1042 *primary_key == id,
1043 InvalidRegionRequestSnafu {
1044 region_id: metadata.region_id,
1045 err: format!(
1046 "column with same name {} has different id, existing: {}, got: {}",
1047 name, id, primary_key
1048 ),
1049 }
1050 );
1051 }
1052
1053 let new_ts_column = column_metadatas
1054 .iter()
1055 .find(|c| c.semantic_type == SemanticType::Timestamp)
1056 .map(|c| (c.column_schema.name.as_str(), c.column_id))
1057 .context(InvalidRegionRequestSnafu {
1058 region_id: metadata.region_id,
1059 err: "timestamp column not found",
1060 })?;
1061
1062 let old_ts_column = metadata
1064 .column_metadatas
1065 .iter()
1066 .find(|c| c.semantic_type == SemanticType::Timestamp)
1067 .map(|c| (c.column_schema.name.as_str(), c.column_id))
1068 .unwrap();
1069
1070 ensure!(
1071 new_ts_column == old_ts_column,
1072 InvalidRegionRequestSnafu {
1073 region_id: metadata.region_id,
1074 err: format!(
1075 "timestamp column {} has different id, existing: {}, got: {}",
1076 old_ts_column.0, old_ts_column.1, new_ts_column.1
1077 ),
1078 }
1079 );
1080 }
1081 }
1082 Ok(())
1083 }
1084
1085 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1087 debug_assert!(self.validate(metadata).is_ok());
1088 match self {
1089 AlterKind::AddColumns { columns } => columns
1090 .iter()
1091 .any(|col_to_add| col_to_add.need_alter(metadata)),
1092 AlterKind::DropColumns { names } => names
1093 .iter()
1094 .any(|name| metadata.column_by_name(name).is_some()),
1095 AlterKind::ModifyColumnTypes { columns } => columns
1096 .iter()
1097 .any(|col_to_change| col_to_change.need_alter(metadata)),
1098 AlterKind::SetJsonSettings {
1099 column_name,
1100 settings,
1101 } => metadata.column_by_name(column_name).is_some_and(|col| {
1102 col.column_schema
1103 .extension_type::<Json2ExtensionType>()
1104 .ok()
1105 .flatten()
1106 .is_none_or(|extension| {
1107 !extension.metadata().json_settings().equivalent(settings)
1108 })
1109 }),
1110 AlterKind::SetRegionOptions { .. } => true,
1111 AlterKind::UnsetRegionOptions { .. } => true,
1112 AlterKind::SetIndexes { options, .. } => options
1113 .iter()
1114 .any(|option| metadata.column_by_name(option.column_name()).is_some()),
1115 AlterKind::UnsetIndexes { options } => options
1116 .iter()
1117 .any(|option| metadata.column_by_name(option.column_name()).is_some()),
1118 AlterKind::DropDefaults { names } => names
1119 .iter()
1120 .any(|name| metadata.column_by_name(name).is_some()),
1121
1122 AlterKind::SetDefaults { columns } => columns
1123 .iter()
1124 .any(|x| metadata.column_by_name(&x.name).is_some()),
1125 AlterKind::SyncColumns { column_metadatas } => {
1126 metadata.column_metadatas != *column_metadatas
1127 }
1128 }
1129 }
1130
1131 fn validate_column_to_drop(name: &str, metadata: &RegionMetadata) -> Result<()> {
1133 let Some(column) = metadata.column_by_name(name) else {
1134 return Ok(());
1135 };
1136 ensure!(
1137 column.semantic_type == SemanticType::Field,
1138 InvalidRegionRequestSnafu {
1139 region_id: metadata.region_id,
1140 err: format!("column {} is not a field and could not be dropped", name),
1141 }
1142 );
1143 Ok(())
1144 }
1145
1146 fn validate_column_alter_index_option(
1148 column_name: &String,
1149 metadata: &RegionMetadata,
1150 is_fulltext: bool,
1151 ) -> Result<()> {
1152 let column = metadata
1153 .column_by_name(column_name)
1154 .context(InvalidRegionRequestSnafu {
1155 region_id: metadata.region_id,
1156 err: format!("column {} not found", column_name),
1157 })?;
1158
1159 if is_fulltext {
1160 ensure!(
1161 column.column_schema.data_type.is_string(),
1162 InvalidRegionRequestSnafu {
1163 region_id: metadata.region_id,
1164 err: format!(
1165 "cannot change alter index options for non-string column {}",
1166 column_name
1167 ),
1168 }
1169 );
1170 }
1171
1172 Ok(())
1173 }
1174
1175 fn validate_column_existence(column_name: &String, metadata: &RegionMetadata) -> Result<()> {
1177 metadata
1178 .column_by_name(column_name)
1179 .context(InvalidRegionRequestSnafu {
1180 region_id: metadata.region_id,
1181 err: format!("column {} not found", column_name),
1182 })?;
1183
1184 Ok(())
1185 }
1186
1187 fn validate_set_json_settings(col_name: &String, metadata: &RegionMetadata) -> Result<()> {
1188 let region_id = metadata.region_id;
1189
1190 let col = metadata
1191 .column_by_name(col_name)
1192 .with_context(|| InvalidRegionRequestSnafu {
1193 region_id,
1194 err: format!("column {} not found", col_name),
1195 })?;
1196
1197 ensure!(
1198 col.semantic_type == SemanticType::Field,
1199 InvalidRegionRequestSnafu {
1200 region_id,
1201 err: format!("column {} is not a field column", col_name),
1202 }
1203 );
1204 ensure!(
1205 col.column_schema.data_type.is_json2(),
1206 InvalidRegionRequestSnafu {
1207 region_id,
1208 err: format!("column {} is not a JSON2 column", col_name),
1209 }
1210 );
1211
1212 Ok(())
1213 }
1214}
1215
1216impl TryFrom<alter_request::Kind> for AlterKind {
1217 type Error = MetadataError;
1218
1219 fn try_from(kind: alter_request::Kind) -> Result<Self> {
1220 let alter_kind = match kind {
1221 alter_request::Kind::AddColumns(x) => {
1222 let columns = x
1223 .add_columns
1224 .into_iter()
1225 .map(|x| x.try_into())
1226 .collect::<Result<Vec<_>>>()?;
1227 AlterKind::AddColumns { columns }
1228 }
1229 alter_request::Kind::ModifyColumnTypes(x) => {
1230 let columns = x
1231 .modify_column_types
1232 .into_iter()
1233 .map(|x| x.into())
1234 .collect::<Vec<_>>();
1235 AlterKind::ModifyColumnTypes { columns }
1236 }
1237 alter_request::Kind::SetJsonSettings(x) => {
1238 let settings = x.settings.context(InvalidRawRegionRequestSnafu {
1239 err: "missing settings in SetJsonSettings",
1240 })?;
1241 AlterKind::SetJsonSettings {
1242 column_name: x.column_name,
1243 settings: json_settings_from_proto(settings)?,
1244 }
1245 }
1246 alter_request::Kind::DropColumns(x) => {
1247 let names = x.drop_columns.into_iter().map(|x| x.name).collect();
1248 AlterKind::DropColumns { names }
1249 }
1250 alter_request::Kind::SetTableOptions(options) => AlterKind::SetRegionOptions {
1251 options: options
1252 .table_options
1253 .iter()
1254 .map(TryFrom::try_from)
1255 .collect::<Result<Vec<_>>>()?,
1256 },
1257 alter_request::Kind::UnsetTableOptions(options) => AlterKind::UnsetRegionOptions {
1258 keys: options
1259 .keys
1260 .iter()
1261 .map(|key| UnsetRegionOption::try_from(key.as_str()))
1262 .collect::<Result<Vec<_>>>()?,
1263 },
1264 alter_request::Kind::SetIndex(o) => AlterKind::SetIndexes {
1265 options: vec![SetIndexOption::try_from(o)?],
1266 },
1267 alter_request::Kind::UnsetIndex(o) => AlterKind::UnsetIndexes {
1268 options: vec![UnsetIndexOption::try_from(o)?],
1269 },
1270 alter_request::Kind::SetIndexes(o) => AlterKind::SetIndexes {
1271 options: o
1272 .set_indexes
1273 .into_iter()
1274 .map(SetIndexOption::try_from)
1275 .collect::<Result<Vec<_>>>()?,
1276 },
1277 alter_request::Kind::UnsetIndexes(o) => AlterKind::UnsetIndexes {
1278 options: o
1279 .unset_indexes
1280 .into_iter()
1281 .map(UnsetIndexOption::try_from)
1282 .collect::<Result<Vec<_>>>()?,
1283 },
1284 alter_request::Kind::DropDefaults(x) => AlterKind::DropDefaults {
1285 names: x.drop_defaults.into_iter().map(|x| x.column_name).collect(),
1286 },
1287 alter_request::Kind::SetDefaults(x) => AlterKind::SetDefaults {
1288 columns: x
1289 .set_defaults
1290 .into_iter()
1291 .map(|x| {
1292 Ok(SetDefault {
1293 name: x.column_name,
1294 default_constraint: x.default_constraint.clone(),
1295 })
1296 })
1297 .collect::<Result<Vec<_>>>()?,
1298 },
1299 alter_request::Kind::SyncColumns(x) => AlterKind::SyncColumns {
1300 column_metadatas: x
1301 .column_defs
1302 .into_iter()
1303 .map(ColumnMetadata::try_from_column_def)
1304 .collect::<Result<Vec<_>>>()?,
1305 },
1306 };
1307
1308 Ok(alter_kind)
1309 }
1310}
1311
1312#[derive(Debug, PartialEq, Eq, Clone)]
1314pub struct AddColumn {
1315 pub column_metadata: ColumnMetadata,
1317 pub location: Option<AddColumnLocation>,
1320}
1321
1322impl AddColumn {
1323 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
1328 ensure!(
1329 self.column_metadata.column_schema.is_nullable()
1330 || self
1331 .column_metadata
1332 .column_schema
1333 .default_constraint()
1334 .is_some(),
1335 InvalidRegionRequestSnafu {
1336 region_id: metadata.region_id,
1337 err: format!(
1338 "no default value for column {}",
1339 self.column_metadata.column_schema.name
1340 ),
1341 }
1342 );
1343
1344 if let Some(existing_column) =
1345 metadata.column_by_name(&self.column_metadata.column_schema.name)
1346 {
1347 ensure!(
1349 *existing_column == self.column_metadata,
1350 InvalidRegionRequestSnafu {
1351 region_id: metadata.region_id,
1352 err: format!(
1353 "column {} already exists with different metadata, existing: {:?}, got: {:?}",
1354 self.column_metadata.column_schema.name,
1355 existing_column,
1356 self.column_metadata,
1357 ),
1358 }
1359 );
1360 ensure!(
1361 self.location.is_none(),
1362 InvalidRegionRequestSnafu {
1363 region_id: metadata.region_id,
1364 err: format!(
1365 "column {} already exists, but location is specified",
1366 self.column_metadata.column_schema.name
1367 ),
1368 }
1369 );
1370 }
1371
1372 if let Some(existing_column) = metadata.column_by_id(self.column_metadata.column_id) {
1373 ensure!(
1375 existing_column.column_schema.name == self.column_metadata.column_schema.name,
1376 InvalidRegionRequestSnafu {
1377 region_id: metadata.region_id,
1378 err: format!(
1379 "column id {} already exists with different name {}",
1380 self.column_metadata.column_id, existing_column.column_schema.name
1381 ),
1382 }
1383 );
1384 }
1385
1386 Ok(())
1387 }
1388
1389 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1391 debug_assert!(self.validate(metadata).is_ok());
1392 metadata
1393 .column_by_name(&self.column_metadata.column_schema.name)
1394 .is_none()
1395 }
1396}
1397
1398impl TryFrom<v1::region::AddColumn> for AddColumn {
1399 type Error = MetadataError;
1400
1401 fn try_from(add_column: v1::region::AddColumn) -> Result<Self> {
1402 let column_def = add_column
1403 .column_def
1404 .context(InvalidRawRegionRequestSnafu {
1405 err: "missing column_def in AddColumn",
1406 })?;
1407
1408 let column_metadata = ColumnMetadata::try_from_column_def(column_def)?;
1409 let location = add_column
1410 .location
1411 .map(AddColumnLocation::try_from)
1412 .transpose()?;
1413
1414 Ok(AddColumn {
1415 column_metadata,
1416 location,
1417 })
1418 }
1419}
1420
1421#[derive(Debug, PartialEq, Eq, Clone)]
1423pub enum AddColumnLocation {
1424 First,
1426 After {
1428 column_name: String,
1430 },
1431}
1432
1433impl TryFrom<v1::AddColumnLocation> for AddColumnLocation {
1434 type Error = MetadataError;
1435
1436 fn try_from(location: v1::AddColumnLocation) -> Result<Self> {
1437 let location_type = LocationType::try_from(location.location_type)
1438 .map_err(|e| InvalidRawRegionRequestSnafu { err: e.to_string() }.build())?;
1439 let add_column_location = match location_type {
1440 LocationType::First => AddColumnLocation::First,
1441 LocationType::After => AddColumnLocation::After {
1442 column_name: location.after_column_name,
1443 },
1444 };
1445
1446 Ok(add_column_location)
1447 }
1448}
1449
1450#[derive(Debug, PartialEq, Eq, Clone)]
1452pub struct ModifyColumnType {
1453 pub column_name: String,
1455 pub target_type: ConcreteDataType,
1457}
1458
1459impl ModifyColumnType {
1460 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
1462 let column_meta = metadata
1463 .column_by_name(&self.column_name)
1464 .with_context(|| InvalidRegionRequestSnafu {
1465 region_id: metadata.region_id,
1466 err: format!("column {} not found", self.column_name),
1467 })?;
1468
1469 match column_meta.semantic_type {
1470 SemanticType::Field => {
1471 ensure!(
1472 column_meta
1473 .column_schema
1474 .data_type
1475 .can_arrow_type_cast_to(&self.target_type),
1476 InvalidRegionRequestSnafu {
1477 region_id: metadata.region_id,
1478 err: format!(
1479 "column '{}' cannot be cast automatically to type '{}'",
1480 self.column_name, self.target_type
1481 ),
1482 }
1483 );
1484 }
1485 SemanticType::Timestamp => {
1490 ensure!(
1491 column_meta.column_schema.data_type == self.target_type
1492 || column_meta
1493 .column_schema
1494 .data_type
1495 .is_timestamp_unit_widening_to(&self.target_type),
1496 InvalidRegionRequestSnafu {
1497 region_id: metadata.region_id,
1498 err: time_index_not_widening_error(
1499 &column_meta.column_schema.name,
1500 &column_meta.column_schema.data_type,
1501 &self.target_type,
1502 ),
1503 }
1504 );
1505 }
1506 SemanticType::Tag => {
1507 return InvalidRegionRequestSnafu {
1508 region_id: metadata.region_id,
1509 err: format!(
1510 "tag column '{}' cannot change type, it is part of the primary key",
1511 self.column_name
1512 ),
1513 }
1514 .fail();
1515 }
1516 }
1517
1518 Ok(())
1519 }
1520
1521 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1525 debug_assert!(self.validate(metadata).is_ok());
1526 metadata
1527 .column_by_name(&self.column_name)
1528 .is_some_and(|column| column.column_schema.data_type != self.target_type)
1529 }
1530}
1531
1532impl From<v1::ModifyColumnType> for ModifyColumnType {
1533 fn from(modify_column_type: v1::ModifyColumnType) -> Self {
1534 let target_type = ColumnDataTypeWrapper::new(
1535 modify_column_type.target_type(),
1536 modify_column_type.target_type_extension,
1537 )
1538 .into();
1539
1540 ModifyColumnType {
1541 column_name: modify_column_type.column_name,
1542 target_type,
1543 }
1544 }
1545}
1546
1547fn json_settings_from_proto(settings: v1::JsonSettings) -> Result<JsonSettings> {
1548 let type_hints = settings
1549 .type_hints
1550 .into_iter()
1551 .map(|hint| {
1552 let wrapper = ColumnDataTypeWrapper::try_new(hint.data_type, hint.datatype_extension)
1553 .map_err(|err| {
1554 InvalidRawRegionRequestSnafu {
1555 err: err.to_string(),
1556 }
1557 .build()
1558 })?;
1559 let data_type = ConcreteDataType::from(wrapper);
1560
1561 Ok(JsonTypeHint {
1562 path: hint.path,
1563 data_type,
1564 inverted_index: false,
1565 })
1566 })
1567 .collect::<Result<Vec<_>>>()?;
1568
1569 JsonSettings::try_new(type_hints, settings.max_auto_expanded_paths).map_err(|err| {
1570 InvalidRawRegionRequestSnafu {
1571 err: err.to_string(),
1572 }
1573 .build()
1574 })
1575}
1576
1577#[derive(Debug, Eq, PartialEq, Clone)]
1582pub enum SetRegionOption {
1583 WriteBufferSize(Option<ReadableSize>),
1584 Ttl(Option<TimeToLive>),
1585 Twsc(String, String),
1587 Format(String),
1589 AppendMode(bool),
1591 AutoFlushInterval(Option<Duration>),
1593 MaxRowGroupRowCount(Option<usize>),
1595 PreserveRowSequence(bool),
1596 SkipWal(bool),
1598}
1599
1600#[derive(Serialize, Deserialize)]
1601enum SetRegionOptionSerde {
1602 WriteBufferSize(Option<ReadableSize>),
1603 Ttl(Option<TimeToLive>),
1604 Twsc(String, String),
1605 Format(String),
1606 AppendMode(bool),
1607 AutoFlushInterval(Option<Duration>),
1608 MaxRowGroupRowCount(Option<usize>),
1609 PreserveRowSequence(bool),
1610 SkipWal(bool),
1611}
1612
1613impl Serialize for SetRegionOption {
1614 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1615 where
1616 S: Serializer,
1617 {
1618 if matches!(self, Self::SkipWal(true)) {
1620 return serializer.serialize_unit_variant("SetRegionOption", 8, "SkipWal");
1621 }
1622
1623 let option = match self {
1624 Self::WriteBufferSize(value) => SetRegionOptionSerde::WriteBufferSize(*value),
1625 Self::Ttl(value) => SetRegionOptionSerde::Ttl(*value),
1626 Self::Twsc(key, value) => SetRegionOptionSerde::Twsc(key.clone(), value.clone()),
1627 Self::Format(value) => SetRegionOptionSerde::Format(value.clone()),
1628 Self::AppendMode(value) => SetRegionOptionSerde::AppendMode(*value),
1629 Self::AutoFlushInterval(value) => SetRegionOptionSerde::AutoFlushInterval(*value),
1630 Self::MaxRowGroupRowCount(value) => SetRegionOptionSerde::MaxRowGroupRowCount(*value),
1631 Self::PreserveRowSequence(value) => SetRegionOptionSerde::PreserveRowSequence(*value),
1632 Self::SkipWal(value) => SetRegionOptionSerde::SkipWal(*value),
1633 };
1634 option.serialize(serializer)
1635 }
1636}
1637
1638#[derive(Deserialize)]
1639enum LegacySetRegionOption {
1640 SkipWal,
1641}
1642
1643#[derive(Deserialize)]
1644#[serde(untagged)]
1645enum BackwardCompatibleSetRegionOption {
1646 Current(SetRegionOptionSerde),
1647 Legacy(LegacySetRegionOption),
1648}
1649
1650impl From<SetRegionOptionSerde> for SetRegionOption {
1651 fn from(option: SetRegionOptionSerde) -> Self {
1652 match option {
1653 SetRegionOptionSerde::WriteBufferSize(value) => Self::WriteBufferSize(value),
1654 SetRegionOptionSerde::Ttl(value) => Self::Ttl(value),
1655 SetRegionOptionSerde::Twsc(key, value) => Self::Twsc(key, value),
1656 SetRegionOptionSerde::Format(value) => Self::Format(value),
1657 SetRegionOptionSerde::AppendMode(value) => Self::AppendMode(value),
1658 SetRegionOptionSerde::AutoFlushInterval(value) => Self::AutoFlushInterval(value),
1659 SetRegionOptionSerde::MaxRowGroupRowCount(value) => Self::MaxRowGroupRowCount(value),
1660 SetRegionOptionSerde::PreserveRowSequence(value) => Self::PreserveRowSequence(value),
1661 SetRegionOptionSerde::SkipWal(value) => Self::SkipWal(value),
1662 }
1663 }
1664}
1665
1666impl<'de> Deserialize<'de> for SetRegionOption {
1667 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1668 where
1669 D: Deserializer<'de>,
1670 {
1671 Ok(
1672 match BackwardCompatibleSetRegionOption::deserialize(deserializer)? {
1673 BackwardCompatibleSetRegionOption::Current(option) => option.into(),
1674 BackwardCompatibleSetRegionOption::Legacy(LegacySetRegionOption::SkipWal) => {
1675 Self::SkipWal(true)
1676 }
1677 },
1678 )
1679 }
1680}
1681
1682impl TryFrom<&PbOption> for SetRegionOption {
1683 type Error = MetadataError;
1684
1685 fn try_from(value: &PbOption) -> std::result::Result<Self, Self::Error> {
1686 let PbOption { key, value } = value;
1687 match key.as_str() {
1688 WRITE_BUFFER_SIZE_KEY => {
1689 let size = value
1690 .parse::<ReadableSize>()
1691 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1692 Ok(Self::WriteBufferSize(Some(size)))
1693 }
1694 TTL_KEY => {
1695 let ttl = TimeToLive::from_humantime_or_str(value)
1696 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1697
1698 Ok(Self::Ttl(Some(ttl)))
1699 }
1700 TWCS_TRIGGER_FILE_NUM
1701 | TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM
1702 | TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER
1703 | TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM
1704 | TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER
1705 | TWCS_MAX_OUTPUT_FILE_SIZE
1706 | TWCS_TIME_WINDOW => Ok(Self::Twsc(key.clone(), value.clone())),
1707 SST_FORMAT_KEY => Ok(Self::Format(value.clone())),
1708 APPEND_MODE_KEY => {
1709 let append_mode = value
1710 .parse::<bool>()
1711 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1712 Ok(Self::AppendMode(append_mode))
1713 }
1714 AUTO_FLUSH_INTERVAL_KEY => {
1715 if value.is_empty() {
1716 return Ok(Self::AutoFlushInterval(None));
1720 }
1721 let interval = humantime::parse_duration(value)
1722 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1723 if interval <= Duration::ZERO {
1724 return InvalidSetRegionOptionRequestSnafu { key, value }.fail();
1725 }
1726 Ok(Self::AutoFlushInterval(Some(interval)))
1727 }
1728 MAX_ROW_GROUP_ROW_COUNT => {
1729 if value.is_empty() {
1730 return Ok(Self::MaxRowGroupRowCount(None));
1731 }
1732 let row_count = value
1733 .parse::<usize>()
1734 .ok()
1735 .filter(|row_count| {
1736 *row_count > 0 && *row_count <= MAX_ROW_GROUP_ROW_COUNT_LIMIT
1737 })
1738 .ok_or_else(|| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1739 Ok(Self::MaxRowGroupRowCount(Some(row_count)))
1740 }
1741 PRESERVE_ROW_SEQUENCE => {
1742 let preserve = value
1743 .parse::<bool>()
1744 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1745 Ok(Self::PreserveRowSequence(preserve))
1746 }
1747 SKIP_WAL_KEY => {
1748 let skip_wal = value
1749 .parse::<bool>()
1750 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1751 Ok(Self::SkipWal(skip_wal))
1752 }
1753 _ => InvalidSetRegionOptionRequestSnafu { key, value }.fail(),
1754 }
1755 }
1756}
1757
1758impl From<&UnsetRegionOption> for SetRegionOption {
1759 fn from(unset_option: &UnsetRegionOption) -> Self {
1760 match unset_option {
1761 UnsetRegionOption::TwcsTriggerFileNum => {
1762 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1763 }
1764 UnsetRegionOption::TwcsActiveWindowTriggerFileNum => {
1765 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1766 }
1767 UnsetRegionOption::TwcsActiveWindowL1MergeTrigger => {
1768 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1769 }
1770 UnsetRegionOption::TwcsInactiveWindowTriggerFileNum => {
1771 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1772 }
1773 UnsetRegionOption::TwcsInactiveWindowL1MergeTrigger => {
1774 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1775 }
1776 UnsetRegionOption::TwcsMaxOutputFileSize => {
1777 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1778 }
1779 UnsetRegionOption::TwcsTimeWindow => {
1780 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1781 }
1782 UnsetRegionOption::Ttl => SetRegionOption::Ttl(Default::default()),
1783 UnsetRegionOption::MaxRowGroupRowCount => SetRegionOption::MaxRowGroupRowCount(None),
1784 UnsetRegionOption::WriteBufferSize => SetRegionOption::WriteBufferSize(None),
1785 UnsetRegionOption::PreserveRowSequence => SetRegionOption::PreserveRowSequence(false),
1786 }
1787 }
1788}
1789
1790impl TryFrom<&str> for UnsetRegionOption {
1791 type Error = MetadataError;
1792
1793 fn try_from(key: &str) -> Result<Self> {
1794 match key.to_ascii_lowercase().as_str() {
1795 TTL_KEY => Ok(Self::Ttl),
1796 WRITE_BUFFER_SIZE_KEY => Ok(Self::WriteBufferSize),
1797 TWCS_TRIGGER_FILE_NUM => Ok(Self::TwcsTriggerFileNum),
1798 TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM => Ok(Self::TwcsActiveWindowTriggerFileNum),
1799 TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER => Ok(Self::TwcsActiveWindowL1MergeTrigger),
1800 TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM => Ok(Self::TwcsInactiveWindowTriggerFileNum),
1801 TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER => Ok(Self::TwcsInactiveWindowL1MergeTrigger),
1802 TWCS_MAX_OUTPUT_FILE_SIZE => Ok(Self::TwcsMaxOutputFileSize),
1803 TWCS_TIME_WINDOW => Ok(Self::TwcsTimeWindow),
1804 MAX_ROW_GROUP_ROW_COUNT => Ok(Self::MaxRowGroupRowCount),
1805 PRESERVE_ROW_SEQUENCE => Ok(Self::PreserveRowSequence),
1806 _ => InvalidUnsetRegionOptionRequestSnafu { key }.fail(),
1807 }
1808 }
1809}
1810
1811#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
1812pub enum UnsetRegionOption {
1813 TwcsTriggerFileNum,
1814 TwcsActiveWindowTriggerFileNum,
1815 TwcsInactiveWindowTriggerFileNum,
1816 TwcsInactiveWindowL1MergeTrigger,
1817 TwcsMaxOutputFileSize,
1818 TwcsTimeWindow,
1819 Ttl,
1820 MaxRowGroupRowCount,
1821 WriteBufferSize,
1822 PreserveRowSequence,
1823 TwcsActiveWindowL1MergeTrigger,
1824}
1825
1826impl UnsetRegionOption {
1827 pub fn as_str(&self) -> &str {
1828 match self {
1829 Self::Ttl => TTL_KEY,
1830 Self::WriteBufferSize => WRITE_BUFFER_SIZE_KEY,
1831 Self::TwcsTriggerFileNum => TWCS_TRIGGER_FILE_NUM,
1832 Self::TwcsActiveWindowTriggerFileNum => TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM,
1833 Self::TwcsActiveWindowL1MergeTrigger => TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER,
1834 Self::TwcsInactiveWindowTriggerFileNum => TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM,
1835 Self::TwcsInactiveWindowL1MergeTrigger => TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER,
1836 Self::TwcsMaxOutputFileSize => TWCS_MAX_OUTPUT_FILE_SIZE,
1837 Self::TwcsTimeWindow => TWCS_TIME_WINDOW,
1838 Self::MaxRowGroupRowCount => MAX_ROW_GROUP_ROW_COUNT,
1839 Self::PreserveRowSequence => PRESERVE_ROW_SEQUENCE,
1840 }
1841 }
1842}
1843
1844impl Display for UnsetRegionOption {
1845 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1846 write!(f, "{}", self.as_str())
1847 }
1848}
1849
1850#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1851pub enum RegionFlushReason {
1852 RegionMigration,
1854 Repartition,
1856 RemoteWalPrune,
1858 Closing,
1860 Downgrading,
1862}
1863
1864#[derive(Debug, Clone, Default)]
1865pub struct RegionFlushRequest {
1866 pub row_group_size: Option<usize>,
1867 pub reason: Option<RegionFlushReason>,
1868}
1869
1870#[derive(Debug)]
1871pub struct RegionCompactRequest {
1872 pub options: compact_request::Options,
1873 pub parallelism: Option<u32>,
1874 pub time_range: Option<TimestampRange>,
1875}
1876
1877impl Default for RegionCompactRequest {
1878 fn default() -> Self {
1879 Self {
1880 options: compact_request::Options::Regular(Default::default()),
1882 parallelism: None,
1883 time_range: None,
1884 }
1885 }
1886}
1887
1888#[derive(Debug, Clone, Default)]
1889pub struct RegionBuildIndexRequest {
1890 pub options: Option<build_index_request::Options>,
1892}
1893
1894#[derive(Debug)]
1896pub enum RegionTruncateRequest {
1897 All,
1899 Unflushed,
1909 ByTimeRanges {
1910 time_ranges: Vec<(Timestamp, Timestamp)>,
1914 },
1915}
1916
1917#[derive(Debug, Clone, Copy, Default)]
1922pub struct RegionCatchupRequest {
1923 pub set_writable: bool,
1925 pub entry_id: Option<entry::Id>,
1928 pub metadata_entry_id: Option<entry::Id>,
1932 pub checkpoint: Option<ReplayCheckpoint>,
1934}
1935
1936#[derive(Debug, Clone)]
1937pub struct RegionBulkInsertsRequest {
1938 pub skip_wal: bool,
1940 pub region_id: RegionId,
1941 pub payload: DfRecordBatch,
1942 pub raw_data: ArrowIpc,
1943 pub partition_expr_version: Option<u64>,
1944 pub aligned_schema_version: Option<u64>,
1945}
1946
1947impl RegionBulkInsertsRequest {
1948 pub fn estimated_size(&self) -> usize {
1949 self.payload.get_array_memory_size()
1950 }
1951}
1952
1953#[derive(Debug, Clone, PartialEq, Eq)]
1959pub enum StagingPartitionDirective {
1960 UpdatePartitionExpr(String),
1961 RejectAllWrites,
1962}
1963
1964impl StagingPartitionDirective {
1965 pub fn partition_expr(&self) -> Option<&str> {
1967 match self {
1968 Self::UpdatePartitionExpr(expr) => Some(expr),
1969 Self::RejectAllWrites => None,
1970 }
1971 }
1972}
1973
1974#[derive(Debug, Clone)]
1975pub struct EnterStagingRequest {
1976 pub partition_directive: StagingPartitionDirective,
1978}
1979
1980impl EnterStagingRequest {
1981 pub fn with_partition_expr(partition_expr: String) -> Self {
1983 Self {
1984 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(partition_expr),
1985 }
1986 }
1987}
1988
1989#[derive(Debug, Clone)]
2008pub struct ApplyStagingManifestRequest {
2009 pub partition_expr: String,
2011 pub central_region_id: RegionId,
2013 pub manifest_path: String,
2016}
2017
2018impl fmt::Display for RegionRequest {
2019 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020 match self {
2021 RegionRequest::Put(_) => write!(f, "Put"),
2022 RegionRequest::Delete(_) => write!(f, "Delete"),
2023 RegionRequest::Create(_) => write!(f, "Create"),
2024 RegionRequest::Drop(_) => write!(f, "Drop"),
2025 RegionRequest::Open(_) => write!(f, "Open"),
2026 RegionRequest::CleanUp(_) => write!(f, "CleanUp"),
2027 RegionRequest::Close(_) => write!(f, "Close"),
2028 RegionRequest::Alter(_) => write!(f, "Alter"),
2029 RegionRequest::Flush(_) => write!(f, "Flush"),
2030 RegionRequest::Compact(_) => write!(f, "Compact"),
2031 RegionRequest::BuildIndex(_) => write!(f, "BuildIndex"),
2032 RegionRequest::Truncate(_) => write!(f, "Truncate"),
2033 RegionRequest::Catchup(_) => write!(f, "Catchup"),
2034 RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"),
2035 RegionRequest::EnterStaging(_) => write!(f, "EnterStaging"),
2036 RegionRequest::ApplyStagingManifest(_) => write!(f, "ApplyStagingManifest"),
2037 }
2038 }
2039}
2040
2041#[cfg(test)]
2042mod tests {
2043
2044 use api::v1::region::RegionColumnDef;
2045 use api::v1::{ColumnDataType, ColumnDef};
2046 use common_time::range::TimestampRange;
2047 use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
2048 use datatypes::json::JsonSettings;
2049 use datatypes::prelude::ConcreteDataType;
2050 use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
2051 use datatypes::types::JsonType;
2052
2053 use super::*;
2054 use crate::metadata::RegionMetadataBuilder;
2055
2056 #[test]
2057 fn test_build_index_options_round_trip() {
2058 use prost::Message;
2059
2060 for options in [
2061 None,
2062 Some(build_index_request::Options::SstIndex(Default::default())),
2063 Some(build_index_request::Options::SeriesIndex(Default::default())),
2064 ] {
2065 let request = BuildIndexRequest {
2066 region_id: 42,
2067 options,
2068 };
2069 let decoded = BuildIndexRequest::decode(request.encode_to_vec().as_slice()).unwrap();
2070 let requests = make_region_build_index(decoded).unwrap();
2071 assert_eq!(1, requests.len());
2072 assert_eq!(RegionId::from_u64(42), requests[0].0);
2073 let RegionRequest::BuildIndex(request) = &requests[0].1 else {
2074 panic!("expected build-index request");
2075 };
2076 assert_eq!(options, request.options);
2077 }
2078 let legacy = BuildIndexRequest::decode(&[0x08, 42][..]).unwrap();
2080 assert!(legacy.options.is_none());
2081 }
2082
2083 #[test]
2084 fn test_make_region_puts_preserves_skip_wal() {
2085 let region_id = RegionId::new(42, 3);
2086 let rows = Rows::default();
2087 let requests = make_region_puts(InsertRequests {
2088 requests: [false, true, false]
2089 .into_iter()
2090 .map(|skip_wal| api::v1::region::InsertRequest {
2091 region_id: region_id.as_u64(),
2092 rows: Some(rows.clone()),
2093 partition_expr_version: Some(api::v1::PartitionExprVersion { value: 7 }),
2094 skip_wal,
2095 })
2096 .collect(),
2097 })
2098 .unwrap();
2099
2100 assert_eq!(3, requests.len());
2101 for ((id, request), skip_wal) in requests.into_iter().zip([false, true, false]) {
2102 assert_eq!(region_id, id);
2103 let RegionRequest::Put(request) = request else {
2104 panic!("expected a put request");
2105 };
2106 assert_eq!(rows, request.rows);
2107 assert_eq!(skip_wal, request.skip_wal);
2108 assert_eq!(Some(7), request.partition_expr_version);
2109 assert!(request.hint.is_none());
2110 }
2111 }
2112
2113 #[test]
2114 fn test_make_region_compact_with_time_range() {
2115 let requests = make_region_compact(CompactRequest {
2116 region_id: 42,
2117 time_range: Some(api::v1::region::CompactionTimeRange {
2118 start: 1_000,
2119 end: 2_000,
2120 time_unit: api::v1::TimeUnit::Microsecond as i32,
2121 }),
2122 ..Default::default()
2123 })
2124 .unwrap();
2125
2126 let RegionRequest::Compact(request) = &requests[0].1 else {
2127 unreachable!();
2128 };
2129 assert_eq!(
2130 Some(
2131 TimestampRange::new(
2132 Timestamp::new_microsecond(1_000),
2133 Timestamp::new_microsecond(2_000),
2134 )
2135 .unwrap()
2136 ),
2137 request.time_range
2138 );
2139 }
2140
2141 #[test]
2142 fn test_make_region_truncate_unflushed() {
2143 let region_id = RegionId::new(42, 3);
2144 let requests =
2145 RegionRequest::try_from_request_body(region_request::Body::Truncate(TruncateRequest {
2146 region_id: region_id.as_u64(),
2147 kind: Some(truncate_request::Kind::Unflushed(
2148 api::v1::region::Unflushed {},
2149 )),
2150 }))
2151 .unwrap();
2152
2153 assert_eq!(region_id, requests[0].0);
2154 assert!(matches!(
2155 requests[0].1,
2156 RegionRequest::Truncate(RegionTruncateRequest::Unflushed)
2157 ));
2158 }
2159
2160 #[test]
2161 fn test_make_region_truncate_requires_kind() {
2162 let error =
2163 RegionRequest::try_from_request_body(region_request::Body::Truncate(TruncateRequest {
2164 region_id: RegionId::new(42, 3).as_u64(),
2165 kind: None,
2166 }))
2167 .unwrap_err();
2168
2169 assert!(
2170 error
2171 .to_string()
2172 .contains("missing kind in TruncateRequest")
2173 );
2174 }
2175
2176 #[test]
2177 fn test_from_proto_location() {
2178 let proto_location = v1::AddColumnLocation {
2179 location_type: LocationType::First as i32,
2180 after_column_name: String::default(),
2181 };
2182 let location = AddColumnLocation::try_from(proto_location).unwrap();
2183 assert_eq!(location, AddColumnLocation::First);
2184
2185 let proto_location = v1::AddColumnLocation {
2186 location_type: 10,
2187 after_column_name: String::default(),
2188 };
2189 AddColumnLocation::try_from(proto_location).unwrap_err();
2190
2191 let proto_location = v1::AddColumnLocation {
2192 location_type: LocationType::After as i32,
2193 after_column_name: "a".to_string(),
2194 };
2195 let location = AddColumnLocation::try_from(proto_location).unwrap();
2196 assert_eq!(
2197 location,
2198 AddColumnLocation::After {
2199 column_name: "a".to_string()
2200 }
2201 );
2202 }
2203
2204 #[test]
2205 fn test_from_none_proto_add_column() {
2206 AddColumn::try_from(v1::region::AddColumn {
2207 column_def: None,
2208 location: None,
2209 })
2210 .unwrap_err();
2211 }
2212
2213 #[test]
2214 fn test_set_region_option_auto_flush_interval_try_from() {
2215 use std::time::Duration;
2216
2217 let pb = PbOption {
2219 key: "auto_flush_interval".to_string(),
2220 value: "5m".to_string(),
2221 };
2222 let opt = SetRegionOption::try_from(&pb).unwrap();
2223 assert_eq!(
2224 opt,
2225 SetRegionOption::AutoFlushInterval(Some(Duration::from_secs(300)))
2226 );
2227
2228 let pb = PbOption {
2230 key: "auto_flush_interval".to_string(),
2231 value: String::new(),
2232 };
2233 let opt = SetRegionOption::try_from(&pb).unwrap();
2234 assert_eq!(opt, SetRegionOption::AutoFlushInterval(None));
2235
2236 let pb = PbOption {
2238 key: "auto_flush_interval".to_string(),
2239 value: "0s".to_string(),
2240 };
2241 assert!(SetRegionOption::try_from(&pb).is_err());
2242
2243 let pb = PbOption {
2245 key: "auto_flush_interval".to_string(),
2246 value: "not_a_duration".to_string(),
2247 };
2248 assert!(SetRegionOption::try_from(&pb).is_err());
2249 }
2250
2251 #[test]
2252 fn test_set_region_option_skip_wal_try_from() {
2253 let pb = PbOption {
2254 key: SKIP_WAL_KEY.to_string(),
2255 value: "true".to_string(),
2256 };
2257 assert_eq!(
2258 SetRegionOption::SkipWal(true),
2259 SetRegionOption::try_from(&pb).unwrap()
2260 );
2261
2262 let pb = PbOption {
2263 key: SKIP_WAL_KEY.to_string(),
2264 value: "false".to_string(),
2265 };
2266 assert_eq!(
2267 SetRegionOption::SkipWal(false),
2268 SetRegionOption::try_from(&pb).unwrap()
2269 );
2270
2271 for value in ["", "invalid"] {
2272 let pb = PbOption {
2273 key: SKIP_WAL_KEY.to_string(),
2274 value: value.to_string(),
2275 };
2276 assert!(SetRegionOption::try_from(&pb).is_err());
2277 }
2278
2279 assert!(UnsetRegionOption::try_from(SKIP_WAL_KEY).is_err());
2280 }
2281
2282 #[test]
2283 fn test_set_region_option_skip_wal_serde_compatibility() {
2284 let legacy = serde_json::from_str::<SetRegionOption>(r#""SkipWal""#).unwrap();
2285 assert_eq!(SetRegionOption::SkipWal(true), legacy);
2286
2287 check_set_region_option_skip_wal_serde_compatibility(false);
2288 check_set_region_option_skip_wal_serde_compatibility(true);
2289 }
2290
2291 fn check_set_region_option_skip_wal_serde_compatibility(skip_wal: bool) {
2292 let option = SetRegionOption::SkipWal(skip_wal);
2293 let serialized = serde_json::to_string(&option).unwrap();
2294 let expected = if skip_wal {
2295 r#""SkipWal""#
2296 } else {
2297 r#"{"SkipWal":false}"#
2298 };
2299 assert_eq!(expected, serialized);
2300 assert_eq!(
2301 option,
2302 serde_json::from_str::<SetRegionOption>(&serialized).unwrap()
2303 );
2304 if skip_wal {
2305 assert!(serde_json::from_str::<LegacySetRegionOption>(&serialized).is_ok());
2306 assert_eq!(
2307 option,
2308 serde_json::from_str::<SetRegionOption>(r#"{"SkipWal":true}"#).unwrap()
2309 );
2310 }
2311 }
2312
2313 #[test]
2314 fn test_set_region_option_max_row_group_row_count_try_from() {
2315 let pb = PbOption {
2316 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2317 value: "512".to_string(),
2318 };
2319 assert_eq!(
2320 SetRegionOption::MaxRowGroupRowCount(Some(512)),
2321 SetRegionOption::try_from(&pb).unwrap()
2322 );
2323
2324 let pb = PbOption {
2325 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2326 value: String::new(),
2327 };
2328 assert_eq!(
2329 SetRegionOption::MaxRowGroupRowCount(None),
2330 SetRegionOption::try_from(&pb).unwrap()
2331 );
2332
2333 for value in [
2334 "0".to_string(),
2335 (MAX_ROW_GROUP_ROW_COUNT_LIMIT + 1).to_string(),
2336 "invalid".to_string(),
2337 ] {
2338 let pb = PbOption {
2339 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2340 value,
2341 };
2342 assert!(SetRegionOption::try_from(&pb).is_err());
2343 }
2344
2345 assert_eq!(
2346 UnsetRegionOption::MaxRowGroupRowCount,
2347 UnsetRegionOption::try_from(MAX_ROW_GROUP_ROW_COUNT).unwrap()
2348 );
2349 }
2350
2351 #[test]
2352 fn test_set_region_option_preserve_row_sequence_try_from() {
2353 for (value, expected) in [("true", true), ("false", false)] {
2354 let pb = PbOption {
2355 key: PRESERVE_ROW_SEQUENCE.to_string(),
2356 value: value.to_string(),
2357 };
2358 assert_eq!(
2359 SetRegionOption::PreserveRowSequence(expected),
2360 SetRegionOption::try_from(&pb).unwrap()
2361 );
2362 }
2363
2364 for value in ["1", "invalid"] {
2365 let pb = PbOption {
2366 key: PRESERVE_ROW_SEQUENCE.to_string(),
2367 value: value.to_string(),
2368 };
2369 assert!(SetRegionOption::try_from(&pb).is_err());
2370 }
2371
2372 assert_eq!(
2373 UnsetRegionOption::PreserveRowSequence,
2374 UnsetRegionOption::try_from(PRESERVE_ROW_SEQUENCE).unwrap()
2375 );
2376 assert_eq!(
2377 SetRegionOption::PreserveRowSequence(false),
2378 (&UnsetRegionOption::PreserveRowSequence).into()
2379 );
2380 }
2381
2382 #[test]
2383 fn test_set_twcs_window_trigger_options_try_from() {
2384 for key in [
2385 "compaction.twcs.active_window.trigger_file_num",
2386 "compaction.twcs.active_window.l1_merge_trigger",
2387 "compaction.twcs.inactive_window.trigger_file_num",
2388 "compaction.twcs.inactive_window.l1_merge_trigger",
2389 ] {
2390 let option = PbOption {
2391 key: key.to_string(),
2392 value: "8".to_string(),
2393 };
2394 assert_eq!(
2395 SetRegionOption::Twsc(key.to_string(), "8".to_string()),
2396 SetRegionOption::try_from(&option).unwrap(),
2397 "{key}"
2398 );
2399 }
2400 }
2401
2402 #[test]
2403 fn test_unset_twcs_window_trigger_options_try_from() {
2404 for (key, expected) in [
2405 (
2406 "compaction.twcs.active_window.trigger_file_num",
2407 UnsetRegionOption::TwcsActiveWindowTriggerFileNum,
2408 ),
2409 (
2410 "compaction.twcs.active_window.l1_merge_trigger",
2411 UnsetRegionOption::TwcsActiveWindowL1MergeTrigger,
2412 ),
2413 (
2414 "compaction.twcs.inactive_window.trigger_file_num",
2415 UnsetRegionOption::TwcsInactiveWindowTriggerFileNum,
2416 ),
2417 ] {
2418 assert_eq!(expected, UnsetRegionOption::try_from(key).unwrap());
2419 assert_eq!(
2420 SetRegionOption::Twsc(key.to_string(), String::new()),
2421 SetRegionOption::from(&expected)
2422 );
2423 }
2424
2425 let key = "compaction.twcs.inactive_window.l1_merge_trigger";
2426 let option = UnsetRegionOption::try_from(key).unwrap();
2427 assert_eq!(key, option.to_string());
2428 assert_eq!(
2429 SetRegionOption::Twsc(key.to_string(), String::new()),
2430 SetRegionOption::from(&option)
2431 );
2432 }
2433
2434 #[test]
2435 fn test_from_proto_alter_request() {
2436 RegionAlterRequest::try_from(AlterRequest {
2437 region_id: 0,
2438 schema_version: 1,
2439 kind: None,
2440 })
2441 .unwrap_err();
2442
2443 let request = RegionAlterRequest::try_from(AlterRequest {
2444 region_id: 0,
2445 schema_version: 1,
2446 kind: Some(alter_request::Kind::AddColumns(v1::region::AddColumns {
2447 add_columns: vec![v1::region::AddColumn {
2448 column_def: Some(RegionColumnDef {
2449 column_def: Some(ColumnDef {
2450 name: "a".to_string(),
2451 data_type: ColumnDataType::String as i32,
2452 is_nullable: true,
2453 default_constraint: vec![],
2454 semantic_type: SemanticType::Field as i32,
2455 comment: String::new(),
2456 ..Default::default()
2457 }),
2458 column_id: 1,
2459 }),
2460 location: Some(v1::AddColumnLocation {
2461 location_type: LocationType::First as i32,
2462 after_column_name: String::default(),
2463 }),
2464 }],
2465 })),
2466 })
2467 .unwrap();
2468
2469 assert_eq!(
2470 request,
2471 RegionAlterRequest {
2472 kind: AlterKind::AddColumns {
2473 columns: vec![AddColumn {
2474 column_metadata: ColumnMetadata {
2475 column_schema: ColumnSchema::new(
2476 "a",
2477 ConcreteDataType::string_datatype(),
2478 true,
2479 ),
2480 semantic_type: SemanticType::Field,
2481 column_id: 1,
2482 },
2483 location: Some(AddColumnLocation::First),
2484 }]
2485 },
2486 }
2487 );
2488
2489 let request = RegionAlterRequest::try_from(AlterRequest {
2490 region_id: 0,
2491 schema_version: 1,
2492 kind: Some(alter_request::Kind::SetJsonSettings(v1::SetJsonSettings {
2493 column_name: "payload".to_string(),
2494 settings: Some(v1::JsonSettings {
2495 type_hints: vec![v1::JsonTypeHint {
2496 path: vec!["service".to_string()],
2497 data_type: ColumnDataType::String as i32,
2498 datatype_extension: None,
2499 }],
2500 max_auto_expanded_paths: Some(10),
2501 }),
2502 })),
2503 })
2504 .unwrap();
2505
2506 let AlterKind::SetJsonSettings {
2507 column_name,
2508 settings,
2509 } = request.kind
2510 else {
2511 unreachable!()
2512 };
2513 assert_eq!("payload", column_name);
2514 assert_eq!(Some(10), settings.max_auto_expanded_paths());
2515 assert_eq!(1, settings.type_hints().len());
2516 }
2517
2518 #[test]
2519 fn test_write_buffer_size_region_options() {
2520 let option = PbOption {
2521 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2522 value: "128MiB".to_string(),
2523 };
2524 assert_eq!(
2525 SetRegionOption::WriteBufferSize(Some(ReadableSize::mb(128))),
2526 SetRegionOption::try_from(&option).unwrap()
2527 );
2528
2529 let option = PbOption {
2530 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2531 value: "invalid".to_string(),
2532 };
2533 SetRegionOption::try_from(&option).unwrap_err();
2534
2535 let option = PbOption {
2538 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2539 value: String::new(),
2540 };
2541 SetRegionOption::try_from(&option).unwrap_err();
2542
2543 assert_eq!(
2544 UnsetRegionOption::WriteBufferSize,
2545 UnsetRegionOption::try_from(WRITE_BUFFER_SIZE_KEY).unwrap()
2546 );
2547 assert_eq!(
2548 SetRegionOption::WriteBufferSize(None),
2549 SetRegionOption::from(&UnsetRegionOption::WriteBufferSize)
2550 );
2551 }
2552
2553 fn new_metadata() -> RegionMetadata {
2556 let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
2557 builder
2558 .push_column_metadata(ColumnMetadata {
2559 column_schema: ColumnSchema::new(
2560 "ts",
2561 ConcreteDataType::timestamp_millisecond_datatype(),
2562 false,
2563 ),
2564 semantic_type: SemanticType::Timestamp,
2565 column_id: 1,
2566 })
2567 .push_column_metadata(ColumnMetadata {
2568 column_schema: ColumnSchema::new(
2569 "tag_0",
2570 ConcreteDataType::string_datatype(),
2571 true,
2572 ),
2573 semantic_type: SemanticType::Tag,
2574 column_id: 2,
2575 })
2576 .push_column_metadata(ColumnMetadata {
2577 column_schema: ColumnSchema::new(
2578 "field_0",
2579 ConcreteDataType::string_datatype(),
2580 true,
2581 ),
2582 semantic_type: SemanticType::Field,
2583 column_id: 3,
2584 })
2585 .push_column_metadata(ColumnMetadata {
2586 column_schema: ColumnSchema::new(
2587 "field_1",
2588 ConcreteDataType::boolean_datatype(),
2589 true,
2590 ),
2591 semantic_type: SemanticType::Field,
2592 column_id: 4,
2593 })
2594 .primary_key(vec![2]);
2595 builder.build().unwrap()
2596 }
2597
2598 fn json2_column_schema(name: &str, settings: JsonSettings) -> ColumnSchema {
2599 let mut column_schema =
2600 ColumnSchema::new(name, ConcreteDataType::Json(JsonType::null()), true);
2601 column_schema.with_extension_type(&Json2ExtensionType::new(std::sync::Arc::new(
2602 JsonMetadata::new(settings),
2603 )));
2604 column_schema
2605 }
2606
2607 #[test]
2608 fn test_add_column_validate() {
2609 let metadata = new_metadata();
2610 let add_column = AddColumn {
2611 column_metadata: ColumnMetadata {
2612 column_schema: ColumnSchema::new(
2613 "tag_1",
2614 ConcreteDataType::string_datatype(),
2615 true,
2616 ),
2617 semantic_type: SemanticType::Tag,
2618 column_id: 5,
2619 },
2620 location: None,
2621 };
2622 add_column.validate(&metadata).unwrap();
2623 assert!(add_column.need_alter(&metadata));
2624
2625 AddColumn {
2627 column_metadata: ColumnMetadata {
2628 column_schema: ColumnSchema::new(
2629 "tag_1",
2630 ConcreteDataType::string_datatype(),
2631 false,
2632 ),
2633 semantic_type: SemanticType::Tag,
2634 column_id: 5,
2635 },
2636 location: None,
2637 }
2638 .validate(&metadata)
2639 .unwrap_err();
2640
2641 let add_column = AddColumn {
2643 column_metadata: ColumnMetadata {
2644 column_schema: ColumnSchema::new(
2645 "tag_0",
2646 ConcreteDataType::string_datatype(),
2647 true,
2648 ),
2649 semantic_type: SemanticType::Tag,
2650 column_id: 2,
2651 },
2652 location: None,
2653 };
2654 add_column.validate(&metadata).unwrap();
2655 assert!(!add_column.need_alter(&metadata));
2656 }
2657
2658 #[test]
2659 fn test_add_duplicate_columns() {
2660 let kind = AlterKind::AddColumns {
2661 columns: vec![
2662 AddColumn {
2663 column_metadata: ColumnMetadata {
2664 column_schema: ColumnSchema::new(
2665 "tag_1",
2666 ConcreteDataType::string_datatype(),
2667 true,
2668 ),
2669 semantic_type: SemanticType::Tag,
2670 column_id: 5,
2671 },
2672 location: None,
2673 },
2674 AddColumn {
2675 column_metadata: ColumnMetadata {
2676 column_schema: ColumnSchema::new(
2677 "tag_1",
2678 ConcreteDataType::string_datatype(),
2679 true,
2680 ),
2681 semantic_type: SemanticType::Field,
2682 column_id: 6,
2683 },
2684 location: None,
2685 },
2686 ],
2687 };
2688 let metadata = new_metadata();
2689 kind.validate(&metadata).unwrap();
2690 assert!(kind.need_alter(&metadata));
2691 }
2692
2693 #[test]
2694 fn test_add_existing_column_different_metadata() {
2695 let metadata = new_metadata();
2696
2697 let kind = AlterKind::AddColumns {
2699 columns: vec![AddColumn {
2700 column_metadata: ColumnMetadata {
2701 column_schema: ColumnSchema::new(
2702 "tag_0",
2703 ConcreteDataType::string_datatype(),
2704 true,
2705 ),
2706 semantic_type: SemanticType::Tag,
2707 column_id: 4,
2708 },
2709 location: None,
2710 }],
2711 };
2712 kind.validate(&metadata).unwrap_err();
2713
2714 let kind = AlterKind::AddColumns {
2716 columns: vec![AddColumn {
2717 column_metadata: ColumnMetadata {
2718 column_schema: ColumnSchema::new(
2719 "tag_0",
2720 ConcreteDataType::int64_datatype(),
2721 true,
2722 ),
2723 semantic_type: SemanticType::Tag,
2724 column_id: 2,
2725 },
2726 location: None,
2727 }],
2728 };
2729 kind.validate(&metadata).unwrap_err();
2730
2731 let kind = AlterKind::AddColumns {
2733 columns: vec![AddColumn {
2734 column_metadata: ColumnMetadata {
2735 column_schema: ColumnSchema::new(
2736 "tag_1",
2737 ConcreteDataType::string_datatype(),
2738 true,
2739 ),
2740 semantic_type: SemanticType::Tag,
2741 column_id: 2,
2742 },
2743 location: None,
2744 }],
2745 };
2746 kind.validate(&metadata).unwrap_err();
2747 }
2748
2749 #[test]
2750 fn test_add_existing_column_with_location() {
2751 let metadata = new_metadata();
2752 let kind = AlterKind::AddColumns {
2753 columns: vec![AddColumn {
2754 column_metadata: ColumnMetadata {
2755 column_schema: ColumnSchema::new(
2756 "tag_0",
2757 ConcreteDataType::string_datatype(),
2758 true,
2759 ),
2760 semantic_type: SemanticType::Tag,
2761 column_id: 2,
2762 },
2763 location: Some(AddColumnLocation::First),
2764 }],
2765 };
2766 kind.validate(&metadata).unwrap_err();
2767 }
2768
2769 #[test]
2770 fn test_validate_drop_column() {
2771 let metadata = new_metadata();
2772 let kind = AlterKind::DropColumns {
2773 names: vec!["xxxx".to_string()],
2774 };
2775 kind.validate(&metadata).unwrap();
2776 assert!(!kind.need_alter(&metadata));
2777
2778 AlterKind::DropColumns {
2779 names: vec!["tag_0".to_string()],
2780 }
2781 .validate(&metadata)
2782 .unwrap_err();
2783
2784 let kind = AlterKind::DropColumns {
2785 names: vec!["field_0".to_string()],
2786 };
2787 kind.validate(&metadata).unwrap();
2788 assert!(kind.need_alter(&metadata));
2789 }
2790
2791 #[test]
2792 fn test_validate_modify_column_type() {
2793 let metadata = new_metadata();
2794 AlterKind::ModifyColumnTypes {
2795 columns: vec![ModifyColumnType {
2796 column_name: "xxxx".to_string(),
2797 target_type: ConcreteDataType::string_datatype(),
2798 }],
2799 }
2800 .validate(&metadata)
2801 .unwrap_err();
2802
2803 AlterKind::ModifyColumnTypes {
2804 columns: vec![ModifyColumnType {
2805 column_name: "field_1".to_string(),
2806 target_type: ConcreteDataType::date_datatype(),
2807 }],
2808 }
2809 .validate(&metadata)
2810 .unwrap_err();
2811
2812 AlterKind::ModifyColumnTypes {
2813 columns: vec![ModifyColumnType {
2814 column_name: "ts".to_string(),
2815 target_type: ConcreteDataType::date_datatype(),
2816 }],
2817 }
2818 .validate(&metadata)
2819 .unwrap_err();
2820
2821 let kind = AlterKind::ModifyColumnTypes {
2823 columns: vec![ModifyColumnType {
2824 column_name: "ts".to_string(),
2825 target_type: ConcreteDataType::timestamp_microsecond_datatype(),
2826 }],
2827 };
2828 kind.validate(&metadata).unwrap();
2829 assert!(kind.need_alter(&metadata));
2830
2831 let metadata_nano = {
2833 let mut metadata = new_metadata();
2834 for col in metadata.column_metadatas.iter_mut() {
2835 if col.column_schema.name == "ts" {
2836 col.column_schema.data_type = ConcreteDataType::timestamp_nanosecond_datatype();
2837 }
2838 }
2839 metadata
2840 };
2841 AlterKind::ModifyColumnTypes {
2842 columns: vec![ModifyColumnType {
2843 column_name: "ts".to_string(),
2844 target_type: ConcreteDataType::timestamp_millisecond_datatype(),
2845 }],
2846 }
2847 .validate(&metadata_nano)
2848 .unwrap_err();
2849
2850 let same_type = AlterKind::ModifyColumnTypes {
2854 columns: vec![ModifyColumnType {
2855 column_name: "ts".to_string(),
2856 target_type: ConcreteDataType::timestamp_millisecond_datatype(),
2857 }],
2858 };
2859 same_type.validate(&metadata).unwrap();
2860 assert!(!same_type.need_alter(&metadata));
2861
2862 AlterKind::ModifyColumnTypes {
2864 columns: vec![ModifyColumnType {
2865 column_name: "ts".to_string(),
2866 target_type: ConcreteDataType::string_datatype(),
2867 }],
2868 }
2869 .validate(&metadata)
2870 .unwrap_err();
2871
2872 AlterKind::ModifyColumnTypes {
2873 columns: vec![ModifyColumnType {
2874 column_name: "tag_0".to_string(),
2875 target_type: ConcreteDataType::date_datatype(),
2876 }],
2877 }
2878 .validate(&metadata)
2879 .unwrap_err();
2880
2881 let kind = AlterKind::ModifyColumnTypes {
2882 columns: vec![ModifyColumnType {
2883 column_name: "field_0".to_string(),
2884 target_type: ConcreteDataType::int32_datatype(),
2885 }],
2886 };
2887 kind.validate(&metadata).unwrap();
2888 assert!(kind.need_alter(&metadata));
2889 }
2890
2891 #[test]
2892 fn test_validate_set_json_settings() {
2893 let mut metadata = new_metadata();
2894 let current_schema = json2_column_schema("field_0", JsonSettings::new_v2());
2895 let target_settings = JsonSettings::try_new(vec![], Some(10)).unwrap();
2896 metadata
2897 .column_metadatas
2898 .iter_mut()
2899 .find(|column| column.column_schema.name == "field_0")
2900 .unwrap()
2901 .column_schema = current_schema.clone();
2902
2903 let kind = AlterKind::SetJsonSettings {
2904 column_name: "field_0".to_string(),
2905 settings: target_settings.clone(),
2906 };
2907 kind.validate(&metadata).unwrap();
2908 assert!(kind.need_alter(&metadata));
2909
2910 let no_op = AlterKind::SetJsonSettings {
2911 column_name: "field_0".to_string(),
2912 settings: JsonSettings::new_v2(),
2913 };
2914 no_op.validate(&metadata).unwrap();
2915 assert!(!no_op.need_alter(&metadata));
2916
2917 AlterKind::SetJsonSettings {
2918 column_name: "tag_0".to_string(),
2919 settings: target_settings,
2920 }
2921 .validate(&metadata)
2922 .unwrap_err();
2923 }
2924
2925 #[test]
2926 fn test_validate_add_columns() {
2927 let kind = AlterKind::AddColumns {
2928 columns: vec![
2929 AddColumn {
2930 column_metadata: ColumnMetadata {
2931 column_schema: ColumnSchema::new(
2932 "tag_1",
2933 ConcreteDataType::string_datatype(),
2934 true,
2935 ),
2936 semantic_type: SemanticType::Tag,
2937 column_id: 5,
2938 },
2939 location: None,
2940 },
2941 AddColumn {
2942 column_metadata: ColumnMetadata {
2943 column_schema: ColumnSchema::new(
2944 "field_2",
2945 ConcreteDataType::string_datatype(),
2946 true,
2947 ),
2948 semantic_type: SemanticType::Field,
2949 column_id: 6,
2950 },
2951 location: None,
2952 },
2953 ],
2954 };
2955 let request = RegionAlterRequest { kind };
2956 let mut metadata = new_metadata();
2957 metadata.schema_version = 1;
2958 request.validate(&metadata).unwrap();
2959 }
2960
2961 #[test]
2962 fn test_validate_create_region() {
2963 let column_metadatas = vec![
2964 ColumnMetadata {
2965 column_schema: ColumnSchema::new(
2966 "ts",
2967 ConcreteDataType::timestamp_millisecond_datatype(),
2968 false,
2969 ),
2970 semantic_type: SemanticType::Timestamp,
2971 column_id: 1,
2972 },
2973 ColumnMetadata {
2974 column_schema: ColumnSchema::new(
2975 "tag_0",
2976 ConcreteDataType::string_datatype(),
2977 true,
2978 ),
2979 semantic_type: SemanticType::Tag,
2980 column_id: 2,
2981 },
2982 ColumnMetadata {
2983 column_schema: ColumnSchema::new(
2984 "field_0",
2985 ConcreteDataType::string_datatype(),
2986 true,
2987 ),
2988 semantic_type: SemanticType::Field,
2989 column_id: 3,
2990 },
2991 ];
2992 let create = RegionCreateRequest {
2993 engine: "mito".to_string(),
2994 column_metadatas,
2995 primary_key: vec![3, 4],
2996 options: HashMap::new(),
2997 table_dir: "path".to_string(),
2998 path_type: PathType::Bare,
2999 partition_expr_json: Some("".to_string()),
3000 requirements: Default::default(),
3001 };
3002
3003 assert!(create.validate().is_err());
3004 }
3005
3006 #[test]
3007 fn test_parse_create_region_requirements_defaults_to_empty() {
3008 let create = CreateRequest {
3009 region_id: RegionId::new(42, 0).as_u64(),
3010 engine: "mito".to_string(),
3011 column_defs: vec![],
3012 primary_key: vec![],
3013 path: "test".to_string(),
3014 options: HashMap::new(),
3015 partition: None,
3016 requirements: None,
3017 };
3018
3019 let requests =
3020 RegionRequest::try_from_request_body(region_request::Body::Create(create)).unwrap();
3021 let RegionRequest::Create(request) = &requests[0].1 else {
3022 unreachable!()
3023 };
3024
3025 assert_eq!(request.requirements, RegionRequirements::empty());
3026 }
3027
3028 #[test]
3029 fn test_parse_create_region_requirements_from_proto() {
3030 let create = CreateRequest {
3031 region_id: RegionId::new(42, 0).as_u64(),
3032 engine: "mito".to_string(),
3033 column_defs: vec![],
3034 primary_key: vec![],
3035 path: "test".to_string(),
3036 options: HashMap::new(),
3037 partition: None,
3038 requirements: Some(api::v1::region::RegionRequirements {
3039 object_storage: true,
3040 }),
3041 };
3042
3043 let requests =
3044 RegionRequest::try_from_request_body(region_request::Body::Create(create)).unwrap();
3045 let RegionRequest::Create(request) = &requests[0].1 else {
3046 unreachable!()
3047 };
3048
3049 assert_eq!(request.requirements, RegionRequirements::object_storage());
3050 }
3051
3052 #[test]
3053 fn test_parse_region_cleanup_from_proto() {
3054 let clean_up = api::v1::region::CleanUpRequest {
3055 region_id: RegionId::new(42, 3).as_u64(),
3056 engine: "mito".to_string(),
3057 path: "test".to_string(),
3058 options: HashMap::from([("k".to_string(), "v".to_string())]),
3059 };
3060
3061 let requests =
3062 RegionRequest::try_from_request_body(region_request::Body::CleanUp(clean_up)).unwrap();
3063 let RegionRequest::CleanUp(request) = &requests[0].1 else {
3064 unreachable!()
3065 };
3066
3067 assert_eq!(requests[0].0, RegionId::new(42, 3));
3068 assert_eq!(request.engine, "mito");
3069 assert_eq!(request.table_dir, "data/test/42/");
3070 assert_eq!(request.path_type, PathType::Bare);
3071 assert_eq!(request.options.get("k"), Some(&"v".to_string()));
3072 }
3073
3074 #[test]
3075 fn test_validate_modify_column_fulltext_options() {
3076 let kind = AlterKind::SetIndexes {
3077 options: vec![SetIndexOption::Fulltext {
3078 column_name: "tag_0".to_string(),
3079 options: FulltextOptions::new_unchecked(
3080 true,
3081 FulltextAnalyzer::Chinese,
3082 false,
3083 FulltextBackend::Bloom,
3084 1000,
3085 0.01,
3086 ),
3087 }],
3088 };
3089 let request = RegionAlterRequest { kind };
3090 let mut metadata = new_metadata();
3091 metadata.schema_version = 1;
3092 request.validate(&metadata).unwrap();
3093
3094 let kind = AlterKind::UnsetIndexes {
3095 options: vec![UnsetIndexOption::Fulltext {
3096 column_name: "tag_0".to_string(),
3097 }],
3098 };
3099 let request = RegionAlterRequest { kind };
3100 let mut metadata = new_metadata();
3101 metadata.schema_version = 1;
3102 request.validate(&metadata).unwrap();
3103 }
3104
3105 #[test]
3106 fn test_validate_sync_columns() {
3107 let metadata = new_metadata();
3108 let kind = AlterKind::SyncColumns {
3109 column_metadatas: vec![
3110 ColumnMetadata {
3111 column_schema: ColumnSchema::new(
3112 "tag_1",
3113 ConcreteDataType::string_datatype(),
3114 true,
3115 ),
3116 semantic_type: SemanticType::Tag,
3117 column_id: 5,
3118 },
3119 ColumnMetadata {
3120 column_schema: ColumnSchema::new(
3121 "field_2",
3122 ConcreteDataType::string_datatype(),
3123 true,
3124 ),
3125 semantic_type: SemanticType::Field,
3126 column_id: 6,
3127 },
3128 ],
3129 };
3130 let err = kind.validate(&metadata).unwrap_err();
3131 assert!(err.to_string().contains("not a primary key"));
3132
3133 let mut column_metadatas_with_different_ts_column = metadata.column_metadatas.clone();
3135 let ts_column = column_metadatas_with_different_ts_column
3136 .iter_mut()
3137 .find(|c| c.semantic_type == SemanticType::Timestamp)
3138 .unwrap();
3139 ts_column.column_schema.name = "ts1".to_string();
3140
3141 let kind = AlterKind::SyncColumns {
3142 column_metadatas: column_metadatas_with_different_ts_column,
3143 };
3144 let err = kind.validate(&metadata).unwrap_err();
3145 assert!(
3146 err.to_string()
3147 .contains("timestamp column ts has different id")
3148 );
3149
3150 let mut column_metadatas_with_different_pk_column = metadata.column_metadatas.clone();
3152 let pk_column = column_metadatas_with_different_pk_column
3153 .iter_mut()
3154 .find(|c| c.column_schema.name == "tag_0")
3155 .unwrap();
3156 pk_column.column_id = 100;
3157 let kind = AlterKind::SyncColumns {
3158 column_metadatas: column_metadatas_with_different_pk_column,
3159 };
3160 let err = kind.validate(&metadata).unwrap_err();
3161 assert!(
3162 err.to_string()
3163 .contains("column with same name tag_0 has different id")
3164 );
3165
3166 let mut column_metadatas_with_new_field_column = metadata.column_metadatas.clone();
3168 column_metadatas_with_new_field_column.push(ColumnMetadata {
3169 column_schema: ColumnSchema::new("field_2", ConcreteDataType::string_datatype(), true),
3170 semantic_type: SemanticType::Field,
3171 column_id: 4,
3172 });
3173 let kind = AlterKind::SyncColumns {
3174 column_metadatas: column_metadatas_with_new_field_column,
3175 };
3176 kind.validate(&metadata).unwrap();
3177 }
3178
3179 #[test]
3180 fn test_cast_path_type_to_primitive() {
3181 assert_eq!(PathType::Bare as u8, 0);
3182 assert_eq!(PathType::Data as u8, 1);
3183 assert_eq!(PathType::Metadata as u8, 2);
3184 assert_eq!(
3185 PathType::try_from(PathType::Bare as u8).unwrap(),
3186 PathType::Bare
3187 );
3188 assert_eq!(
3189 PathType::try_from(PathType::Data as u8).unwrap(),
3190 PathType::Data
3191 );
3192 assert_eq!(
3193 PathType::try_from(PathType::Metadata as u8).unwrap(),
3194 PathType::Metadata
3195 );
3196 }
3197}