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