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 location_id: Option<u64>,
1934 pub checkpoint: Option<ReplayCheckpoint>,
1936}
1937
1938#[derive(Debug, Clone)]
1939pub struct RegionBulkInsertsRequest {
1940 pub skip_wal: bool,
1942 pub region_id: RegionId,
1943 pub payload: DfRecordBatch,
1944 pub raw_data: ArrowIpc,
1945 pub partition_expr_version: Option<u64>,
1946 pub aligned_schema_version: Option<u64>,
1947}
1948
1949impl RegionBulkInsertsRequest {
1950 pub fn estimated_size(&self) -> usize {
1951 self.payload.get_array_memory_size()
1952 }
1953}
1954
1955#[derive(Debug, Clone, PartialEq, Eq)]
1961pub enum StagingPartitionDirective {
1962 UpdatePartitionExpr(String),
1963 RejectAllWrites,
1964}
1965
1966impl StagingPartitionDirective {
1967 pub fn partition_expr(&self) -> Option<&str> {
1969 match self {
1970 Self::UpdatePartitionExpr(expr) => Some(expr),
1971 Self::RejectAllWrites => None,
1972 }
1973 }
1974}
1975
1976#[derive(Debug, Clone)]
1977pub struct EnterStagingRequest {
1978 pub partition_directive: StagingPartitionDirective,
1980}
1981
1982impl EnterStagingRequest {
1983 pub fn with_partition_expr(partition_expr: String) -> Self {
1985 Self {
1986 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(partition_expr),
1987 }
1988 }
1989}
1990
1991#[derive(Debug, Clone)]
2010pub struct ApplyStagingManifestRequest {
2011 pub partition_expr: String,
2013 pub central_region_id: RegionId,
2015 pub manifest_path: String,
2018}
2019
2020impl fmt::Display for RegionRequest {
2021 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2022 match self {
2023 RegionRequest::Put(_) => write!(f, "Put"),
2024 RegionRequest::Delete(_) => write!(f, "Delete"),
2025 RegionRequest::Create(_) => write!(f, "Create"),
2026 RegionRequest::Drop(_) => write!(f, "Drop"),
2027 RegionRequest::Open(_) => write!(f, "Open"),
2028 RegionRequest::CleanUp(_) => write!(f, "CleanUp"),
2029 RegionRequest::Close(_) => write!(f, "Close"),
2030 RegionRequest::Alter(_) => write!(f, "Alter"),
2031 RegionRequest::Flush(_) => write!(f, "Flush"),
2032 RegionRequest::Compact(_) => write!(f, "Compact"),
2033 RegionRequest::BuildIndex(_) => write!(f, "BuildIndex"),
2034 RegionRequest::Truncate(_) => write!(f, "Truncate"),
2035 RegionRequest::Catchup(_) => write!(f, "Catchup"),
2036 RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"),
2037 RegionRequest::EnterStaging(_) => write!(f, "EnterStaging"),
2038 RegionRequest::ApplyStagingManifest(_) => write!(f, "ApplyStagingManifest"),
2039 }
2040 }
2041}
2042
2043#[cfg(test)]
2044mod tests {
2045
2046 use api::v1::region::RegionColumnDef;
2047 use api::v1::{ColumnDataType, ColumnDef};
2048 use common_time::range::TimestampRange;
2049 use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
2050 use datatypes::json::JsonSettings;
2051 use datatypes::prelude::ConcreteDataType;
2052 use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
2053 use datatypes::types::JsonType;
2054
2055 use super::*;
2056 use crate::metadata::RegionMetadataBuilder;
2057
2058 #[test]
2059 fn test_build_index_options_round_trip() {
2060 use prost::Message;
2061
2062 for options in [
2063 None,
2064 Some(build_index_request::Options::SstIndex(Default::default())),
2065 Some(build_index_request::Options::SeriesIndex(Default::default())),
2066 ] {
2067 let request = BuildIndexRequest {
2068 region_id: 42,
2069 options,
2070 };
2071 let decoded = BuildIndexRequest::decode(request.encode_to_vec().as_slice()).unwrap();
2072 let requests = make_region_build_index(decoded).unwrap();
2073 assert_eq!(1, requests.len());
2074 assert_eq!(RegionId::from_u64(42), requests[0].0);
2075 let RegionRequest::BuildIndex(request) = &requests[0].1 else {
2076 panic!("expected build-index request");
2077 };
2078 assert_eq!(options, request.options);
2079 }
2080 let legacy = BuildIndexRequest::decode(&[0x08, 42][..]).unwrap();
2082 assert!(legacy.options.is_none());
2083 }
2084
2085 #[test]
2086 fn test_make_region_puts_preserves_skip_wal() {
2087 let region_id = RegionId::new(42, 3);
2088 let rows = Rows::default();
2089 let requests = make_region_puts(InsertRequests {
2090 requests: [false, true, false]
2091 .into_iter()
2092 .map(|skip_wal| api::v1::region::InsertRequest {
2093 region_id: region_id.as_u64(),
2094 rows: Some(rows.clone()),
2095 partition_expr_version: Some(api::v1::PartitionExprVersion { value: 7 }),
2096 skip_wal,
2097 })
2098 .collect(),
2099 })
2100 .unwrap();
2101
2102 assert_eq!(3, requests.len());
2103 for ((id, request), skip_wal) in requests.into_iter().zip([false, true, false]) {
2104 assert_eq!(region_id, id);
2105 let RegionRequest::Put(request) = request else {
2106 panic!("expected a put request");
2107 };
2108 assert_eq!(rows, request.rows);
2109 assert_eq!(skip_wal, request.skip_wal);
2110 assert_eq!(Some(7), request.partition_expr_version);
2111 assert!(request.hint.is_none());
2112 }
2113 }
2114
2115 #[test]
2116 fn test_make_region_compact_with_time_range() {
2117 let requests = make_region_compact(CompactRequest {
2118 region_id: 42,
2119 time_range: Some(api::v1::region::CompactionTimeRange {
2120 start: 1_000,
2121 end: 2_000,
2122 time_unit: api::v1::TimeUnit::Microsecond as i32,
2123 }),
2124 ..Default::default()
2125 })
2126 .unwrap();
2127
2128 let RegionRequest::Compact(request) = &requests[0].1 else {
2129 unreachable!();
2130 };
2131 assert_eq!(
2132 Some(
2133 TimestampRange::new(
2134 Timestamp::new_microsecond(1_000),
2135 Timestamp::new_microsecond(2_000),
2136 )
2137 .unwrap()
2138 ),
2139 request.time_range
2140 );
2141 }
2142
2143 #[test]
2144 fn test_make_region_truncate_unflushed() {
2145 let region_id = RegionId::new(42, 3);
2146 let requests =
2147 RegionRequest::try_from_request_body(region_request::Body::Truncate(TruncateRequest {
2148 region_id: region_id.as_u64(),
2149 kind: Some(truncate_request::Kind::Unflushed(
2150 api::v1::region::Unflushed {},
2151 )),
2152 }))
2153 .unwrap();
2154
2155 assert_eq!(region_id, requests[0].0);
2156 assert!(matches!(
2157 requests[0].1,
2158 RegionRequest::Truncate(RegionTruncateRequest::Unflushed)
2159 ));
2160 }
2161
2162 #[test]
2163 fn test_make_region_truncate_requires_kind() {
2164 let error =
2165 RegionRequest::try_from_request_body(region_request::Body::Truncate(TruncateRequest {
2166 region_id: RegionId::new(42, 3).as_u64(),
2167 kind: None,
2168 }))
2169 .unwrap_err();
2170
2171 assert!(
2172 error
2173 .to_string()
2174 .contains("missing kind in TruncateRequest")
2175 );
2176 }
2177
2178 #[test]
2179 fn test_from_proto_location() {
2180 let proto_location = v1::AddColumnLocation {
2181 location_type: LocationType::First as i32,
2182 after_column_name: String::default(),
2183 };
2184 let location = AddColumnLocation::try_from(proto_location).unwrap();
2185 assert_eq!(location, AddColumnLocation::First);
2186
2187 let proto_location = v1::AddColumnLocation {
2188 location_type: 10,
2189 after_column_name: String::default(),
2190 };
2191 AddColumnLocation::try_from(proto_location).unwrap_err();
2192
2193 let proto_location = v1::AddColumnLocation {
2194 location_type: LocationType::After as i32,
2195 after_column_name: "a".to_string(),
2196 };
2197 let location = AddColumnLocation::try_from(proto_location).unwrap();
2198 assert_eq!(
2199 location,
2200 AddColumnLocation::After {
2201 column_name: "a".to_string()
2202 }
2203 );
2204 }
2205
2206 #[test]
2207 fn test_from_none_proto_add_column() {
2208 AddColumn::try_from(v1::region::AddColumn {
2209 column_def: None,
2210 location: None,
2211 })
2212 .unwrap_err();
2213 }
2214
2215 #[test]
2216 fn test_set_region_option_auto_flush_interval_try_from() {
2217 use std::time::Duration;
2218
2219 let pb = PbOption {
2221 key: "auto_flush_interval".to_string(),
2222 value: "5m".to_string(),
2223 };
2224 let opt = SetRegionOption::try_from(&pb).unwrap();
2225 assert_eq!(
2226 opt,
2227 SetRegionOption::AutoFlushInterval(Some(Duration::from_secs(300)))
2228 );
2229
2230 let pb = PbOption {
2232 key: "auto_flush_interval".to_string(),
2233 value: String::new(),
2234 };
2235 let opt = SetRegionOption::try_from(&pb).unwrap();
2236 assert_eq!(opt, SetRegionOption::AutoFlushInterval(None));
2237
2238 let pb = PbOption {
2240 key: "auto_flush_interval".to_string(),
2241 value: "0s".to_string(),
2242 };
2243 assert!(SetRegionOption::try_from(&pb).is_err());
2244
2245 let pb = PbOption {
2247 key: "auto_flush_interval".to_string(),
2248 value: "not_a_duration".to_string(),
2249 };
2250 assert!(SetRegionOption::try_from(&pb).is_err());
2251 }
2252
2253 #[test]
2254 fn test_set_region_option_skip_wal_try_from() {
2255 let pb = PbOption {
2256 key: SKIP_WAL_KEY.to_string(),
2257 value: "true".to_string(),
2258 };
2259 assert_eq!(
2260 SetRegionOption::SkipWal(true),
2261 SetRegionOption::try_from(&pb).unwrap()
2262 );
2263
2264 let pb = PbOption {
2265 key: SKIP_WAL_KEY.to_string(),
2266 value: "false".to_string(),
2267 };
2268 assert_eq!(
2269 SetRegionOption::SkipWal(false),
2270 SetRegionOption::try_from(&pb).unwrap()
2271 );
2272
2273 for value in ["", "invalid"] {
2274 let pb = PbOption {
2275 key: SKIP_WAL_KEY.to_string(),
2276 value: value.to_string(),
2277 };
2278 assert!(SetRegionOption::try_from(&pb).is_err());
2279 }
2280
2281 assert!(UnsetRegionOption::try_from(SKIP_WAL_KEY).is_err());
2282 }
2283
2284 #[test]
2285 fn test_set_region_option_skip_wal_serde_compatibility() {
2286 let legacy = serde_json::from_str::<SetRegionOption>(r#""SkipWal""#).unwrap();
2287 assert_eq!(SetRegionOption::SkipWal(true), legacy);
2288
2289 check_set_region_option_skip_wal_serde_compatibility(false);
2290 check_set_region_option_skip_wal_serde_compatibility(true);
2291 }
2292
2293 fn check_set_region_option_skip_wal_serde_compatibility(skip_wal: bool) {
2294 let option = SetRegionOption::SkipWal(skip_wal);
2295 let serialized = serde_json::to_string(&option).unwrap();
2296 let expected = if skip_wal {
2297 r#""SkipWal""#
2298 } else {
2299 r#"{"SkipWal":false}"#
2300 };
2301 assert_eq!(expected, serialized);
2302 assert_eq!(
2303 option,
2304 serde_json::from_str::<SetRegionOption>(&serialized).unwrap()
2305 );
2306 if skip_wal {
2307 assert!(serde_json::from_str::<LegacySetRegionOption>(&serialized).is_ok());
2308 assert_eq!(
2309 option,
2310 serde_json::from_str::<SetRegionOption>(r#"{"SkipWal":true}"#).unwrap()
2311 );
2312 }
2313 }
2314
2315 #[test]
2316 fn test_set_region_option_max_row_group_row_count_try_from() {
2317 let pb = PbOption {
2318 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2319 value: "512".to_string(),
2320 };
2321 assert_eq!(
2322 SetRegionOption::MaxRowGroupRowCount(Some(512)),
2323 SetRegionOption::try_from(&pb).unwrap()
2324 );
2325
2326 let pb = PbOption {
2327 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2328 value: String::new(),
2329 };
2330 assert_eq!(
2331 SetRegionOption::MaxRowGroupRowCount(None),
2332 SetRegionOption::try_from(&pb).unwrap()
2333 );
2334
2335 for value in [
2336 "0".to_string(),
2337 (MAX_ROW_GROUP_ROW_COUNT_LIMIT + 1).to_string(),
2338 "invalid".to_string(),
2339 ] {
2340 let pb = PbOption {
2341 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2342 value,
2343 };
2344 assert!(SetRegionOption::try_from(&pb).is_err());
2345 }
2346
2347 assert_eq!(
2348 UnsetRegionOption::MaxRowGroupRowCount,
2349 UnsetRegionOption::try_from(MAX_ROW_GROUP_ROW_COUNT).unwrap()
2350 );
2351 }
2352
2353 #[test]
2354 fn test_set_region_option_preserve_row_sequence_try_from() {
2355 for (value, expected) in [("true", true), ("false", false)] {
2356 let pb = PbOption {
2357 key: PRESERVE_ROW_SEQUENCE.to_string(),
2358 value: value.to_string(),
2359 };
2360 assert_eq!(
2361 SetRegionOption::PreserveRowSequence(expected),
2362 SetRegionOption::try_from(&pb).unwrap()
2363 );
2364 }
2365
2366 for value in ["1", "invalid"] {
2367 let pb = PbOption {
2368 key: PRESERVE_ROW_SEQUENCE.to_string(),
2369 value: value.to_string(),
2370 };
2371 assert!(SetRegionOption::try_from(&pb).is_err());
2372 }
2373
2374 assert_eq!(
2375 UnsetRegionOption::PreserveRowSequence,
2376 UnsetRegionOption::try_from(PRESERVE_ROW_SEQUENCE).unwrap()
2377 );
2378 assert_eq!(
2379 SetRegionOption::PreserveRowSequence(false),
2380 (&UnsetRegionOption::PreserveRowSequence).into()
2381 );
2382 }
2383
2384 #[test]
2385 fn test_set_twcs_window_trigger_options_try_from() {
2386 for key in [
2387 "compaction.twcs.active_window.trigger_file_num",
2388 "compaction.twcs.active_window.l1_merge_trigger",
2389 "compaction.twcs.inactive_window.trigger_file_num",
2390 "compaction.twcs.inactive_window.l1_merge_trigger",
2391 ] {
2392 let option = PbOption {
2393 key: key.to_string(),
2394 value: "8".to_string(),
2395 };
2396 assert_eq!(
2397 SetRegionOption::Twsc(key.to_string(), "8".to_string()),
2398 SetRegionOption::try_from(&option).unwrap(),
2399 "{key}"
2400 );
2401 }
2402 }
2403
2404 #[test]
2405 fn test_unset_twcs_window_trigger_options_try_from() {
2406 for (key, expected) in [
2407 (
2408 "compaction.twcs.active_window.trigger_file_num",
2409 UnsetRegionOption::TwcsActiveWindowTriggerFileNum,
2410 ),
2411 (
2412 "compaction.twcs.active_window.l1_merge_trigger",
2413 UnsetRegionOption::TwcsActiveWindowL1MergeTrigger,
2414 ),
2415 (
2416 "compaction.twcs.inactive_window.trigger_file_num",
2417 UnsetRegionOption::TwcsInactiveWindowTriggerFileNum,
2418 ),
2419 ] {
2420 assert_eq!(expected, UnsetRegionOption::try_from(key).unwrap());
2421 assert_eq!(
2422 SetRegionOption::Twsc(key.to_string(), String::new()),
2423 SetRegionOption::from(&expected)
2424 );
2425 }
2426
2427 let key = "compaction.twcs.inactive_window.l1_merge_trigger";
2428 let option = UnsetRegionOption::try_from(key).unwrap();
2429 assert_eq!(key, option.to_string());
2430 assert_eq!(
2431 SetRegionOption::Twsc(key.to_string(), String::new()),
2432 SetRegionOption::from(&option)
2433 );
2434 }
2435
2436 #[test]
2437 fn test_from_proto_alter_request() {
2438 RegionAlterRequest::try_from(AlterRequest {
2439 region_id: 0,
2440 schema_version: 1,
2441 kind: None,
2442 })
2443 .unwrap_err();
2444
2445 let request = RegionAlterRequest::try_from(AlterRequest {
2446 region_id: 0,
2447 schema_version: 1,
2448 kind: Some(alter_request::Kind::AddColumns(v1::region::AddColumns {
2449 add_columns: vec![v1::region::AddColumn {
2450 column_def: Some(RegionColumnDef {
2451 column_def: Some(ColumnDef {
2452 name: "a".to_string(),
2453 data_type: ColumnDataType::String as i32,
2454 is_nullable: true,
2455 default_constraint: vec![],
2456 semantic_type: SemanticType::Field as i32,
2457 comment: String::new(),
2458 ..Default::default()
2459 }),
2460 column_id: 1,
2461 }),
2462 location: Some(v1::AddColumnLocation {
2463 location_type: LocationType::First as i32,
2464 after_column_name: String::default(),
2465 }),
2466 }],
2467 })),
2468 })
2469 .unwrap();
2470
2471 assert_eq!(
2472 request,
2473 RegionAlterRequest {
2474 kind: AlterKind::AddColumns {
2475 columns: vec![AddColumn {
2476 column_metadata: ColumnMetadata {
2477 column_schema: ColumnSchema::new(
2478 "a",
2479 ConcreteDataType::string_datatype(),
2480 true,
2481 ),
2482 semantic_type: SemanticType::Field,
2483 column_id: 1,
2484 },
2485 location: Some(AddColumnLocation::First),
2486 }]
2487 },
2488 }
2489 );
2490
2491 let request = RegionAlterRequest::try_from(AlterRequest {
2492 region_id: 0,
2493 schema_version: 1,
2494 kind: Some(alter_request::Kind::SetJsonSettings(v1::SetJsonSettings {
2495 column_name: "payload".to_string(),
2496 settings: Some(v1::JsonSettings {
2497 type_hints: vec![v1::JsonTypeHint {
2498 path: vec!["service".to_string()],
2499 data_type: ColumnDataType::String as i32,
2500 datatype_extension: None,
2501 }],
2502 max_auto_expanded_paths: Some(10),
2503 }),
2504 })),
2505 })
2506 .unwrap();
2507
2508 let AlterKind::SetJsonSettings {
2509 column_name,
2510 settings,
2511 } = request.kind
2512 else {
2513 unreachable!()
2514 };
2515 assert_eq!("payload", column_name);
2516 assert_eq!(Some(10), settings.max_auto_expanded_paths());
2517 assert_eq!(1, settings.type_hints().len());
2518 }
2519
2520 #[test]
2521 fn test_write_buffer_size_region_options() {
2522 let option = PbOption {
2523 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2524 value: "128MiB".to_string(),
2525 };
2526 assert_eq!(
2527 SetRegionOption::WriteBufferSize(Some(ReadableSize::mb(128))),
2528 SetRegionOption::try_from(&option).unwrap()
2529 );
2530
2531 let option = PbOption {
2532 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2533 value: "invalid".to_string(),
2534 };
2535 SetRegionOption::try_from(&option).unwrap_err();
2536
2537 let option = PbOption {
2540 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2541 value: String::new(),
2542 };
2543 SetRegionOption::try_from(&option).unwrap_err();
2544
2545 assert_eq!(
2546 UnsetRegionOption::WriteBufferSize,
2547 UnsetRegionOption::try_from(WRITE_BUFFER_SIZE_KEY).unwrap()
2548 );
2549 assert_eq!(
2550 SetRegionOption::WriteBufferSize(None),
2551 SetRegionOption::from(&UnsetRegionOption::WriteBufferSize)
2552 );
2553 }
2554
2555 fn new_metadata() -> RegionMetadata {
2558 let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
2559 builder
2560 .push_column_metadata(ColumnMetadata {
2561 column_schema: ColumnSchema::new(
2562 "ts",
2563 ConcreteDataType::timestamp_millisecond_datatype(),
2564 false,
2565 ),
2566 semantic_type: SemanticType::Timestamp,
2567 column_id: 1,
2568 })
2569 .push_column_metadata(ColumnMetadata {
2570 column_schema: ColumnSchema::new(
2571 "tag_0",
2572 ConcreteDataType::string_datatype(),
2573 true,
2574 ),
2575 semantic_type: SemanticType::Tag,
2576 column_id: 2,
2577 })
2578 .push_column_metadata(ColumnMetadata {
2579 column_schema: ColumnSchema::new(
2580 "field_0",
2581 ConcreteDataType::string_datatype(),
2582 true,
2583 ),
2584 semantic_type: SemanticType::Field,
2585 column_id: 3,
2586 })
2587 .push_column_metadata(ColumnMetadata {
2588 column_schema: ColumnSchema::new(
2589 "field_1",
2590 ConcreteDataType::boolean_datatype(),
2591 true,
2592 ),
2593 semantic_type: SemanticType::Field,
2594 column_id: 4,
2595 })
2596 .primary_key(vec![2]);
2597 builder.build().unwrap()
2598 }
2599
2600 fn json2_column_schema(name: &str, settings: JsonSettings) -> ColumnSchema {
2601 let mut column_schema =
2602 ColumnSchema::new(name, ConcreteDataType::Json(JsonType::null()), true);
2603 column_schema.with_extension_type(&Json2ExtensionType::new(std::sync::Arc::new(
2604 JsonMetadata::new(settings),
2605 )));
2606 column_schema
2607 }
2608
2609 #[test]
2610 fn test_add_column_validate() {
2611 let metadata = new_metadata();
2612 let add_column = AddColumn {
2613 column_metadata: ColumnMetadata {
2614 column_schema: ColumnSchema::new(
2615 "tag_1",
2616 ConcreteDataType::string_datatype(),
2617 true,
2618 ),
2619 semantic_type: SemanticType::Tag,
2620 column_id: 5,
2621 },
2622 location: None,
2623 };
2624 add_column.validate(&metadata).unwrap();
2625 assert!(add_column.need_alter(&metadata));
2626
2627 AddColumn {
2629 column_metadata: ColumnMetadata {
2630 column_schema: ColumnSchema::new(
2631 "tag_1",
2632 ConcreteDataType::string_datatype(),
2633 false,
2634 ),
2635 semantic_type: SemanticType::Tag,
2636 column_id: 5,
2637 },
2638 location: None,
2639 }
2640 .validate(&metadata)
2641 .unwrap_err();
2642
2643 let add_column = AddColumn {
2645 column_metadata: ColumnMetadata {
2646 column_schema: ColumnSchema::new(
2647 "tag_0",
2648 ConcreteDataType::string_datatype(),
2649 true,
2650 ),
2651 semantic_type: SemanticType::Tag,
2652 column_id: 2,
2653 },
2654 location: None,
2655 };
2656 add_column.validate(&metadata).unwrap();
2657 assert!(!add_column.need_alter(&metadata));
2658 }
2659
2660 #[test]
2661 fn test_add_duplicate_columns() {
2662 let kind = AlterKind::AddColumns {
2663 columns: vec![
2664 AddColumn {
2665 column_metadata: ColumnMetadata {
2666 column_schema: ColumnSchema::new(
2667 "tag_1",
2668 ConcreteDataType::string_datatype(),
2669 true,
2670 ),
2671 semantic_type: SemanticType::Tag,
2672 column_id: 5,
2673 },
2674 location: None,
2675 },
2676 AddColumn {
2677 column_metadata: ColumnMetadata {
2678 column_schema: ColumnSchema::new(
2679 "tag_1",
2680 ConcreteDataType::string_datatype(),
2681 true,
2682 ),
2683 semantic_type: SemanticType::Field,
2684 column_id: 6,
2685 },
2686 location: None,
2687 },
2688 ],
2689 };
2690 let metadata = new_metadata();
2691 kind.validate(&metadata).unwrap();
2692 assert!(kind.need_alter(&metadata));
2693 }
2694
2695 #[test]
2696 fn test_add_existing_column_different_metadata() {
2697 let metadata = new_metadata();
2698
2699 let kind = AlterKind::AddColumns {
2701 columns: vec![AddColumn {
2702 column_metadata: ColumnMetadata {
2703 column_schema: ColumnSchema::new(
2704 "tag_0",
2705 ConcreteDataType::string_datatype(),
2706 true,
2707 ),
2708 semantic_type: SemanticType::Tag,
2709 column_id: 4,
2710 },
2711 location: None,
2712 }],
2713 };
2714 kind.validate(&metadata).unwrap_err();
2715
2716 let kind = AlterKind::AddColumns {
2718 columns: vec![AddColumn {
2719 column_metadata: ColumnMetadata {
2720 column_schema: ColumnSchema::new(
2721 "tag_0",
2722 ConcreteDataType::int64_datatype(),
2723 true,
2724 ),
2725 semantic_type: SemanticType::Tag,
2726 column_id: 2,
2727 },
2728 location: None,
2729 }],
2730 };
2731 kind.validate(&metadata).unwrap_err();
2732
2733 let kind = AlterKind::AddColumns {
2735 columns: vec![AddColumn {
2736 column_metadata: ColumnMetadata {
2737 column_schema: ColumnSchema::new(
2738 "tag_1",
2739 ConcreteDataType::string_datatype(),
2740 true,
2741 ),
2742 semantic_type: SemanticType::Tag,
2743 column_id: 2,
2744 },
2745 location: None,
2746 }],
2747 };
2748 kind.validate(&metadata).unwrap_err();
2749 }
2750
2751 #[test]
2752 fn test_add_existing_column_with_location() {
2753 let metadata = new_metadata();
2754 let kind = AlterKind::AddColumns {
2755 columns: vec![AddColumn {
2756 column_metadata: ColumnMetadata {
2757 column_schema: ColumnSchema::new(
2758 "tag_0",
2759 ConcreteDataType::string_datatype(),
2760 true,
2761 ),
2762 semantic_type: SemanticType::Tag,
2763 column_id: 2,
2764 },
2765 location: Some(AddColumnLocation::First),
2766 }],
2767 };
2768 kind.validate(&metadata).unwrap_err();
2769 }
2770
2771 #[test]
2772 fn test_validate_drop_column() {
2773 let metadata = new_metadata();
2774 let kind = AlterKind::DropColumns {
2775 names: vec!["xxxx".to_string()],
2776 };
2777 kind.validate(&metadata).unwrap();
2778 assert!(!kind.need_alter(&metadata));
2779
2780 AlterKind::DropColumns {
2781 names: vec!["tag_0".to_string()],
2782 }
2783 .validate(&metadata)
2784 .unwrap_err();
2785
2786 let kind = AlterKind::DropColumns {
2787 names: vec!["field_0".to_string()],
2788 };
2789 kind.validate(&metadata).unwrap();
2790 assert!(kind.need_alter(&metadata));
2791 }
2792
2793 #[test]
2794 fn test_validate_modify_column_type() {
2795 let metadata = new_metadata();
2796 AlterKind::ModifyColumnTypes {
2797 columns: vec![ModifyColumnType {
2798 column_name: "xxxx".to_string(),
2799 target_type: ConcreteDataType::string_datatype(),
2800 }],
2801 }
2802 .validate(&metadata)
2803 .unwrap_err();
2804
2805 AlterKind::ModifyColumnTypes {
2806 columns: vec![ModifyColumnType {
2807 column_name: "field_1".to_string(),
2808 target_type: ConcreteDataType::date_datatype(),
2809 }],
2810 }
2811 .validate(&metadata)
2812 .unwrap_err();
2813
2814 AlterKind::ModifyColumnTypes {
2815 columns: vec![ModifyColumnType {
2816 column_name: "ts".to_string(),
2817 target_type: ConcreteDataType::date_datatype(),
2818 }],
2819 }
2820 .validate(&metadata)
2821 .unwrap_err();
2822
2823 let kind = AlterKind::ModifyColumnTypes {
2825 columns: vec![ModifyColumnType {
2826 column_name: "ts".to_string(),
2827 target_type: ConcreteDataType::timestamp_microsecond_datatype(),
2828 }],
2829 };
2830 kind.validate(&metadata).unwrap();
2831 assert!(kind.need_alter(&metadata));
2832
2833 let metadata_nano = {
2835 let mut metadata = new_metadata();
2836 for col in metadata.column_metadatas.iter_mut() {
2837 if col.column_schema.name == "ts" {
2838 col.column_schema.data_type = ConcreteDataType::timestamp_nanosecond_datatype();
2839 }
2840 }
2841 metadata
2842 };
2843 AlterKind::ModifyColumnTypes {
2844 columns: vec![ModifyColumnType {
2845 column_name: "ts".to_string(),
2846 target_type: ConcreteDataType::timestamp_millisecond_datatype(),
2847 }],
2848 }
2849 .validate(&metadata_nano)
2850 .unwrap_err();
2851
2852 let same_type = AlterKind::ModifyColumnTypes {
2856 columns: vec![ModifyColumnType {
2857 column_name: "ts".to_string(),
2858 target_type: ConcreteDataType::timestamp_millisecond_datatype(),
2859 }],
2860 };
2861 same_type.validate(&metadata).unwrap();
2862 assert!(!same_type.need_alter(&metadata));
2863
2864 AlterKind::ModifyColumnTypes {
2866 columns: vec![ModifyColumnType {
2867 column_name: "ts".to_string(),
2868 target_type: ConcreteDataType::string_datatype(),
2869 }],
2870 }
2871 .validate(&metadata)
2872 .unwrap_err();
2873
2874 AlterKind::ModifyColumnTypes {
2875 columns: vec![ModifyColumnType {
2876 column_name: "tag_0".to_string(),
2877 target_type: ConcreteDataType::date_datatype(),
2878 }],
2879 }
2880 .validate(&metadata)
2881 .unwrap_err();
2882
2883 let kind = AlterKind::ModifyColumnTypes {
2884 columns: vec![ModifyColumnType {
2885 column_name: "field_0".to_string(),
2886 target_type: ConcreteDataType::int32_datatype(),
2887 }],
2888 };
2889 kind.validate(&metadata).unwrap();
2890 assert!(kind.need_alter(&metadata));
2891 }
2892
2893 #[test]
2894 fn test_validate_set_json_settings() {
2895 let mut metadata = new_metadata();
2896 let current_schema = json2_column_schema("field_0", JsonSettings::new_v2());
2897 let target_settings = JsonSettings::try_new(vec![], Some(10)).unwrap();
2898 metadata
2899 .column_metadatas
2900 .iter_mut()
2901 .find(|column| column.column_schema.name == "field_0")
2902 .unwrap()
2903 .column_schema = current_schema.clone();
2904
2905 let kind = AlterKind::SetJsonSettings {
2906 column_name: "field_0".to_string(),
2907 settings: target_settings.clone(),
2908 };
2909 kind.validate(&metadata).unwrap();
2910 assert!(kind.need_alter(&metadata));
2911
2912 let no_op = AlterKind::SetJsonSettings {
2913 column_name: "field_0".to_string(),
2914 settings: JsonSettings::new_v2(),
2915 };
2916 no_op.validate(&metadata).unwrap();
2917 assert!(!no_op.need_alter(&metadata));
2918
2919 AlterKind::SetJsonSettings {
2920 column_name: "tag_0".to_string(),
2921 settings: target_settings,
2922 }
2923 .validate(&metadata)
2924 .unwrap_err();
2925 }
2926
2927 #[test]
2928 fn test_validate_add_columns() {
2929 let kind = AlterKind::AddColumns {
2930 columns: vec![
2931 AddColumn {
2932 column_metadata: ColumnMetadata {
2933 column_schema: ColumnSchema::new(
2934 "tag_1",
2935 ConcreteDataType::string_datatype(),
2936 true,
2937 ),
2938 semantic_type: SemanticType::Tag,
2939 column_id: 5,
2940 },
2941 location: None,
2942 },
2943 AddColumn {
2944 column_metadata: ColumnMetadata {
2945 column_schema: ColumnSchema::new(
2946 "field_2",
2947 ConcreteDataType::string_datatype(),
2948 true,
2949 ),
2950 semantic_type: SemanticType::Field,
2951 column_id: 6,
2952 },
2953 location: None,
2954 },
2955 ],
2956 };
2957 let request = RegionAlterRequest { kind };
2958 let mut metadata = new_metadata();
2959 metadata.schema_version = 1;
2960 request.validate(&metadata).unwrap();
2961 }
2962
2963 #[test]
2964 fn test_validate_create_region() {
2965 let column_metadatas = vec![
2966 ColumnMetadata {
2967 column_schema: ColumnSchema::new(
2968 "ts",
2969 ConcreteDataType::timestamp_millisecond_datatype(),
2970 false,
2971 ),
2972 semantic_type: SemanticType::Timestamp,
2973 column_id: 1,
2974 },
2975 ColumnMetadata {
2976 column_schema: ColumnSchema::new(
2977 "tag_0",
2978 ConcreteDataType::string_datatype(),
2979 true,
2980 ),
2981 semantic_type: SemanticType::Tag,
2982 column_id: 2,
2983 },
2984 ColumnMetadata {
2985 column_schema: ColumnSchema::new(
2986 "field_0",
2987 ConcreteDataType::string_datatype(),
2988 true,
2989 ),
2990 semantic_type: SemanticType::Field,
2991 column_id: 3,
2992 },
2993 ];
2994 let create = RegionCreateRequest {
2995 engine: "mito".to_string(),
2996 column_metadatas,
2997 primary_key: vec![3, 4],
2998 options: HashMap::new(),
2999 table_dir: "path".to_string(),
3000 path_type: PathType::Bare,
3001 partition_expr_json: Some("".to_string()),
3002 requirements: Default::default(),
3003 };
3004
3005 assert!(create.validate().is_err());
3006 }
3007
3008 #[test]
3009 fn test_parse_create_region_requirements_defaults_to_empty() {
3010 let create = CreateRequest {
3011 region_id: RegionId::new(42, 0).as_u64(),
3012 engine: "mito".to_string(),
3013 column_defs: vec![],
3014 primary_key: vec![],
3015 path: "test".to_string(),
3016 options: HashMap::new(),
3017 partition: None,
3018 requirements: None,
3019 };
3020
3021 let requests =
3022 RegionRequest::try_from_request_body(region_request::Body::Create(create)).unwrap();
3023 let RegionRequest::Create(request) = &requests[0].1 else {
3024 unreachable!()
3025 };
3026
3027 assert_eq!(request.requirements, RegionRequirements::empty());
3028 }
3029
3030 #[test]
3031 fn test_parse_create_region_requirements_from_proto() {
3032 let create = CreateRequest {
3033 region_id: RegionId::new(42, 0).as_u64(),
3034 engine: "mito".to_string(),
3035 column_defs: vec![],
3036 primary_key: vec![],
3037 path: "test".to_string(),
3038 options: HashMap::new(),
3039 partition: None,
3040 requirements: Some(api::v1::region::RegionRequirements {
3041 object_storage: true,
3042 }),
3043 };
3044
3045 let requests =
3046 RegionRequest::try_from_request_body(region_request::Body::Create(create)).unwrap();
3047 let RegionRequest::Create(request) = &requests[0].1 else {
3048 unreachable!()
3049 };
3050
3051 assert_eq!(request.requirements, RegionRequirements::object_storage());
3052 }
3053
3054 #[test]
3055 fn test_parse_region_cleanup_from_proto() {
3056 let clean_up = api::v1::region::CleanUpRequest {
3057 region_id: RegionId::new(42, 3).as_u64(),
3058 engine: "mito".to_string(),
3059 path: "test".to_string(),
3060 options: HashMap::from([("k".to_string(), "v".to_string())]),
3061 };
3062
3063 let requests =
3064 RegionRequest::try_from_request_body(region_request::Body::CleanUp(clean_up)).unwrap();
3065 let RegionRequest::CleanUp(request) = &requests[0].1 else {
3066 unreachable!()
3067 };
3068
3069 assert_eq!(requests[0].0, RegionId::new(42, 3));
3070 assert_eq!(request.engine, "mito");
3071 assert_eq!(request.table_dir, "data/test/42/");
3072 assert_eq!(request.path_type, PathType::Bare);
3073 assert_eq!(request.options.get("k"), Some(&"v".to_string()));
3074 }
3075
3076 #[test]
3077 fn test_validate_modify_column_fulltext_options() {
3078 let kind = AlterKind::SetIndexes {
3079 options: vec![SetIndexOption::Fulltext {
3080 column_name: "tag_0".to_string(),
3081 options: FulltextOptions::new_unchecked(
3082 true,
3083 FulltextAnalyzer::Chinese,
3084 false,
3085 FulltextBackend::Bloom,
3086 1000,
3087 0.01,
3088 ),
3089 }],
3090 };
3091 let request = RegionAlterRequest { kind };
3092 let mut metadata = new_metadata();
3093 metadata.schema_version = 1;
3094 request.validate(&metadata).unwrap();
3095
3096 let kind = AlterKind::UnsetIndexes {
3097 options: vec![UnsetIndexOption::Fulltext {
3098 column_name: "tag_0".to_string(),
3099 }],
3100 };
3101 let request = RegionAlterRequest { kind };
3102 let mut metadata = new_metadata();
3103 metadata.schema_version = 1;
3104 request.validate(&metadata).unwrap();
3105 }
3106
3107 #[test]
3108 fn test_validate_sync_columns() {
3109 let metadata = new_metadata();
3110 let kind = AlterKind::SyncColumns {
3111 column_metadatas: vec![
3112 ColumnMetadata {
3113 column_schema: ColumnSchema::new(
3114 "tag_1",
3115 ConcreteDataType::string_datatype(),
3116 true,
3117 ),
3118 semantic_type: SemanticType::Tag,
3119 column_id: 5,
3120 },
3121 ColumnMetadata {
3122 column_schema: ColumnSchema::new(
3123 "field_2",
3124 ConcreteDataType::string_datatype(),
3125 true,
3126 ),
3127 semantic_type: SemanticType::Field,
3128 column_id: 6,
3129 },
3130 ],
3131 };
3132 let err = kind.validate(&metadata).unwrap_err();
3133 assert!(err.to_string().contains("not a primary key"));
3134
3135 let mut column_metadatas_with_different_ts_column = metadata.column_metadatas.clone();
3137 let ts_column = column_metadatas_with_different_ts_column
3138 .iter_mut()
3139 .find(|c| c.semantic_type == SemanticType::Timestamp)
3140 .unwrap();
3141 ts_column.column_schema.name = "ts1".to_string();
3142
3143 let kind = AlterKind::SyncColumns {
3144 column_metadatas: column_metadatas_with_different_ts_column,
3145 };
3146 let err = kind.validate(&metadata).unwrap_err();
3147 assert!(
3148 err.to_string()
3149 .contains("timestamp column ts has different id")
3150 );
3151
3152 let mut column_metadatas_with_different_pk_column = metadata.column_metadatas.clone();
3154 let pk_column = column_metadatas_with_different_pk_column
3155 .iter_mut()
3156 .find(|c| c.column_schema.name == "tag_0")
3157 .unwrap();
3158 pk_column.column_id = 100;
3159 let kind = AlterKind::SyncColumns {
3160 column_metadatas: column_metadatas_with_different_pk_column,
3161 };
3162 let err = kind.validate(&metadata).unwrap_err();
3163 assert!(
3164 err.to_string()
3165 .contains("column with same name tag_0 has different id")
3166 );
3167
3168 let mut column_metadatas_with_new_field_column = metadata.column_metadatas.clone();
3170 column_metadatas_with_new_field_column.push(ColumnMetadata {
3171 column_schema: ColumnSchema::new("field_2", ConcreteDataType::string_datatype(), true),
3172 semantic_type: SemanticType::Field,
3173 column_id: 4,
3174 });
3175 let kind = AlterKind::SyncColumns {
3176 column_metadatas: column_metadatas_with_new_field_column,
3177 };
3178 kind.validate(&metadata).unwrap();
3179 }
3180
3181 #[test]
3182 fn test_cast_path_type_to_primitive() {
3183 assert_eq!(PathType::Bare as u8, 0);
3184 assert_eq!(PathType::Data as u8, 1);
3185 assert_eq!(PathType::Metadata as u8, 2);
3186 assert_eq!(
3187 PathType::try_from(PathType::Bare as u8).unwrap(),
3188 PathType::Bare
3189 );
3190 assert_eq!(
3191 PathType::try_from(PathType::Data as u8).unwrap(),
3192 PathType::Data
3193 );
3194 assert_eq!(
3195 PathType::try_from(PathType::Metadata as u8).unwrap(),
3196 PathType::Metadata
3197 );
3198 }
3199}