1use std::collections::HashMap;
16use std::fmt::{self, Display};
17
18use api::helper::{ColumnDataTypeWrapper, from_pb_time_ranges};
19use api::v1::add_column_location::LocationType;
20use api::v1::column_def::{
21 as_fulltext_option_analyzer, as_fulltext_option_backend, as_skipping_index_type,
22};
23use api::v1::region::bulk_insert_request::Body;
24use api::v1::region::{
25 AlterRequest, AlterRequests, BuildIndexRequest, BulkInsertRequest, CloseRequest,
26 CompactRequest, CreateRequest, CreateRequests, DeleteRequests, DropRequest, DropRequests,
27 FlushRequest, InsertRequests, OpenRequest, TruncateRequest, alter_request, compact_request,
28 region_request, truncate_request,
29};
30use api::v1::{
31 self, Analyzer, ArrowIpc, FulltextBackend as PbFulltextBackend, Option as PbOption, Rows,
32 SemanticType, SkippingIndexType as PbSkippingIndexType, WriteHint,
33};
34pub use common_base::AffectedRows;
35use common_grpc::flight::FlightDecoder;
36use common_recordbatch::DfRecordBatch;
37use common_time::{TimeToLive, Timestamp};
38use datatypes::prelude::ConcreteDataType;
39use datatypes::schema::{FulltextOptions, SkippingIndexOptions};
40use num_enum::TryFromPrimitive;
41use serde::{Deserialize, Serialize};
42use snafu::{OptionExt, ResultExt, ensure};
43use strum::{AsRefStr, IntoStaticStr};
44
45use crate::logstore::entry;
46use crate::metadata::{
47 ColumnMetadata, ConvertTimeRangesSnafu, DecodeProtoSnafu, FlightCodecSnafu,
48 InvalidIndexOptionSnafu, InvalidRawRegionRequestSnafu, InvalidRegionRequestSnafu,
49 InvalidSetRegionOptionRequestSnafu, InvalidUnsetRegionOptionRequestSnafu, MetadataError,
50 RegionMetadata, Result, UnexpectedSnafu,
51};
52use crate::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
53use crate::metrics;
54use crate::mito_engine_options::{
55 APPEND_MODE_KEY, SST_FORMAT_KEY, TTL_KEY, TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW,
56 TWCS_TRIGGER_FILE_NUM,
57};
58use crate::path_utils::table_dir;
59use crate::storage::{ColumnId, RegionId, ScanRequest};
60
61#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
63#[repr(u8)]
64pub enum PathType {
65 Bare,
69 Data,
73 Metadata,
77}
78
79#[derive(Debug, IntoStaticStr)]
80pub enum BatchRegionDdlRequest {
81 Create(Vec<(RegionId, RegionCreateRequest)>),
82 Drop(Vec<(RegionId, RegionDropRequest)>),
83 Alter(Vec<(RegionId, RegionAlterRequest)>),
84}
85
86impl BatchRegionDdlRequest {
87 pub fn try_from_request_body(body: region_request::Body) -> Result<Option<Self>> {
89 match body {
90 region_request::Body::Creates(creates) => {
91 let requests = creates
92 .requests
93 .into_iter()
94 .map(parse_region_create)
95 .collect::<Result<Vec<_>>>()?;
96 Ok(Some(Self::Create(requests)))
97 }
98 region_request::Body::Drops(drops) => {
99 let requests = drops
100 .requests
101 .into_iter()
102 .map(parse_region_drop)
103 .collect::<Result<Vec<_>>>()?;
104 Ok(Some(Self::Drop(requests)))
105 }
106 region_request::Body::Alters(alters) => {
107 let requests = alters
108 .requests
109 .into_iter()
110 .map(parse_region_alter)
111 .collect::<Result<Vec<_>>>()?;
112 Ok(Some(Self::Alter(requests)))
113 }
114 _ => Ok(None),
115 }
116 }
117
118 pub fn request_type(&self) -> &'static str {
119 self.into()
120 }
121
122 pub fn into_region_requests(self) -> Vec<(RegionId, RegionRequest)> {
123 match self {
124 Self::Create(requests) => requests
125 .into_iter()
126 .map(|(region_id, request)| (region_id, RegionRequest::Create(request)))
127 .collect(),
128 Self::Drop(requests) => requests
129 .into_iter()
130 .map(|(region_id, request)| (region_id, RegionRequest::Drop(request)))
131 .collect(),
132 Self::Alter(requests) => requests
133 .into_iter()
134 .map(|(region_id, request)| (region_id, RegionRequest::Alter(request)))
135 .collect(),
136 }
137 }
138}
139
140#[derive(Debug, IntoStaticStr)]
141pub enum RegionRequest {
142 Put(RegionPutRequest),
143 Delete(RegionDeleteRequest),
144 Create(RegionCreateRequest),
145 Drop(RegionDropRequest),
146 Open(RegionOpenRequest),
147 Close(RegionCloseRequest),
148 Alter(RegionAlterRequest),
149 Flush(RegionFlushRequest),
150 Compact(RegionCompactRequest),
151 BuildIndex(RegionBuildIndexRequest),
152 Truncate(RegionTruncateRequest),
153 Catchup(RegionCatchupRequest),
154 BulkInserts(RegionBulkInsertsRequest),
155 EnterStaging(EnterStagingRequest),
156 ApplyStagingManifest(ApplyStagingManifestRequest),
157}
158
159impl RegionRequest {
160 pub fn try_from_request_body(body: region_request::Body) -> Result<Vec<(RegionId, Self)>> {
163 match body {
164 region_request::Body::Inserts(inserts) => make_region_puts(inserts),
165 region_request::Body::Deletes(deletes) => make_region_deletes(deletes),
166 region_request::Body::Create(create) => make_region_create(create),
167 region_request::Body::Drop(drop) => make_region_drop(drop),
168 region_request::Body::Open(open) => make_region_open(open),
169 region_request::Body::Close(close) => make_region_close(close),
170 region_request::Body::Alter(alter) => make_region_alter(alter),
171 region_request::Body::Flush(flush) => make_region_flush(flush),
172 region_request::Body::Compact(compact) => make_region_compact(compact),
173 region_request::Body::BuildIndex(index) => make_region_build_index(index),
174 region_request::Body::Truncate(truncate) => make_region_truncate(truncate),
175 region_request::Body::Creates(creates) => make_region_creates(creates),
176 region_request::Body::Drops(drops) => make_region_drops(drops),
177 region_request::Body::Alters(alters) => make_region_alters(alters),
178 region_request::Body::BulkInsert(bulk) => make_region_bulk_inserts(bulk),
179 region_request::Body::Sync(_) => UnexpectedSnafu {
180 reason: "Sync request should be handled separately by RegionServer",
181 }
182 .fail(),
183 region_request::Body::ListMetadata(_) => UnexpectedSnafu {
184 reason: "ListMetadata request should be handled separately by RegionServer",
185 }
186 .fail(),
187 region_request::Body::ApplyStagingManifest(apply) => {
188 make_region_apply_staging_manifest(apply)
189 }
190 }
191 }
192
193 pub fn request_type(&self) -> &'static str {
195 self.into()
196 }
197}
198
199fn make_region_puts(inserts: InsertRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
200 let requests = inserts
201 .requests
202 .into_iter()
203 .filter_map(|r| {
204 let region_id = r.region_id.into();
205 r.rows.map(|rows| {
206 (
207 region_id,
208 RegionRequest::Put(RegionPutRequest {
209 rows,
210 hint: None,
211 partition_expr_version: r.partition_expr_version.map(|v| v.value),
212 }),
213 )
214 })
215 })
216 .collect();
217 Ok(requests)
218}
219
220fn make_region_deletes(deletes: DeleteRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
221 let requests = deletes
222 .requests
223 .into_iter()
224 .filter_map(|r| {
225 let region_id = r.region_id.into();
226 r.rows.map(|rows| {
227 (
228 region_id,
229 RegionRequest::Delete(RegionDeleteRequest {
230 rows,
231 hint: None,
232 partition_expr_version: r.partition_expr_version.map(|v| v.value),
233 }),
234 )
235 })
236 })
237 .collect();
238 Ok(requests)
239}
240
241fn parse_region_create(create: CreateRequest) -> Result<(RegionId, RegionCreateRequest)> {
242 let column_metadatas = create
243 .column_defs
244 .into_iter()
245 .map(ColumnMetadata::try_from_column_def)
246 .collect::<Result<Vec<_>>>()?;
247 let region_id = RegionId::from(create.region_id);
248 let table_dir = table_dir(&create.path, region_id.table_id());
249 let partition_expr_json = create.partition.as_ref().map(|p| p.expression.clone());
250 Ok((
251 region_id,
252 RegionCreateRequest {
253 engine: create.engine,
254 column_metadatas,
255 primary_key: create.primary_key,
256 options: create.options,
257 table_dir,
258 path_type: PathType::Bare,
259 partition_expr_json,
260 },
261 ))
262}
263
264fn make_region_create(create: CreateRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
265 let (region_id, request) = parse_region_create(create)?;
266 Ok(vec![(region_id, RegionRequest::Create(request))])
267}
268
269fn make_region_creates(creates: CreateRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
270 let mut requests = Vec::with_capacity(creates.requests.len());
271 for create in creates.requests {
272 requests.extend(make_region_create(create)?);
273 }
274 Ok(requests)
275}
276
277fn parse_region_drop(drop: DropRequest) -> Result<(RegionId, RegionDropRequest)> {
278 let region_id = drop.region_id.into();
279 Ok((
280 region_id,
281 RegionDropRequest {
282 fast_path: drop.fast_path,
283 force: drop.force,
284 partial_drop: drop.partial_drop,
285 },
286 ))
287}
288
289fn make_region_drop(drop: DropRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
290 let (region_id, request) = parse_region_drop(drop)?;
291 Ok(vec![(region_id, RegionRequest::Drop(request))])
292}
293
294fn make_region_drops(drops: DropRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
295 let mut requests = Vec::with_capacity(drops.requests.len());
296 for drop in drops.requests {
297 requests.extend(make_region_drop(drop)?);
298 }
299 Ok(requests)
300}
301
302fn make_region_open(open: OpenRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
303 let region_id = RegionId::from(open.region_id);
304 let table_dir = table_dir(&open.path, region_id.table_id());
305 Ok(vec![(
306 region_id,
307 RegionRequest::Open(RegionOpenRequest {
308 engine: open.engine,
309 table_dir,
310 path_type: PathType::Bare,
311 options: open.options,
312 skip_wal_replay: false,
313 checkpoint: None,
314 }),
315 )])
316}
317
318fn make_region_close(close: CloseRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
319 let region_id = close.region_id.into();
320 Ok(vec![(
321 region_id,
322 RegionRequest::Close(RegionCloseRequest {}),
323 )])
324}
325
326fn parse_region_alter(alter: AlterRequest) -> Result<(RegionId, RegionAlterRequest)> {
327 let region_id = alter.region_id.into();
328 let request = RegionAlterRequest::try_from(alter)?;
329 Ok((region_id, request))
330}
331
332fn make_region_alter(alter: AlterRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
333 let (region_id, request) = parse_region_alter(alter)?;
334 Ok(vec![(region_id, RegionRequest::Alter(request))])
335}
336
337fn make_region_alters(alters: AlterRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
338 let mut requests = Vec::with_capacity(alters.requests.len());
339 for alter in alters.requests {
340 requests.extend(make_region_alter(alter)?);
341 }
342 Ok(requests)
343}
344
345fn make_region_flush(flush: FlushRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
346 let region_id = flush.region_id.into();
347 Ok(vec![(
348 region_id,
349 RegionRequest::Flush(RegionFlushRequest::default()),
350 )])
351}
352
353fn make_region_compact(compact: CompactRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
354 let region_id = compact.region_id.into();
355 let options = compact
356 .options
357 .unwrap_or(compact_request::Options::Regular(Default::default()));
358 let parallelism = if compact.parallelism == 0 {
360 None
361 } else {
362 Some(compact.parallelism)
363 };
364 Ok(vec![(
365 region_id,
366 RegionRequest::Compact(RegionCompactRequest {
367 options,
368 parallelism,
369 }),
370 )])
371}
372
373fn make_region_build_index(index: BuildIndexRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
374 let region_id = index.region_id.into();
375 Ok(vec![(
376 region_id,
377 RegionRequest::BuildIndex(RegionBuildIndexRequest {}),
378 )])
379}
380
381fn make_region_truncate(truncate: TruncateRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
382 let region_id = truncate.region_id.into();
383 match truncate.kind {
384 None => InvalidRawRegionRequestSnafu {
385 err: "missing kind in TruncateRequest".to_string(),
386 }
387 .fail(),
388 Some(truncate_request::Kind::All(_)) => Ok(vec![(
389 region_id,
390 RegionRequest::Truncate(RegionTruncateRequest::All),
391 )]),
392 Some(truncate_request::Kind::TimeRanges(time_ranges)) => {
393 let time_ranges = from_pb_time_ranges(time_ranges).context(ConvertTimeRangesSnafu)?;
394
395 Ok(vec![(
396 region_id,
397 RegionRequest::Truncate(RegionTruncateRequest::ByTimeRanges { time_ranges }),
398 )])
399 }
400 }
401}
402
403fn make_region_bulk_inserts(request: BulkInsertRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
405 let region_id = request.region_id.into();
406 let partition_expr_version = request.partition_expr_version.map(|v| v.value);
407 let Some(Body::ArrowIpc(request)) = request.body else {
408 return Ok(vec![]);
409 };
410
411 let decoder_timer = metrics::CONVERT_REGION_BULK_REQUEST
412 .with_label_values(&["decode"])
413 .start_timer();
414 let mut decoder =
415 FlightDecoder::try_from_schema_bytes(&request.schema).context(FlightCodecSnafu)?;
416 let payload = decoder
417 .try_decode_record_batch(&request.data_header, &request.payload)
418 .context(FlightCodecSnafu)?;
419 decoder_timer.observe_duration();
420 Ok(vec![(
421 region_id,
422 RegionRequest::BulkInserts(RegionBulkInsertsRequest {
423 region_id,
424 payload,
425 raw_data: request,
426 partition_expr_version,
427 }),
428 )])
429}
430
431fn make_region_apply_staging_manifest(
432 api::v1::region::ApplyStagingManifestRequest {
433 region_id,
434 partition_expr,
435 central_region_id,
436 manifest_path,
437 }: api::v1::region::ApplyStagingManifestRequest,
438) -> Result<Vec<(RegionId, RegionRequest)>> {
439 let region_id = region_id.into();
440 Ok(vec![(
441 region_id,
442 RegionRequest::ApplyStagingManifest(ApplyStagingManifestRequest {
443 partition_expr,
444 central_region_id: central_region_id.into(),
445 manifest_path,
446 }),
447 )])
448}
449
450#[derive(Debug)]
452pub struct RegionPutRequest {
453 pub rows: Rows,
455 pub hint: Option<WriteHint>,
457 pub partition_expr_version: Option<u64>,
459}
460
461#[derive(Debug)]
462pub struct RegionReadRequest {
463 pub request: ScanRequest,
464}
465
466#[derive(Debug)]
468pub struct RegionDeleteRequest {
469 pub rows: Rows,
473 pub hint: Option<WriteHint>,
475 pub partition_expr_version: Option<u64>,
477}
478
479#[derive(Debug, Clone)]
480pub struct RegionCreateRequest {
481 pub engine: String,
483 pub column_metadatas: Vec<ColumnMetadata>,
485 pub primary_key: Vec<ColumnId>,
487 pub options: HashMap<String, String>,
489 pub table_dir: String,
491 pub path_type: PathType,
493 pub partition_expr_json: Option<String>,
496}
497
498impl RegionCreateRequest {
499 pub fn validate(&self) -> Result<()> {
501 ensure!(
503 self.column_metadatas
504 .iter()
505 .any(|x| x.semantic_type == SemanticType::Timestamp),
506 InvalidRegionRequestSnafu {
507 region_id: RegionId::new(0, 0),
508 err: "missing timestamp column in create region request".to_string(),
509 }
510 );
511
512 let mut column_id_to_indices = HashMap::with_capacity(self.column_metadatas.len());
514 for (i, c) in self.column_metadatas.iter().enumerate() {
515 if let Some(previous) = column_id_to_indices.insert(c.column_id, i) {
516 return InvalidRegionRequestSnafu {
517 region_id: RegionId::new(0, 0),
518 err: format!(
519 "duplicate column id {} (at position {} and {}) in create region request",
520 c.column_id, previous, i
521 ),
522 }
523 .fail();
524 }
525 }
526
527 for column_id in &self.primary_key {
529 ensure!(
530 column_id_to_indices.contains_key(column_id),
531 InvalidRegionRequestSnafu {
532 region_id: RegionId::new(0, 0),
533 err: format!(
534 "missing primary key column {} in create region request",
535 column_id
536 ),
537 }
538 );
539 }
540
541 Ok(())
542 }
543
544 pub fn is_physical_table(&self) -> bool {
546 self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
547 }
548}
549
550#[derive(Debug, Clone)]
551pub struct RegionDropRequest {
552 pub fast_path: bool,
555
556 pub force: bool,
559
560 pub partial_drop: bool,
563}
564
565#[derive(Debug, Clone)]
567pub struct RegionOpenRequest {
568 pub engine: String,
570 pub table_dir: String,
572 pub path_type: PathType,
574 pub options: HashMap<String, String>,
576 pub skip_wal_replay: bool,
578 pub checkpoint: Option<ReplayCheckpoint>,
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
583pub struct ReplayCheckpoint {
584 pub entry_id: u64,
585 pub metadata_entry_id: Option<u64>,
586}
587
588impl RegionOpenRequest {
589 pub fn is_physical_table(&self) -> bool {
591 self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
592 }
593}
594
595#[derive(Debug)]
597pub struct RegionCloseRequest {}
598
599#[derive(Debug, PartialEq, Eq, Clone)]
601pub struct RegionAlterRequest {
602 pub kind: AlterKind,
604}
605
606impl RegionAlterRequest {
607 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
609 self.kind.validate(metadata)?;
610
611 Ok(())
612 }
613
614 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
618 debug_assert!(self.validate(metadata).is_ok());
619 self.kind.need_alter(metadata)
620 }
621}
622
623impl TryFrom<AlterRequest> for RegionAlterRequest {
624 type Error = MetadataError;
625
626 fn try_from(value: AlterRequest) -> Result<Self> {
627 let kind = value.kind.context(InvalidRawRegionRequestSnafu {
628 err: "missing kind in AlterRequest",
629 })?;
630
631 let kind = AlterKind::try_from(kind)?;
632 Ok(RegionAlterRequest { kind })
633 }
634}
635
636#[derive(Debug, PartialEq, Eq, Clone, AsRefStr)]
638pub enum AlterKind {
639 AddColumns {
641 columns: Vec<AddColumn>,
643 },
644 DropColumns {
646 names: Vec<String>,
648 },
649 ModifyColumnTypes {
651 columns: Vec<ModifyColumnType>,
653 },
654 SetRegionOptions { options: Vec<SetRegionOption> },
656 UnsetRegionOptions { keys: Vec<UnsetRegionOption> },
658 SetIndexes { options: Vec<SetIndexOption> },
660 UnsetIndexes { options: Vec<UnsetIndexOption> },
662 DropDefaults {
664 names: Vec<String>,
666 },
667 SetDefaults {
669 columns: Vec<SetDefault>,
671 },
672 SyncColumns {
674 column_metadatas: Vec<ColumnMetadata>,
675 },
676}
677#[derive(Debug, PartialEq, Eq, Clone)]
678pub struct SetDefault {
679 pub name: String,
680 pub default_constraint: Vec<u8>,
681}
682
683#[derive(Debug, PartialEq, Eq, Clone)]
684pub enum SetIndexOption {
685 Fulltext {
686 column_name: String,
687 options: FulltextOptions,
688 },
689 Inverted {
690 column_name: String,
691 },
692 Skipping {
693 column_name: String,
694 options: SkippingIndexOptions,
695 },
696}
697
698impl SetIndexOption {
699 pub fn column_name(&self) -> &String {
701 match self {
702 SetIndexOption::Fulltext { column_name, .. } => column_name,
703 SetIndexOption::Inverted { column_name } => column_name,
704 SetIndexOption::Skipping { column_name, .. } => column_name,
705 }
706 }
707
708 pub fn is_fulltext(&self) -> bool {
710 match self {
711 SetIndexOption::Fulltext { .. } => true,
712 SetIndexOption::Inverted { .. } => false,
713 SetIndexOption::Skipping { .. } => false,
714 }
715 }
716}
717
718impl TryFrom<v1::SetIndex> for SetIndexOption {
719 type Error = MetadataError;
720
721 fn try_from(value: v1::SetIndex) -> Result<Self> {
722 let option = value.options.context(InvalidRawRegionRequestSnafu {
723 err: "missing options in SetIndex",
724 })?;
725
726 let opt = match option {
727 v1::set_index::Options::Fulltext(x) => SetIndexOption::Fulltext {
728 column_name: x.column_name.clone(),
729 options: FulltextOptions::new(
730 x.enable,
731 as_fulltext_option_analyzer(
732 Analyzer::try_from(x.analyzer).context(DecodeProtoSnafu)?,
733 ),
734 x.case_sensitive,
735 as_fulltext_option_backend(
736 PbFulltextBackend::try_from(x.backend).context(DecodeProtoSnafu)?,
737 ),
738 x.granularity as u32,
739 x.false_positive_rate,
740 )
741 .context(InvalidIndexOptionSnafu)?,
742 },
743 v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
744 column_name: i.column_name,
745 },
746 v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
747 column_name: s.column_name,
748 options: SkippingIndexOptions::new(
749 s.granularity as u32,
750 s.false_positive_rate,
751 as_skipping_index_type(
752 PbSkippingIndexType::try_from(s.skipping_index_type)
753 .context(DecodeProtoSnafu)?,
754 ),
755 )
756 .context(InvalidIndexOptionSnafu)?,
757 },
758 };
759
760 Ok(opt)
761 }
762}
763
764#[derive(Debug, PartialEq, Eq, Clone)]
765pub enum UnsetIndexOption {
766 Fulltext { column_name: String },
767 Inverted { column_name: String },
768 Skipping { column_name: String },
769}
770
771impl UnsetIndexOption {
772 pub fn column_name(&self) -> &String {
773 match self {
774 UnsetIndexOption::Fulltext { column_name } => column_name,
775 UnsetIndexOption::Inverted { column_name } => column_name,
776 UnsetIndexOption::Skipping { column_name } => column_name,
777 }
778 }
779
780 pub fn is_fulltext(&self) -> bool {
781 match self {
782 UnsetIndexOption::Fulltext { .. } => true,
783 UnsetIndexOption::Inverted { .. } => false,
784 UnsetIndexOption::Skipping { .. } => false,
785 }
786 }
787}
788
789impl TryFrom<v1::UnsetIndex> for UnsetIndexOption {
790 type Error = MetadataError;
791
792 fn try_from(value: v1::UnsetIndex) -> Result<Self> {
793 let option = value.options.context(InvalidRawRegionRequestSnafu {
794 err: "missing options in UnsetIndex",
795 })?;
796
797 let opt = match option {
798 v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
799 column_name: f.column_name,
800 },
801 v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
802 column_name: i.column_name,
803 },
804 v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
805 column_name: s.column_name,
806 },
807 };
808
809 Ok(opt)
810 }
811}
812
813impl AlterKind {
814 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
818 match self {
819 AlterKind::AddColumns { columns } => {
820 for col_to_add in columns {
821 col_to_add.validate(metadata)?;
822 }
823 }
824 AlterKind::DropColumns { names } => {
825 for name in names {
826 Self::validate_column_to_drop(name, metadata)?;
827 }
828 }
829 AlterKind::ModifyColumnTypes { columns } => {
830 for col_to_change in columns {
831 col_to_change.validate(metadata)?;
832 }
833 }
834 AlterKind::SetRegionOptions { .. } => {}
835 AlterKind::UnsetRegionOptions { .. } => {}
836 AlterKind::SetIndexes { options } => {
837 for option in options {
838 Self::validate_column_alter_index_option(
839 option.column_name(),
840 metadata,
841 option.is_fulltext(),
842 )?;
843 }
844 }
845 AlterKind::UnsetIndexes { options } => {
846 for option in options {
847 Self::validate_column_alter_index_option(
848 option.column_name(),
849 metadata,
850 option.is_fulltext(),
851 )?;
852 }
853 }
854 AlterKind::DropDefaults { names } => {
855 names
856 .iter()
857 .try_for_each(|name| Self::validate_column_existence(name, metadata))?;
858 }
859 AlterKind::SetDefaults { columns } => {
860 columns
861 .iter()
862 .try_for_each(|col| Self::validate_column_existence(&col.name, metadata))?;
863 }
864 AlterKind::SyncColumns { column_metadatas } => {
865 let new_primary_keys = column_metadatas
866 .iter()
867 .filter(|c| c.semantic_type == SemanticType::Tag)
868 .map(|c| (c.column_schema.name.as_str(), c.column_id))
869 .collect::<HashMap<_, _>>();
870
871 let old_primary_keys = metadata
872 .column_metadatas
873 .iter()
874 .filter(|c| c.semantic_type == SemanticType::Tag)
875 .map(|c| (c.column_schema.name.as_str(), c.column_id));
876
877 for (name, id) in old_primary_keys {
878 let primary_key =
879 new_primary_keys
880 .get(name)
881 .with_context(|| InvalidRegionRequestSnafu {
882 region_id: metadata.region_id,
883 err: format!("column {} is not a primary key", name),
884 })?;
885
886 ensure!(
887 *primary_key == id,
888 InvalidRegionRequestSnafu {
889 region_id: metadata.region_id,
890 err: format!(
891 "column with same name {} has different id, existing: {}, got: {}",
892 name, id, primary_key
893 ),
894 }
895 );
896 }
897
898 let new_ts_column = column_metadatas
899 .iter()
900 .find(|c| c.semantic_type == SemanticType::Timestamp)
901 .map(|c| (c.column_schema.name.as_str(), c.column_id))
902 .context(InvalidRegionRequestSnafu {
903 region_id: metadata.region_id,
904 err: "timestamp column not found",
905 })?;
906
907 let old_ts_column = metadata
909 .column_metadatas
910 .iter()
911 .find(|c| c.semantic_type == SemanticType::Timestamp)
912 .map(|c| (c.column_schema.name.as_str(), c.column_id))
913 .unwrap();
914
915 ensure!(
916 new_ts_column == old_ts_column,
917 InvalidRegionRequestSnafu {
918 region_id: metadata.region_id,
919 err: format!(
920 "timestamp column {} has different id, existing: {}, got: {}",
921 old_ts_column.0, old_ts_column.1, new_ts_column.1
922 ),
923 }
924 );
925 }
926 }
927 Ok(())
928 }
929
930 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
932 debug_assert!(self.validate(metadata).is_ok());
933 match self {
934 AlterKind::AddColumns { columns } => columns
935 .iter()
936 .any(|col_to_add| col_to_add.need_alter(metadata)),
937 AlterKind::DropColumns { names } => names
938 .iter()
939 .any(|name| metadata.column_by_name(name).is_some()),
940 AlterKind::ModifyColumnTypes { columns } => columns
941 .iter()
942 .any(|col_to_change| col_to_change.need_alter(metadata)),
943 AlterKind::SetRegionOptions { .. } => true,
944 AlterKind::UnsetRegionOptions { .. } => true,
945 AlterKind::SetIndexes { options, .. } => options
946 .iter()
947 .any(|option| metadata.column_by_name(option.column_name()).is_some()),
948 AlterKind::UnsetIndexes { options } => options
949 .iter()
950 .any(|option| metadata.column_by_name(option.column_name()).is_some()),
951 AlterKind::DropDefaults { names } => names
952 .iter()
953 .any(|name| metadata.column_by_name(name).is_some()),
954
955 AlterKind::SetDefaults { columns } => columns
956 .iter()
957 .any(|x| metadata.column_by_name(&x.name).is_some()),
958 AlterKind::SyncColumns { column_metadatas } => {
959 metadata.column_metadatas != *column_metadatas
960 }
961 }
962 }
963
964 fn validate_column_to_drop(name: &str, metadata: &RegionMetadata) -> Result<()> {
966 let Some(column) = metadata.column_by_name(name) else {
967 return Ok(());
968 };
969 ensure!(
970 column.semantic_type == SemanticType::Field,
971 InvalidRegionRequestSnafu {
972 region_id: metadata.region_id,
973 err: format!("column {} is not a field and could not be dropped", name),
974 }
975 );
976 Ok(())
977 }
978
979 fn validate_column_alter_index_option(
981 column_name: &String,
982 metadata: &RegionMetadata,
983 is_fulltext: bool,
984 ) -> Result<()> {
985 let column = metadata
986 .column_by_name(column_name)
987 .context(InvalidRegionRequestSnafu {
988 region_id: metadata.region_id,
989 err: format!("column {} not found", column_name),
990 })?;
991
992 if is_fulltext {
993 ensure!(
994 column.column_schema.data_type.is_string(),
995 InvalidRegionRequestSnafu {
996 region_id: metadata.region_id,
997 err: format!(
998 "cannot change alter index options for non-string column {}",
999 column_name
1000 ),
1001 }
1002 );
1003 }
1004
1005 Ok(())
1006 }
1007
1008 fn validate_column_existence(column_name: &String, metadata: &RegionMetadata) -> Result<()> {
1010 metadata
1011 .column_by_name(column_name)
1012 .context(InvalidRegionRequestSnafu {
1013 region_id: metadata.region_id,
1014 err: format!("column {} not found", column_name),
1015 })?;
1016
1017 Ok(())
1018 }
1019}
1020
1021impl TryFrom<alter_request::Kind> for AlterKind {
1022 type Error = MetadataError;
1023
1024 fn try_from(kind: alter_request::Kind) -> Result<Self> {
1025 let alter_kind = match kind {
1026 alter_request::Kind::AddColumns(x) => {
1027 let columns = x
1028 .add_columns
1029 .into_iter()
1030 .map(|x| x.try_into())
1031 .collect::<Result<Vec<_>>>()?;
1032 AlterKind::AddColumns { columns }
1033 }
1034 alter_request::Kind::ModifyColumnTypes(x) => {
1035 let columns = x
1036 .modify_column_types
1037 .into_iter()
1038 .map(|x| x.into())
1039 .collect::<Vec<_>>();
1040 AlterKind::ModifyColumnTypes { columns }
1041 }
1042 alter_request::Kind::DropColumns(x) => {
1043 let names = x.drop_columns.into_iter().map(|x| x.name).collect();
1044 AlterKind::DropColumns { names }
1045 }
1046 alter_request::Kind::SetTableOptions(options) => AlterKind::SetRegionOptions {
1047 options: options
1048 .table_options
1049 .iter()
1050 .map(TryFrom::try_from)
1051 .collect::<Result<Vec<_>>>()?,
1052 },
1053 alter_request::Kind::UnsetTableOptions(options) => AlterKind::UnsetRegionOptions {
1054 keys: options
1055 .keys
1056 .iter()
1057 .map(|key| UnsetRegionOption::try_from(key.as_str()))
1058 .collect::<Result<Vec<_>>>()?,
1059 },
1060 alter_request::Kind::SetIndex(o) => AlterKind::SetIndexes {
1061 options: vec![SetIndexOption::try_from(o)?],
1062 },
1063 alter_request::Kind::UnsetIndex(o) => AlterKind::UnsetIndexes {
1064 options: vec![UnsetIndexOption::try_from(o)?],
1065 },
1066 alter_request::Kind::SetIndexes(o) => AlterKind::SetIndexes {
1067 options: o
1068 .set_indexes
1069 .into_iter()
1070 .map(SetIndexOption::try_from)
1071 .collect::<Result<Vec<_>>>()?,
1072 },
1073 alter_request::Kind::UnsetIndexes(o) => AlterKind::UnsetIndexes {
1074 options: o
1075 .unset_indexes
1076 .into_iter()
1077 .map(UnsetIndexOption::try_from)
1078 .collect::<Result<Vec<_>>>()?,
1079 },
1080 alter_request::Kind::DropDefaults(x) => AlterKind::DropDefaults {
1081 names: x.drop_defaults.into_iter().map(|x| x.column_name).collect(),
1082 },
1083 alter_request::Kind::SetDefaults(x) => AlterKind::SetDefaults {
1084 columns: x
1085 .set_defaults
1086 .into_iter()
1087 .map(|x| {
1088 Ok(SetDefault {
1089 name: x.column_name,
1090 default_constraint: x.default_constraint.clone(),
1091 })
1092 })
1093 .collect::<Result<Vec<_>>>()?,
1094 },
1095 alter_request::Kind::SyncColumns(x) => AlterKind::SyncColumns {
1096 column_metadatas: x
1097 .column_defs
1098 .into_iter()
1099 .map(ColumnMetadata::try_from_column_def)
1100 .collect::<Result<Vec<_>>>()?,
1101 },
1102 };
1103
1104 Ok(alter_kind)
1105 }
1106}
1107
1108#[derive(Debug, PartialEq, Eq, Clone)]
1110pub struct AddColumn {
1111 pub column_metadata: ColumnMetadata,
1113 pub location: Option<AddColumnLocation>,
1116}
1117
1118impl AddColumn {
1119 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
1124 ensure!(
1125 self.column_metadata.column_schema.is_nullable()
1126 || self
1127 .column_metadata
1128 .column_schema
1129 .default_constraint()
1130 .is_some(),
1131 InvalidRegionRequestSnafu {
1132 region_id: metadata.region_id,
1133 err: format!(
1134 "no default value for column {}",
1135 self.column_metadata.column_schema.name
1136 ),
1137 }
1138 );
1139
1140 if let Some(existing_column) =
1141 metadata.column_by_name(&self.column_metadata.column_schema.name)
1142 {
1143 ensure!(
1145 *existing_column == self.column_metadata,
1146 InvalidRegionRequestSnafu {
1147 region_id: metadata.region_id,
1148 err: format!(
1149 "column {} already exists with different metadata, existing: {:?}, got: {:?}",
1150 self.column_metadata.column_schema.name,
1151 existing_column,
1152 self.column_metadata,
1153 ),
1154 }
1155 );
1156 ensure!(
1157 self.location.is_none(),
1158 InvalidRegionRequestSnafu {
1159 region_id: metadata.region_id,
1160 err: format!(
1161 "column {} already exists, but location is specified",
1162 self.column_metadata.column_schema.name
1163 ),
1164 }
1165 );
1166 }
1167
1168 if let Some(existing_column) = metadata.column_by_id(self.column_metadata.column_id) {
1169 ensure!(
1171 existing_column.column_schema.name == self.column_metadata.column_schema.name,
1172 InvalidRegionRequestSnafu {
1173 region_id: metadata.region_id,
1174 err: format!(
1175 "column id {} already exists with different name {}",
1176 self.column_metadata.column_id, existing_column.column_schema.name
1177 ),
1178 }
1179 );
1180 }
1181
1182 Ok(())
1183 }
1184
1185 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1187 debug_assert!(self.validate(metadata).is_ok());
1188 metadata
1189 .column_by_name(&self.column_metadata.column_schema.name)
1190 .is_none()
1191 }
1192}
1193
1194impl TryFrom<v1::region::AddColumn> for AddColumn {
1195 type Error = MetadataError;
1196
1197 fn try_from(add_column: v1::region::AddColumn) -> Result<Self> {
1198 let column_def = add_column
1199 .column_def
1200 .context(InvalidRawRegionRequestSnafu {
1201 err: "missing column_def in AddColumn",
1202 })?;
1203
1204 let column_metadata = ColumnMetadata::try_from_column_def(column_def)?;
1205 let location = add_column
1206 .location
1207 .map(AddColumnLocation::try_from)
1208 .transpose()?;
1209
1210 Ok(AddColumn {
1211 column_metadata,
1212 location,
1213 })
1214 }
1215}
1216
1217#[derive(Debug, PartialEq, Eq, Clone)]
1219pub enum AddColumnLocation {
1220 First,
1222 After {
1224 column_name: String,
1226 },
1227}
1228
1229impl TryFrom<v1::AddColumnLocation> for AddColumnLocation {
1230 type Error = MetadataError;
1231
1232 fn try_from(location: v1::AddColumnLocation) -> Result<Self> {
1233 let location_type = LocationType::try_from(location.location_type)
1234 .map_err(|e| InvalidRawRegionRequestSnafu { err: e.to_string() }.build())?;
1235 let add_column_location = match location_type {
1236 LocationType::First => AddColumnLocation::First,
1237 LocationType::After => AddColumnLocation::After {
1238 column_name: location.after_column_name,
1239 },
1240 };
1241
1242 Ok(add_column_location)
1243 }
1244}
1245
1246#[derive(Debug, PartialEq, Eq, Clone)]
1248pub struct ModifyColumnType {
1249 pub column_name: String,
1251 pub target_type: ConcreteDataType,
1253}
1254
1255impl ModifyColumnType {
1256 pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
1258 let column_meta = metadata
1259 .column_by_name(&self.column_name)
1260 .with_context(|| InvalidRegionRequestSnafu {
1261 region_id: metadata.region_id,
1262 err: format!("column {} not found", self.column_name),
1263 })?;
1264
1265 ensure!(
1266 matches!(column_meta.semantic_type, SemanticType::Field),
1267 InvalidRegionRequestSnafu {
1268 region_id: metadata.region_id,
1269 err: "'timestamp' or 'tag' column cannot change type".to_string()
1270 }
1271 );
1272 ensure!(
1273 column_meta
1274 .column_schema
1275 .data_type
1276 .can_arrow_type_cast_to(&self.target_type),
1277 InvalidRegionRequestSnafu {
1278 region_id: metadata.region_id,
1279 err: format!(
1280 "column '{}' cannot be cast automatically to type '{}'",
1281 self.column_name, self.target_type
1282 ),
1283 }
1284 );
1285
1286 Ok(())
1287 }
1288
1289 pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1291 debug_assert!(self.validate(metadata).is_ok());
1292 metadata.column_by_name(&self.column_name).is_some()
1293 }
1294}
1295
1296impl From<v1::ModifyColumnType> for ModifyColumnType {
1297 fn from(modify_column_type: v1::ModifyColumnType) -> Self {
1298 let target_type = ColumnDataTypeWrapper::new(
1299 modify_column_type.target_type(),
1300 modify_column_type.target_type_extension,
1301 )
1302 .into();
1303
1304 ModifyColumnType {
1305 column_name: modify_column_type.column_name,
1306 target_type,
1307 }
1308 }
1309}
1310
1311#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
1312pub enum SetRegionOption {
1313 Ttl(Option<TimeToLive>),
1314 Twsc(String, String),
1316 Format(String),
1318 AppendMode(bool),
1320}
1321
1322impl TryFrom<&PbOption> for SetRegionOption {
1323 type Error = MetadataError;
1324
1325 fn try_from(value: &PbOption) -> std::result::Result<Self, Self::Error> {
1326 let PbOption { key, value } = value;
1327 match key.as_str() {
1328 TTL_KEY => {
1329 let ttl = TimeToLive::from_humantime_or_str(value)
1330 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1331
1332 Ok(Self::Ttl(Some(ttl)))
1333 }
1334 TWCS_TRIGGER_FILE_NUM | TWCS_MAX_OUTPUT_FILE_SIZE | TWCS_TIME_WINDOW => {
1335 Ok(Self::Twsc(key.clone(), value.clone()))
1336 }
1337 SST_FORMAT_KEY => Ok(Self::Format(value.clone())),
1338 APPEND_MODE_KEY => {
1339 let append_mode = value
1340 .parse::<bool>()
1341 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1342 Ok(Self::AppendMode(append_mode))
1343 }
1344 _ => InvalidSetRegionOptionRequestSnafu { key, value }.fail(),
1345 }
1346 }
1347}
1348
1349impl From<&UnsetRegionOption> for SetRegionOption {
1350 fn from(unset_option: &UnsetRegionOption) -> Self {
1351 match unset_option {
1352 UnsetRegionOption::TwcsTriggerFileNum => {
1353 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1354 }
1355 UnsetRegionOption::TwcsMaxOutputFileSize => {
1356 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1357 }
1358 UnsetRegionOption::TwcsTimeWindow => {
1359 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1360 }
1361 UnsetRegionOption::Ttl => SetRegionOption::Ttl(Default::default()),
1362 }
1363 }
1364}
1365
1366impl TryFrom<&str> for UnsetRegionOption {
1367 type Error = MetadataError;
1368
1369 fn try_from(key: &str) -> Result<Self> {
1370 match key.to_ascii_lowercase().as_str() {
1371 TTL_KEY => Ok(Self::Ttl),
1372 TWCS_TRIGGER_FILE_NUM => Ok(Self::TwcsTriggerFileNum),
1373 TWCS_MAX_OUTPUT_FILE_SIZE => Ok(Self::TwcsMaxOutputFileSize),
1374 TWCS_TIME_WINDOW => Ok(Self::TwcsTimeWindow),
1375 _ => InvalidUnsetRegionOptionRequestSnafu { key }.fail(),
1376 }
1377 }
1378}
1379
1380#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
1381pub enum UnsetRegionOption {
1382 TwcsTriggerFileNum,
1383 TwcsMaxOutputFileSize,
1384 TwcsTimeWindow,
1385 Ttl,
1386}
1387
1388impl UnsetRegionOption {
1389 pub fn as_str(&self) -> &str {
1390 match self {
1391 Self::Ttl => TTL_KEY,
1392 Self::TwcsTriggerFileNum => TWCS_TRIGGER_FILE_NUM,
1393 Self::TwcsMaxOutputFileSize => TWCS_MAX_OUTPUT_FILE_SIZE,
1394 Self::TwcsTimeWindow => TWCS_TIME_WINDOW,
1395 }
1396 }
1397}
1398
1399impl Display for UnsetRegionOption {
1400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1401 write!(f, "{}", self.as_str())
1402 }
1403}
1404
1405#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1406pub enum RegionFlushReason {
1407 RegionMigration,
1409 Repartition,
1411 RemoteWalPrune,
1413}
1414
1415#[derive(Debug, Clone, Default)]
1416pub struct RegionFlushRequest {
1417 pub row_group_size: Option<usize>,
1418 pub reason: Option<RegionFlushReason>,
1419}
1420
1421#[derive(Debug)]
1422pub struct RegionCompactRequest {
1423 pub options: compact_request::Options,
1424 pub parallelism: Option<u32>,
1425}
1426
1427impl Default for RegionCompactRequest {
1428 fn default() -> Self {
1429 Self {
1430 options: compact_request::Options::Regular(Default::default()),
1432 parallelism: None,
1433 }
1434 }
1435}
1436
1437#[derive(Debug, Clone, Default)]
1438pub struct RegionBuildIndexRequest {}
1439
1440#[derive(Debug)]
1442pub enum RegionTruncateRequest {
1443 All,
1445 ByTimeRanges {
1446 time_ranges: Vec<(Timestamp, Timestamp)>,
1450 },
1451}
1452
1453#[derive(Debug, Clone, Copy, Default)]
1458pub struct RegionCatchupRequest {
1459 pub set_writable: bool,
1461 pub entry_id: Option<entry::Id>,
1464 pub metadata_entry_id: Option<entry::Id>,
1468 pub location_id: Option<u64>,
1470 pub checkpoint: Option<ReplayCheckpoint>,
1472}
1473
1474#[derive(Debug, Clone)]
1475pub struct RegionBulkInsertsRequest {
1476 pub region_id: RegionId,
1477 pub payload: DfRecordBatch,
1478 pub raw_data: ArrowIpc,
1479 pub partition_expr_version: Option<u64>,
1480}
1481
1482impl RegionBulkInsertsRequest {
1483 pub fn estimated_size(&self) -> usize {
1484 self.payload.get_array_memory_size()
1485 }
1486}
1487
1488#[derive(Debug, Clone, PartialEq, Eq)]
1494pub enum StagingPartitionDirective {
1495 UpdatePartitionExpr(String),
1496 RejectAllWrites,
1497}
1498
1499impl StagingPartitionDirective {
1500 pub fn partition_expr(&self) -> Option<&str> {
1502 match self {
1503 Self::UpdatePartitionExpr(expr) => Some(expr),
1504 Self::RejectAllWrites => None,
1505 }
1506 }
1507}
1508
1509#[derive(Debug, Clone)]
1510pub struct EnterStagingRequest {
1511 pub partition_directive: StagingPartitionDirective,
1513}
1514
1515impl EnterStagingRequest {
1516 pub fn with_partition_expr(partition_expr: String) -> Self {
1518 Self {
1519 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(partition_expr),
1520 }
1521 }
1522}
1523
1524#[derive(Debug, Clone)]
1543pub struct ApplyStagingManifestRequest {
1544 pub partition_expr: String,
1546 pub central_region_id: RegionId,
1548 pub manifest_path: String,
1551}
1552
1553impl fmt::Display for RegionRequest {
1554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1555 match self {
1556 RegionRequest::Put(_) => write!(f, "Put"),
1557 RegionRequest::Delete(_) => write!(f, "Delete"),
1558 RegionRequest::Create(_) => write!(f, "Create"),
1559 RegionRequest::Drop(_) => write!(f, "Drop"),
1560 RegionRequest::Open(_) => write!(f, "Open"),
1561 RegionRequest::Close(_) => write!(f, "Close"),
1562 RegionRequest::Alter(_) => write!(f, "Alter"),
1563 RegionRequest::Flush(_) => write!(f, "Flush"),
1564 RegionRequest::Compact(_) => write!(f, "Compact"),
1565 RegionRequest::BuildIndex(_) => write!(f, "BuildIndex"),
1566 RegionRequest::Truncate(_) => write!(f, "Truncate"),
1567 RegionRequest::Catchup(_) => write!(f, "Catchup"),
1568 RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"),
1569 RegionRequest::EnterStaging(_) => write!(f, "EnterStaging"),
1570 RegionRequest::ApplyStagingManifest(_) => write!(f, "ApplyStagingManifest"),
1571 }
1572 }
1573}
1574
1575#[cfg(test)]
1576mod tests {
1577
1578 use api::v1::region::RegionColumnDef;
1579 use api::v1::{ColumnDataType, ColumnDef};
1580 use datatypes::prelude::ConcreteDataType;
1581 use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
1582
1583 use super::*;
1584 use crate::metadata::RegionMetadataBuilder;
1585
1586 #[test]
1587 fn test_from_proto_location() {
1588 let proto_location = v1::AddColumnLocation {
1589 location_type: LocationType::First as i32,
1590 after_column_name: String::default(),
1591 };
1592 let location = AddColumnLocation::try_from(proto_location).unwrap();
1593 assert_eq!(location, AddColumnLocation::First);
1594
1595 let proto_location = v1::AddColumnLocation {
1596 location_type: 10,
1597 after_column_name: String::default(),
1598 };
1599 AddColumnLocation::try_from(proto_location).unwrap_err();
1600
1601 let proto_location = v1::AddColumnLocation {
1602 location_type: LocationType::After as i32,
1603 after_column_name: "a".to_string(),
1604 };
1605 let location = AddColumnLocation::try_from(proto_location).unwrap();
1606 assert_eq!(
1607 location,
1608 AddColumnLocation::After {
1609 column_name: "a".to_string()
1610 }
1611 );
1612 }
1613
1614 #[test]
1615 fn test_from_none_proto_add_column() {
1616 AddColumn::try_from(v1::region::AddColumn {
1617 column_def: None,
1618 location: None,
1619 })
1620 .unwrap_err();
1621 }
1622
1623 #[test]
1624 fn test_from_proto_alter_request() {
1625 RegionAlterRequest::try_from(AlterRequest {
1626 region_id: 0,
1627 schema_version: 1,
1628 kind: None,
1629 })
1630 .unwrap_err();
1631
1632 let request = RegionAlterRequest::try_from(AlterRequest {
1633 region_id: 0,
1634 schema_version: 1,
1635 kind: Some(alter_request::Kind::AddColumns(v1::region::AddColumns {
1636 add_columns: vec![v1::region::AddColumn {
1637 column_def: Some(RegionColumnDef {
1638 column_def: Some(ColumnDef {
1639 name: "a".to_string(),
1640 data_type: ColumnDataType::String as i32,
1641 is_nullable: true,
1642 default_constraint: vec![],
1643 semantic_type: SemanticType::Field as i32,
1644 comment: String::new(),
1645 ..Default::default()
1646 }),
1647 column_id: 1,
1648 }),
1649 location: Some(v1::AddColumnLocation {
1650 location_type: LocationType::First as i32,
1651 after_column_name: String::default(),
1652 }),
1653 }],
1654 })),
1655 })
1656 .unwrap();
1657
1658 assert_eq!(
1659 request,
1660 RegionAlterRequest {
1661 kind: AlterKind::AddColumns {
1662 columns: vec![AddColumn {
1663 column_metadata: ColumnMetadata {
1664 column_schema: ColumnSchema::new(
1665 "a",
1666 ConcreteDataType::string_datatype(),
1667 true,
1668 ),
1669 semantic_type: SemanticType::Field,
1670 column_id: 1,
1671 },
1672 location: Some(AddColumnLocation::First),
1673 }]
1674 },
1675 }
1676 );
1677 }
1678
1679 fn new_metadata() -> RegionMetadata {
1682 let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1683 builder
1684 .push_column_metadata(ColumnMetadata {
1685 column_schema: ColumnSchema::new(
1686 "ts",
1687 ConcreteDataType::timestamp_millisecond_datatype(),
1688 false,
1689 ),
1690 semantic_type: SemanticType::Timestamp,
1691 column_id: 1,
1692 })
1693 .push_column_metadata(ColumnMetadata {
1694 column_schema: ColumnSchema::new(
1695 "tag_0",
1696 ConcreteDataType::string_datatype(),
1697 true,
1698 ),
1699 semantic_type: SemanticType::Tag,
1700 column_id: 2,
1701 })
1702 .push_column_metadata(ColumnMetadata {
1703 column_schema: ColumnSchema::new(
1704 "field_0",
1705 ConcreteDataType::string_datatype(),
1706 true,
1707 ),
1708 semantic_type: SemanticType::Field,
1709 column_id: 3,
1710 })
1711 .push_column_metadata(ColumnMetadata {
1712 column_schema: ColumnSchema::new(
1713 "field_1",
1714 ConcreteDataType::boolean_datatype(),
1715 true,
1716 ),
1717 semantic_type: SemanticType::Field,
1718 column_id: 4,
1719 })
1720 .primary_key(vec![2]);
1721 builder.build().unwrap()
1722 }
1723
1724 #[test]
1725 fn test_add_column_validate() {
1726 let metadata = new_metadata();
1727 let add_column = AddColumn {
1728 column_metadata: ColumnMetadata {
1729 column_schema: ColumnSchema::new(
1730 "tag_1",
1731 ConcreteDataType::string_datatype(),
1732 true,
1733 ),
1734 semantic_type: SemanticType::Tag,
1735 column_id: 5,
1736 },
1737 location: None,
1738 };
1739 add_column.validate(&metadata).unwrap();
1740 assert!(add_column.need_alter(&metadata));
1741
1742 AddColumn {
1744 column_metadata: ColumnMetadata {
1745 column_schema: ColumnSchema::new(
1746 "tag_1",
1747 ConcreteDataType::string_datatype(),
1748 false,
1749 ),
1750 semantic_type: SemanticType::Tag,
1751 column_id: 5,
1752 },
1753 location: None,
1754 }
1755 .validate(&metadata)
1756 .unwrap_err();
1757
1758 let add_column = AddColumn {
1760 column_metadata: ColumnMetadata {
1761 column_schema: ColumnSchema::new(
1762 "tag_0",
1763 ConcreteDataType::string_datatype(),
1764 true,
1765 ),
1766 semantic_type: SemanticType::Tag,
1767 column_id: 2,
1768 },
1769 location: None,
1770 };
1771 add_column.validate(&metadata).unwrap();
1772 assert!(!add_column.need_alter(&metadata));
1773 }
1774
1775 #[test]
1776 fn test_add_duplicate_columns() {
1777 let kind = AlterKind::AddColumns {
1778 columns: vec![
1779 AddColumn {
1780 column_metadata: ColumnMetadata {
1781 column_schema: ColumnSchema::new(
1782 "tag_1",
1783 ConcreteDataType::string_datatype(),
1784 true,
1785 ),
1786 semantic_type: SemanticType::Tag,
1787 column_id: 5,
1788 },
1789 location: None,
1790 },
1791 AddColumn {
1792 column_metadata: ColumnMetadata {
1793 column_schema: ColumnSchema::new(
1794 "tag_1",
1795 ConcreteDataType::string_datatype(),
1796 true,
1797 ),
1798 semantic_type: SemanticType::Field,
1799 column_id: 6,
1800 },
1801 location: None,
1802 },
1803 ],
1804 };
1805 let metadata = new_metadata();
1806 kind.validate(&metadata).unwrap();
1807 assert!(kind.need_alter(&metadata));
1808 }
1809
1810 #[test]
1811 fn test_add_existing_column_different_metadata() {
1812 let metadata = new_metadata();
1813
1814 let kind = AlterKind::AddColumns {
1816 columns: vec![AddColumn {
1817 column_metadata: ColumnMetadata {
1818 column_schema: ColumnSchema::new(
1819 "tag_0",
1820 ConcreteDataType::string_datatype(),
1821 true,
1822 ),
1823 semantic_type: SemanticType::Tag,
1824 column_id: 4,
1825 },
1826 location: None,
1827 }],
1828 };
1829 kind.validate(&metadata).unwrap_err();
1830
1831 let kind = AlterKind::AddColumns {
1833 columns: vec![AddColumn {
1834 column_metadata: ColumnMetadata {
1835 column_schema: ColumnSchema::new(
1836 "tag_0",
1837 ConcreteDataType::int64_datatype(),
1838 true,
1839 ),
1840 semantic_type: SemanticType::Tag,
1841 column_id: 2,
1842 },
1843 location: None,
1844 }],
1845 };
1846 kind.validate(&metadata).unwrap_err();
1847
1848 let kind = AlterKind::AddColumns {
1850 columns: vec![AddColumn {
1851 column_metadata: ColumnMetadata {
1852 column_schema: ColumnSchema::new(
1853 "tag_1",
1854 ConcreteDataType::string_datatype(),
1855 true,
1856 ),
1857 semantic_type: SemanticType::Tag,
1858 column_id: 2,
1859 },
1860 location: None,
1861 }],
1862 };
1863 kind.validate(&metadata).unwrap_err();
1864 }
1865
1866 #[test]
1867 fn test_add_existing_column_with_location() {
1868 let metadata = new_metadata();
1869 let kind = AlterKind::AddColumns {
1870 columns: vec![AddColumn {
1871 column_metadata: ColumnMetadata {
1872 column_schema: ColumnSchema::new(
1873 "tag_0",
1874 ConcreteDataType::string_datatype(),
1875 true,
1876 ),
1877 semantic_type: SemanticType::Tag,
1878 column_id: 2,
1879 },
1880 location: Some(AddColumnLocation::First),
1881 }],
1882 };
1883 kind.validate(&metadata).unwrap_err();
1884 }
1885
1886 #[test]
1887 fn test_validate_drop_column() {
1888 let metadata = new_metadata();
1889 let kind = AlterKind::DropColumns {
1890 names: vec!["xxxx".to_string()],
1891 };
1892 kind.validate(&metadata).unwrap();
1893 assert!(!kind.need_alter(&metadata));
1894
1895 AlterKind::DropColumns {
1896 names: vec!["tag_0".to_string()],
1897 }
1898 .validate(&metadata)
1899 .unwrap_err();
1900
1901 let kind = AlterKind::DropColumns {
1902 names: vec!["field_0".to_string()],
1903 };
1904 kind.validate(&metadata).unwrap();
1905 assert!(kind.need_alter(&metadata));
1906 }
1907
1908 #[test]
1909 fn test_validate_modify_column_type() {
1910 let metadata = new_metadata();
1911 AlterKind::ModifyColumnTypes {
1912 columns: vec![ModifyColumnType {
1913 column_name: "xxxx".to_string(),
1914 target_type: ConcreteDataType::string_datatype(),
1915 }],
1916 }
1917 .validate(&metadata)
1918 .unwrap_err();
1919
1920 AlterKind::ModifyColumnTypes {
1921 columns: vec![ModifyColumnType {
1922 column_name: "field_1".to_string(),
1923 target_type: ConcreteDataType::date_datatype(),
1924 }],
1925 }
1926 .validate(&metadata)
1927 .unwrap_err();
1928
1929 AlterKind::ModifyColumnTypes {
1930 columns: vec![ModifyColumnType {
1931 column_name: "ts".to_string(),
1932 target_type: ConcreteDataType::date_datatype(),
1933 }],
1934 }
1935 .validate(&metadata)
1936 .unwrap_err();
1937
1938 AlterKind::ModifyColumnTypes {
1939 columns: vec![ModifyColumnType {
1940 column_name: "tag_0".to_string(),
1941 target_type: ConcreteDataType::date_datatype(),
1942 }],
1943 }
1944 .validate(&metadata)
1945 .unwrap_err();
1946
1947 let kind = AlterKind::ModifyColumnTypes {
1948 columns: vec![ModifyColumnType {
1949 column_name: "field_0".to_string(),
1950 target_type: ConcreteDataType::int32_datatype(),
1951 }],
1952 };
1953 kind.validate(&metadata).unwrap();
1954 assert!(kind.need_alter(&metadata));
1955 }
1956
1957 #[test]
1958 fn test_validate_add_columns() {
1959 let kind = AlterKind::AddColumns {
1960 columns: vec![
1961 AddColumn {
1962 column_metadata: ColumnMetadata {
1963 column_schema: ColumnSchema::new(
1964 "tag_1",
1965 ConcreteDataType::string_datatype(),
1966 true,
1967 ),
1968 semantic_type: SemanticType::Tag,
1969 column_id: 5,
1970 },
1971 location: None,
1972 },
1973 AddColumn {
1974 column_metadata: ColumnMetadata {
1975 column_schema: ColumnSchema::new(
1976 "field_2",
1977 ConcreteDataType::string_datatype(),
1978 true,
1979 ),
1980 semantic_type: SemanticType::Field,
1981 column_id: 6,
1982 },
1983 location: None,
1984 },
1985 ],
1986 };
1987 let request = RegionAlterRequest { kind };
1988 let mut metadata = new_metadata();
1989 metadata.schema_version = 1;
1990 request.validate(&metadata).unwrap();
1991 }
1992
1993 #[test]
1994 fn test_validate_create_region() {
1995 let column_metadatas = vec![
1996 ColumnMetadata {
1997 column_schema: ColumnSchema::new(
1998 "ts",
1999 ConcreteDataType::timestamp_millisecond_datatype(),
2000 false,
2001 ),
2002 semantic_type: SemanticType::Timestamp,
2003 column_id: 1,
2004 },
2005 ColumnMetadata {
2006 column_schema: ColumnSchema::new(
2007 "tag_0",
2008 ConcreteDataType::string_datatype(),
2009 true,
2010 ),
2011 semantic_type: SemanticType::Tag,
2012 column_id: 2,
2013 },
2014 ColumnMetadata {
2015 column_schema: ColumnSchema::new(
2016 "field_0",
2017 ConcreteDataType::string_datatype(),
2018 true,
2019 ),
2020 semantic_type: SemanticType::Field,
2021 column_id: 3,
2022 },
2023 ];
2024 let create = RegionCreateRequest {
2025 engine: "mito".to_string(),
2026 column_metadatas,
2027 primary_key: vec![3, 4],
2028 options: HashMap::new(),
2029 table_dir: "path".to_string(),
2030 path_type: PathType::Bare,
2031 partition_expr_json: Some("".to_string()),
2032 };
2033
2034 assert!(create.validate().is_err());
2035 }
2036
2037 #[test]
2038 fn test_validate_modify_column_fulltext_options() {
2039 let kind = AlterKind::SetIndexes {
2040 options: vec![SetIndexOption::Fulltext {
2041 column_name: "tag_0".to_string(),
2042 options: FulltextOptions::new_unchecked(
2043 true,
2044 FulltextAnalyzer::Chinese,
2045 false,
2046 FulltextBackend::Bloom,
2047 1000,
2048 0.01,
2049 ),
2050 }],
2051 };
2052 let request = RegionAlterRequest { kind };
2053 let mut metadata = new_metadata();
2054 metadata.schema_version = 1;
2055 request.validate(&metadata).unwrap();
2056
2057 let kind = AlterKind::UnsetIndexes {
2058 options: vec![UnsetIndexOption::Fulltext {
2059 column_name: "tag_0".to_string(),
2060 }],
2061 };
2062 let request = RegionAlterRequest { kind };
2063 let mut metadata = new_metadata();
2064 metadata.schema_version = 1;
2065 request.validate(&metadata).unwrap();
2066 }
2067
2068 #[test]
2069 fn test_validate_sync_columns() {
2070 let metadata = new_metadata();
2071 let kind = AlterKind::SyncColumns {
2072 column_metadatas: vec![
2073 ColumnMetadata {
2074 column_schema: ColumnSchema::new(
2075 "tag_1",
2076 ConcreteDataType::string_datatype(),
2077 true,
2078 ),
2079 semantic_type: SemanticType::Tag,
2080 column_id: 5,
2081 },
2082 ColumnMetadata {
2083 column_schema: ColumnSchema::new(
2084 "field_2",
2085 ConcreteDataType::string_datatype(),
2086 true,
2087 ),
2088 semantic_type: SemanticType::Field,
2089 column_id: 6,
2090 },
2091 ],
2092 };
2093 let err = kind.validate(&metadata).unwrap_err();
2094 assert!(err.to_string().contains("not a primary key"));
2095
2096 let mut column_metadatas_with_different_ts_column = metadata.column_metadatas.clone();
2098 let ts_column = column_metadatas_with_different_ts_column
2099 .iter_mut()
2100 .find(|c| c.semantic_type == SemanticType::Timestamp)
2101 .unwrap();
2102 ts_column.column_schema.name = "ts1".to_string();
2103
2104 let kind = AlterKind::SyncColumns {
2105 column_metadatas: column_metadatas_with_different_ts_column,
2106 };
2107 let err = kind.validate(&metadata).unwrap_err();
2108 assert!(
2109 err.to_string()
2110 .contains("timestamp column ts has different id")
2111 );
2112
2113 let mut column_metadatas_with_different_pk_column = metadata.column_metadatas.clone();
2115 let pk_column = column_metadatas_with_different_pk_column
2116 .iter_mut()
2117 .find(|c| c.column_schema.name == "tag_0")
2118 .unwrap();
2119 pk_column.column_id = 100;
2120 let kind = AlterKind::SyncColumns {
2121 column_metadatas: column_metadatas_with_different_pk_column,
2122 };
2123 let err = kind.validate(&metadata).unwrap_err();
2124 assert!(
2125 err.to_string()
2126 .contains("column with same name tag_0 has different id")
2127 );
2128
2129 let mut column_metadatas_with_new_field_column = metadata.column_metadatas.clone();
2131 column_metadatas_with_new_field_column.push(ColumnMetadata {
2132 column_schema: ColumnSchema::new("field_2", ConcreteDataType::string_datatype(), true),
2133 semantic_type: SemanticType::Field,
2134 column_id: 4,
2135 });
2136 let kind = AlterKind::SyncColumns {
2137 column_metadatas: column_metadatas_with_new_field_column,
2138 };
2139 kind.validate(&metadata).unwrap();
2140 }
2141
2142 #[test]
2143 fn test_cast_path_type_to_primitive() {
2144 assert_eq!(PathType::Bare as u8, 0);
2145 assert_eq!(PathType::Data as u8, 1);
2146 assert_eq!(PathType::Metadata as u8, 2);
2147 assert_eq!(
2148 PathType::try_from(PathType::Bare as u8).unwrap(),
2149 PathType::Bare
2150 );
2151 assert_eq!(
2152 PathType::try_from(PathType::Data as u8).unwrap(),
2153 PathType::Data
2154 );
2155 assert_eq!(
2156 PathType::try_from(PathType::Metadata as u8).unwrap(),
2157 PathType::Metadata
2158 );
2159 }
2160}