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 skipping_index: hint
1575 .skipping_index
1576 .map(api::v1::column_def::json_skipping_options_from_proto)
1577 .transpose()
1578 .map_err(|err| {
1579 InvalidRawRegionRequestSnafu {
1580 err: err.to_string(),
1581 }
1582 .build()
1583 })?,
1584 })
1585 })
1586 .collect::<Result<Vec<_>>>()?;
1587
1588 JsonSettings::try_new(type_hints, settings.max_auto_expanded_paths).map_err(|err| {
1589 InvalidRawRegionRequestSnafu {
1590 err: err.to_string(),
1591 }
1592 .build()
1593 })
1594}
1595
1596#[derive(Debug, Eq, PartialEq, Clone)]
1601pub enum SetRegionOption {
1602 WriteBufferSize(Option<ReadableSize>),
1603 Ttl(Option<TimeToLive>),
1604 Twsc(String, String),
1606 Format(String),
1608 AppendMode(bool),
1610 AutoFlushInterval(Option<Duration>),
1612 MaxRowGroupRowCount(Option<usize>),
1614 PreserveRowSequence(bool),
1615 SkipWal(bool),
1617 CompactionOutputParallelism(Option<usize>),
1619}
1620
1621#[derive(Serialize, Deserialize)]
1622enum SetRegionOptionSerde {
1623 WriteBufferSize(Option<ReadableSize>),
1624 Ttl(Option<TimeToLive>),
1625 Twsc(String, String),
1626 Format(String),
1627 AppendMode(bool),
1628 AutoFlushInterval(Option<Duration>),
1629 MaxRowGroupRowCount(Option<usize>),
1630 PreserveRowSequence(bool),
1631 SkipWal(bool),
1632 CompactionOutputParallelism(Option<usize>),
1633}
1634
1635impl Serialize for SetRegionOption {
1636 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1637 where
1638 S: Serializer,
1639 {
1640 if matches!(self, Self::SkipWal(true)) {
1642 return serializer.serialize_unit_variant("SetRegionOption", 8, "SkipWal");
1643 }
1644
1645 let option = match self {
1646 Self::WriteBufferSize(value) => SetRegionOptionSerde::WriteBufferSize(*value),
1647 Self::Ttl(value) => SetRegionOptionSerde::Ttl(*value),
1648 Self::Twsc(key, value) => SetRegionOptionSerde::Twsc(key.clone(), value.clone()),
1649 Self::Format(value) => SetRegionOptionSerde::Format(value.clone()),
1650 Self::AppendMode(value) => SetRegionOptionSerde::AppendMode(*value),
1651 Self::AutoFlushInterval(value) => SetRegionOptionSerde::AutoFlushInterval(*value),
1652 Self::MaxRowGroupRowCount(value) => SetRegionOptionSerde::MaxRowGroupRowCount(*value),
1653 Self::PreserveRowSequence(value) => SetRegionOptionSerde::PreserveRowSequence(*value),
1654 Self::SkipWal(value) => SetRegionOptionSerde::SkipWal(*value),
1655 Self::CompactionOutputParallelism(value) => {
1656 SetRegionOptionSerde::CompactionOutputParallelism(*value)
1657 }
1658 };
1659 option.serialize(serializer)
1660 }
1661}
1662
1663#[derive(Deserialize)]
1664enum LegacySetRegionOption {
1665 SkipWal,
1666}
1667
1668#[derive(Deserialize)]
1669#[serde(untagged)]
1670enum BackwardCompatibleSetRegionOption {
1671 Current(SetRegionOptionSerde),
1672 Legacy(LegacySetRegionOption),
1673}
1674
1675impl From<SetRegionOptionSerde> for SetRegionOption {
1676 fn from(option: SetRegionOptionSerde) -> Self {
1677 match option {
1678 SetRegionOptionSerde::WriteBufferSize(value) => Self::WriteBufferSize(value),
1679 SetRegionOptionSerde::Ttl(value) => Self::Ttl(value),
1680 SetRegionOptionSerde::Twsc(key, value) => Self::Twsc(key, value),
1681 SetRegionOptionSerde::Format(value) => Self::Format(value),
1682 SetRegionOptionSerde::AppendMode(value) => Self::AppendMode(value),
1683 SetRegionOptionSerde::AutoFlushInterval(value) => Self::AutoFlushInterval(value),
1684 SetRegionOptionSerde::MaxRowGroupRowCount(value) => Self::MaxRowGroupRowCount(value),
1685 SetRegionOptionSerde::PreserveRowSequence(value) => Self::PreserveRowSequence(value),
1686 SetRegionOptionSerde::SkipWal(value) => Self::SkipWal(value),
1687 SetRegionOptionSerde::CompactionOutputParallelism(value) => {
1688 Self::CompactionOutputParallelism(value)
1689 }
1690 }
1691 }
1692}
1693
1694impl<'de> Deserialize<'de> for SetRegionOption {
1695 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1696 where
1697 D: Deserializer<'de>,
1698 {
1699 Ok(
1700 match BackwardCompatibleSetRegionOption::deserialize(deserializer)? {
1701 BackwardCompatibleSetRegionOption::Current(option) => option.into(),
1702 BackwardCompatibleSetRegionOption::Legacy(LegacySetRegionOption::SkipWal) => {
1703 Self::SkipWal(true)
1704 }
1705 },
1706 )
1707 }
1708}
1709
1710impl TryFrom<&PbOption> for SetRegionOption {
1711 type Error = MetadataError;
1712
1713 fn try_from(value: &PbOption) -> std::result::Result<Self, Self::Error> {
1714 let PbOption { key, value } = value;
1715 match key.as_str() {
1716 WRITE_BUFFER_SIZE_KEY => {
1717 let size = value
1718 .parse::<ReadableSize>()
1719 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1720 Ok(Self::WriteBufferSize(Some(size)))
1721 }
1722 TTL_KEY => {
1723 let ttl = TimeToLive::from_humantime_or_str(value)
1724 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1725
1726 Ok(Self::Ttl(Some(ttl)))
1727 }
1728 TWCS_TRIGGER_FILE_NUM
1729 | TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM
1730 | TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER
1731 | TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM
1732 | TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER
1733 | TWCS_MAX_OUTPUT_FILE_SIZE
1734 | TWCS_TIME_WINDOW => Ok(Self::Twsc(key.clone(), value.clone())),
1735 SST_FORMAT_KEY => Ok(Self::Format(value.clone())),
1736 APPEND_MODE_KEY => {
1737 let append_mode = value
1738 .parse::<bool>()
1739 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1740 Ok(Self::AppendMode(append_mode))
1741 }
1742 AUTO_FLUSH_INTERVAL_KEY => {
1743 if value.is_empty() {
1744 return Ok(Self::AutoFlushInterval(None));
1748 }
1749 let interval = humantime::parse_duration(value)
1750 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1751 if interval <= Duration::ZERO {
1752 return InvalidSetRegionOptionRequestSnafu { key, value }.fail();
1753 }
1754 Ok(Self::AutoFlushInterval(Some(interval)))
1755 }
1756 COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM => {
1757 if value.is_empty() {
1758 return Ok(Self::CompactionOutputParallelism(None));
1759 }
1760 let parallelism = value
1761 .parse::<usize>()
1762 .ok()
1763 .filter(|parallelism| *parallelism > 0)
1764 .ok_or_else(|| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1765 Ok(Self::CompactionOutputParallelism(Some(parallelism)))
1766 }
1767 MAX_ROW_GROUP_ROW_COUNT => {
1768 if value.is_empty() {
1769 return Ok(Self::MaxRowGroupRowCount(None));
1770 }
1771 let row_count = value
1772 .parse::<usize>()
1773 .ok()
1774 .filter(|row_count| {
1775 *row_count > 0 && *row_count <= MAX_ROW_GROUP_ROW_COUNT_LIMIT
1776 })
1777 .ok_or_else(|| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1778 Ok(Self::MaxRowGroupRowCount(Some(row_count)))
1779 }
1780 PRESERVE_ROW_SEQUENCE => {
1781 let preserve = value
1782 .parse::<bool>()
1783 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1784 Ok(Self::PreserveRowSequence(preserve))
1785 }
1786 SKIP_WAL_KEY => {
1787 let skip_wal = value
1788 .parse::<bool>()
1789 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1790 Ok(Self::SkipWal(skip_wal))
1791 }
1792 _ => InvalidSetRegionOptionRequestSnafu { key, value }.fail(),
1793 }
1794 }
1795}
1796
1797impl From<&UnsetRegionOption> for SetRegionOption {
1798 fn from(unset_option: &UnsetRegionOption) -> Self {
1799 match unset_option {
1800 UnsetRegionOption::TwcsTriggerFileNum => {
1801 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1802 }
1803 UnsetRegionOption::TwcsActiveWindowTriggerFileNum => {
1804 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1805 }
1806 UnsetRegionOption::TwcsActiveWindowL1MergeTrigger => {
1807 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1808 }
1809 UnsetRegionOption::TwcsInactiveWindowTriggerFileNum => {
1810 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1811 }
1812 UnsetRegionOption::TwcsInactiveWindowL1MergeTrigger => {
1813 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1814 }
1815 UnsetRegionOption::TwcsMaxOutputFileSize => {
1816 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1817 }
1818 UnsetRegionOption::TwcsTimeWindow => {
1819 SetRegionOption::Twsc(unset_option.to_string(), String::new())
1820 }
1821 UnsetRegionOption::Ttl => SetRegionOption::Ttl(Default::default()),
1822 UnsetRegionOption::MaxRowGroupRowCount => SetRegionOption::MaxRowGroupRowCount(None),
1823 UnsetRegionOption::WriteBufferSize => SetRegionOption::WriteBufferSize(None),
1824 UnsetRegionOption::PreserveRowSequence => SetRegionOption::PreserveRowSequence(false),
1825 UnsetRegionOption::CompactionOutputParallelism => {
1826 SetRegionOption::CompactionOutputParallelism(None)
1827 }
1828 }
1829 }
1830}
1831
1832impl TryFrom<&str> for UnsetRegionOption {
1833 type Error = MetadataError;
1834
1835 fn try_from(key: &str) -> Result<Self> {
1836 match key.to_ascii_lowercase().as_str() {
1837 TTL_KEY => Ok(Self::Ttl),
1838 WRITE_BUFFER_SIZE_KEY => Ok(Self::WriteBufferSize),
1839 TWCS_TRIGGER_FILE_NUM => Ok(Self::TwcsTriggerFileNum),
1840 TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM => Ok(Self::TwcsActiveWindowTriggerFileNum),
1841 TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER => Ok(Self::TwcsActiveWindowL1MergeTrigger),
1842 TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM => Ok(Self::TwcsInactiveWindowTriggerFileNum),
1843 TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER => Ok(Self::TwcsInactiveWindowL1MergeTrigger),
1844 TWCS_MAX_OUTPUT_FILE_SIZE => Ok(Self::TwcsMaxOutputFileSize),
1845 TWCS_TIME_WINDOW => Ok(Self::TwcsTimeWindow),
1846 MAX_ROW_GROUP_ROW_COUNT => Ok(Self::MaxRowGroupRowCount),
1847 PRESERVE_ROW_SEQUENCE => Ok(Self::PreserveRowSequence),
1848 COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM => Ok(Self::CompactionOutputParallelism),
1849 _ => InvalidUnsetRegionOptionRequestSnafu { key }.fail(),
1850 }
1851 }
1852}
1853
1854#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
1855pub enum UnsetRegionOption {
1856 TwcsTriggerFileNum,
1857 TwcsActiveWindowTriggerFileNum,
1858 TwcsInactiveWindowTriggerFileNum,
1859 TwcsInactiveWindowL1MergeTrigger,
1860 TwcsMaxOutputFileSize,
1861 TwcsTimeWindow,
1862 Ttl,
1863 MaxRowGroupRowCount,
1864 WriteBufferSize,
1865 PreserveRowSequence,
1866 TwcsActiveWindowL1MergeTrigger,
1867 CompactionOutputParallelism,
1868}
1869
1870impl UnsetRegionOption {
1871 pub fn as_str(&self) -> &str {
1872 match self {
1873 Self::Ttl => TTL_KEY,
1874 Self::WriteBufferSize => WRITE_BUFFER_SIZE_KEY,
1875 Self::TwcsTriggerFileNum => TWCS_TRIGGER_FILE_NUM,
1876 Self::TwcsActiveWindowTriggerFileNum => TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM,
1877 Self::TwcsActiveWindowL1MergeTrigger => TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER,
1878 Self::TwcsInactiveWindowTriggerFileNum => TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM,
1879 Self::TwcsInactiveWindowL1MergeTrigger => TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER,
1880 Self::TwcsMaxOutputFileSize => TWCS_MAX_OUTPUT_FILE_SIZE,
1881 Self::TwcsTimeWindow => TWCS_TIME_WINDOW,
1882 Self::MaxRowGroupRowCount => MAX_ROW_GROUP_ROW_COUNT,
1883 Self::PreserveRowSequence => PRESERVE_ROW_SEQUENCE,
1884 Self::CompactionOutputParallelism => COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM,
1885 }
1886 }
1887}
1888
1889impl Display for UnsetRegionOption {
1890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1891 write!(f, "{}", self.as_str())
1892 }
1893}
1894
1895#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1896pub enum RegionFlushReason {
1897 RegionMigration,
1899 Repartition,
1901 RemoteWalPrune,
1903 Closing,
1905 Downgrading,
1907}
1908
1909#[derive(Debug, Clone, Default)]
1910pub struct RegionFlushRequest {
1911 pub row_group_size: Option<usize>,
1912 pub reason: Option<RegionFlushReason>,
1913}
1914
1915#[derive(Debug)]
1916pub struct RegionCompactRequest {
1917 pub options: compact_request::Options,
1918 pub parallelism: Option<u32>,
1919 pub time_range: Option<TimestampRange>,
1920}
1921
1922impl Default for RegionCompactRequest {
1923 fn default() -> Self {
1924 Self {
1925 options: compact_request::Options::Regular(Default::default()),
1927 parallelism: None,
1928 time_range: None,
1929 }
1930 }
1931}
1932
1933#[derive(Debug, Clone, Default)]
1934pub struct RegionBuildIndexRequest {
1935 pub options: Option<build_index_request::Options>,
1937}
1938
1939#[derive(Debug)]
1941pub enum RegionTruncateRequest {
1942 All,
1944 Unflushed,
1954 ByTimeRanges {
1955 time_ranges: Vec<(Timestamp, Timestamp)>,
1959 },
1960}
1961
1962#[derive(Debug, Clone, Copy, Default)]
1967pub struct RegionCatchupRequest {
1968 pub set_writable: bool,
1970 pub entry_id: Option<entry::Id>,
1973 pub metadata_entry_id: Option<entry::Id>,
1977 pub checkpoint: Option<ReplayCheckpoint>,
1979}
1980
1981#[derive(Debug, Clone)]
1982pub struct RegionBulkInsertsRequest {
1983 pub skip_wal: bool,
1985 pub region_id: RegionId,
1986 pub payload: DfRecordBatch,
1987 pub raw_data: ArrowIpc,
1988 pub partition_expr_version: Option<u64>,
1989 pub aligned_schema_version: Option<u64>,
1990}
1991
1992impl RegionBulkInsertsRequest {
1993 pub fn estimated_size(&self) -> usize {
1994 self.payload.get_array_memory_size()
1995 }
1996}
1997
1998#[derive(Debug, Clone, PartialEq, Eq)]
2004pub enum StagingPartitionDirective {
2005 UpdatePartitionExpr(String),
2006 RejectAllWrites,
2007}
2008
2009impl StagingPartitionDirective {
2010 pub fn partition_expr(&self) -> Option<&str> {
2012 match self {
2013 Self::UpdatePartitionExpr(expr) => Some(expr),
2014 Self::RejectAllWrites => None,
2015 }
2016 }
2017}
2018
2019#[derive(Debug, Clone)]
2020pub struct EnterStagingRequest {
2021 pub partition_directive: StagingPartitionDirective,
2023}
2024
2025impl EnterStagingRequest {
2026 pub fn with_partition_expr(partition_expr: String) -> Self {
2028 Self {
2029 partition_directive: StagingPartitionDirective::UpdatePartitionExpr(partition_expr),
2030 }
2031 }
2032}
2033
2034#[derive(Debug, Clone)]
2053pub struct ApplyStagingManifestRequest {
2054 pub partition_expr: String,
2056 pub central_region_id: RegionId,
2058 pub manifest_path: String,
2061}
2062
2063impl fmt::Display for RegionRequest {
2064 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2065 match self {
2066 RegionRequest::Put(_) => write!(f, "Put"),
2067 RegionRequest::Delete(_) => write!(f, "Delete"),
2068 RegionRequest::Create(_) => write!(f, "Create"),
2069 RegionRequest::Drop(_) => write!(f, "Drop"),
2070 RegionRequest::Open(_) => write!(f, "Open"),
2071 RegionRequest::CleanUp(_) => write!(f, "CleanUp"),
2072 RegionRequest::Close(_) => write!(f, "Close"),
2073 RegionRequest::Alter(_) => write!(f, "Alter"),
2074 RegionRequest::Flush(_) => write!(f, "Flush"),
2075 RegionRequest::Compact(_) => write!(f, "Compact"),
2076 RegionRequest::BuildIndex(_) => write!(f, "BuildIndex"),
2077 RegionRequest::Truncate(_) => write!(f, "Truncate"),
2078 RegionRequest::Catchup(_) => write!(f, "Catchup"),
2079 RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"),
2080 RegionRequest::EnterStaging(_) => write!(f, "EnterStaging"),
2081 RegionRequest::ApplyStagingManifest(_) => write!(f, "ApplyStagingManifest"),
2082 }
2083 }
2084}
2085
2086#[cfg(test)]
2087mod tests {
2088
2089 use api::v1::region::RegionColumnDef;
2090 use api::v1::{ColumnDataType, ColumnDef};
2091 use common_time::range::TimestampRange;
2092 use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
2093 use datatypes::json::JsonSettings;
2094 use datatypes::prelude::ConcreteDataType;
2095 use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
2096 use datatypes::types::JsonType;
2097
2098 use super::*;
2099 use crate::metadata::RegionMetadataBuilder;
2100
2101 #[test]
2102 fn test_build_index_options_round_trip() {
2103 use prost::Message;
2104
2105 for options in [
2106 None,
2107 Some(build_index_request::Options::SstIndex(Default::default())),
2108 Some(build_index_request::Options::SeriesIndex(Default::default())),
2109 ] {
2110 let request = BuildIndexRequest {
2111 region_id: 42,
2112 options,
2113 };
2114 let decoded = BuildIndexRequest::decode(request.encode_to_vec().as_slice()).unwrap();
2115 let requests = make_region_build_index(decoded).unwrap();
2116 assert_eq!(1, requests.len());
2117 assert_eq!(RegionId::from_u64(42), requests[0].0);
2118 let RegionRequest::BuildIndex(request) = &requests[0].1 else {
2119 panic!("expected build-index request");
2120 };
2121 assert_eq!(options, request.options);
2122 }
2123 let legacy = BuildIndexRequest::decode(&[0x08, 42][..]).unwrap();
2125 assert!(legacy.options.is_none());
2126 }
2127
2128 #[test]
2129 fn test_make_region_puts_preserves_skip_wal() {
2130 let region_id = RegionId::new(42, 3);
2131 let rows = Rows::default();
2132 let requests = make_region_puts(InsertRequests {
2133 requests: [false, true, false]
2134 .into_iter()
2135 .map(|skip_wal| api::v1::region::InsertRequest {
2136 region_id: region_id.as_u64(),
2137 rows: Some(rows.clone()),
2138 partition_expr_version: Some(api::v1::PartitionExprVersion { value: 7 }),
2139 skip_wal,
2140 })
2141 .collect(),
2142 })
2143 .unwrap();
2144
2145 assert_eq!(3, requests.len());
2146 for ((id, request), skip_wal) in requests.into_iter().zip([false, true, false]) {
2147 assert_eq!(region_id, id);
2148 let RegionRequest::Put(request) = request else {
2149 panic!("expected a put request");
2150 };
2151 assert_eq!(rows, request.rows);
2152 assert_eq!(skip_wal, request.skip_wal);
2153 assert_eq!(Some(7), request.partition_expr_version);
2154 assert!(request.hint.is_none());
2155 }
2156 }
2157
2158 #[test]
2159 fn test_make_region_compact_with_time_range() {
2160 let requests = make_region_compact(CompactRequest {
2161 region_id: 42,
2162 time_range: Some(api::v1::region::CompactionTimeRange {
2163 start: 1_000,
2164 end: 2_000,
2165 time_unit: api::v1::TimeUnit::Microsecond as i32,
2166 }),
2167 ..Default::default()
2168 })
2169 .unwrap();
2170
2171 let RegionRequest::Compact(request) = &requests[0].1 else {
2172 unreachable!();
2173 };
2174 assert_eq!(
2175 Some(
2176 TimestampRange::new(
2177 Timestamp::new_microsecond(1_000),
2178 Timestamp::new_microsecond(2_000),
2179 )
2180 .unwrap()
2181 ),
2182 request.time_range
2183 );
2184 }
2185
2186 #[test]
2187 fn test_make_region_truncate_unflushed() {
2188 let region_id = RegionId::new(42, 3);
2189 let requests =
2190 RegionRequest::try_from_request_body(region_request::Body::Truncate(TruncateRequest {
2191 region_id: region_id.as_u64(),
2192 kind: Some(truncate_request::Kind::Unflushed(
2193 api::v1::region::Unflushed {},
2194 )),
2195 }))
2196 .unwrap();
2197
2198 assert_eq!(region_id, requests[0].0);
2199 assert!(matches!(
2200 requests[0].1,
2201 RegionRequest::Truncate(RegionTruncateRequest::Unflushed)
2202 ));
2203 }
2204
2205 #[test]
2206 fn test_make_region_truncate_requires_kind() {
2207 let error =
2208 RegionRequest::try_from_request_body(region_request::Body::Truncate(TruncateRequest {
2209 region_id: RegionId::new(42, 3).as_u64(),
2210 kind: None,
2211 }))
2212 .unwrap_err();
2213
2214 assert!(
2215 error
2216 .to_string()
2217 .contains("missing kind in TruncateRequest")
2218 );
2219 }
2220
2221 #[test]
2222 fn test_from_proto_location() {
2223 let proto_location = v1::AddColumnLocation {
2224 location_type: LocationType::First as i32,
2225 after_column_name: String::default(),
2226 };
2227 let location = AddColumnLocation::try_from(proto_location).unwrap();
2228 assert_eq!(location, AddColumnLocation::First);
2229
2230 let proto_location = v1::AddColumnLocation {
2231 location_type: 10,
2232 after_column_name: String::default(),
2233 };
2234 AddColumnLocation::try_from(proto_location).unwrap_err();
2235
2236 let proto_location = v1::AddColumnLocation {
2237 location_type: LocationType::After as i32,
2238 after_column_name: "a".to_string(),
2239 };
2240 let location = AddColumnLocation::try_from(proto_location).unwrap();
2241 assert_eq!(
2242 location,
2243 AddColumnLocation::After {
2244 column_name: "a".to_string()
2245 }
2246 );
2247 }
2248
2249 #[test]
2250 fn test_from_none_proto_add_column() {
2251 AddColumn::try_from(v1::region::AddColumn {
2252 column_def: None,
2253 location: None,
2254 })
2255 .unwrap_err();
2256 }
2257
2258 #[test]
2259 fn test_compaction_output_parallelism_set_unset_and_serde() {
2260 for (raw, expected) in [("", None), ("1", Some(1)), ("4", Some(4))] {
2261 let option = SetRegionOption::try_from(&PbOption {
2262 key: COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM.to_string(),
2263 value: raw.to_string(),
2264 })
2265 .unwrap();
2266 assert_eq!(
2267 SetRegionOption::CompactionOutputParallelism(expected),
2268 option
2269 );
2270 let json = serde_json::to_string(&option).unwrap();
2271 assert_eq!(
2272 option,
2273 serde_json::from_str::<SetRegionOption>(&json).unwrap()
2274 );
2275 }
2276 for raw in ["0", "-1", "1.5", "invalid", "18446744073709551616"] {
2277 assert!(
2278 SetRegionOption::try_from(&PbOption {
2279 key: COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM.to_string(),
2280 value: raw.to_string(),
2281 })
2282 .is_err()
2283 );
2284 }
2285 let unset =
2286 UnsetRegionOption::try_from(COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM).unwrap();
2287 assert_eq!(COMPACTION_EXPERIMENTAL_OUTPUT_PARALLELISM, unset.as_str());
2288 assert_eq!(
2289 SetRegionOption::CompactionOutputParallelism(None),
2290 (&unset).into()
2291 );
2292 }
2293
2294 #[test]
2295 fn test_set_region_option_auto_flush_interval_try_from() {
2296 use std::time::Duration;
2297
2298 let pb = PbOption {
2300 key: "auto_flush_interval".to_string(),
2301 value: "5m".to_string(),
2302 };
2303 let opt = SetRegionOption::try_from(&pb).unwrap();
2304 assert_eq!(
2305 opt,
2306 SetRegionOption::AutoFlushInterval(Some(Duration::from_secs(300)))
2307 );
2308
2309 let pb = PbOption {
2311 key: "auto_flush_interval".to_string(),
2312 value: String::new(),
2313 };
2314 let opt = SetRegionOption::try_from(&pb).unwrap();
2315 assert_eq!(opt, SetRegionOption::AutoFlushInterval(None));
2316
2317 let pb = PbOption {
2319 key: "auto_flush_interval".to_string(),
2320 value: "0s".to_string(),
2321 };
2322 assert!(SetRegionOption::try_from(&pb).is_err());
2323
2324 let pb = PbOption {
2326 key: "auto_flush_interval".to_string(),
2327 value: "not_a_duration".to_string(),
2328 };
2329 assert!(SetRegionOption::try_from(&pb).is_err());
2330 }
2331
2332 #[test]
2333 fn test_set_region_option_skip_wal_try_from() {
2334 let pb = PbOption {
2335 key: SKIP_WAL_KEY.to_string(),
2336 value: "true".to_string(),
2337 };
2338 assert_eq!(
2339 SetRegionOption::SkipWal(true),
2340 SetRegionOption::try_from(&pb).unwrap()
2341 );
2342
2343 let pb = PbOption {
2344 key: SKIP_WAL_KEY.to_string(),
2345 value: "false".to_string(),
2346 };
2347 assert_eq!(
2348 SetRegionOption::SkipWal(false),
2349 SetRegionOption::try_from(&pb).unwrap()
2350 );
2351
2352 for value in ["", "invalid"] {
2353 let pb = PbOption {
2354 key: SKIP_WAL_KEY.to_string(),
2355 value: value.to_string(),
2356 };
2357 assert!(SetRegionOption::try_from(&pb).is_err());
2358 }
2359
2360 assert!(UnsetRegionOption::try_from(SKIP_WAL_KEY).is_err());
2361 }
2362
2363 #[test]
2364 fn test_set_region_option_skip_wal_serde_compatibility() {
2365 let legacy = serde_json::from_str::<SetRegionOption>(r#""SkipWal""#).unwrap();
2366 assert_eq!(SetRegionOption::SkipWal(true), legacy);
2367
2368 check_set_region_option_skip_wal_serde_compatibility(false);
2369 check_set_region_option_skip_wal_serde_compatibility(true);
2370 }
2371
2372 fn check_set_region_option_skip_wal_serde_compatibility(skip_wal: bool) {
2373 let option = SetRegionOption::SkipWal(skip_wal);
2374 let serialized = serde_json::to_string(&option).unwrap();
2375 let expected = if skip_wal {
2376 r#""SkipWal""#
2377 } else {
2378 r#"{"SkipWal":false}"#
2379 };
2380 assert_eq!(expected, serialized);
2381 assert_eq!(
2382 option,
2383 serde_json::from_str::<SetRegionOption>(&serialized).unwrap()
2384 );
2385 if skip_wal {
2386 assert!(serde_json::from_str::<LegacySetRegionOption>(&serialized).is_ok());
2387 assert_eq!(
2388 option,
2389 serde_json::from_str::<SetRegionOption>(r#"{"SkipWal":true}"#).unwrap()
2390 );
2391 }
2392 }
2393
2394 #[test]
2395 fn test_set_region_option_max_row_group_row_count_try_from() {
2396 let pb = PbOption {
2397 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2398 value: "512".to_string(),
2399 };
2400 assert_eq!(
2401 SetRegionOption::MaxRowGroupRowCount(Some(512)),
2402 SetRegionOption::try_from(&pb).unwrap()
2403 );
2404
2405 let pb = PbOption {
2406 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2407 value: String::new(),
2408 };
2409 assert_eq!(
2410 SetRegionOption::MaxRowGroupRowCount(None),
2411 SetRegionOption::try_from(&pb).unwrap()
2412 );
2413
2414 for value in [
2415 "0".to_string(),
2416 (MAX_ROW_GROUP_ROW_COUNT_LIMIT + 1).to_string(),
2417 "invalid".to_string(),
2418 ] {
2419 let pb = PbOption {
2420 key: MAX_ROW_GROUP_ROW_COUNT.to_string(),
2421 value,
2422 };
2423 assert!(SetRegionOption::try_from(&pb).is_err());
2424 }
2425
2426 assert_eq!(
2427 UnsetRegionOption::MaxRowGroupRowCount,
2428 UnsetRegionOption::try_from(MAX_ROW_GROUP_ROW_COUNT).unwrap()
2429 );
2430 }
2431
2432 #[test]
2433 fn test_set_region_option_preserve_row_sequence_try_from() {
2434 for (value, expected) in [("true", true), ("false", false)] {
2435 let pb = PbOption {
2436 key: PRESERVE_ROW_SEQUENCE.to_string(),
2437 value: value.to_string(),
2438 };
2439 assert_eq!(
2440 SetRegionOption::PreserveRowSequence(expected),
2441 SetRegionOption::try_from(&pb).unwrap()
2442 );
2443 }
2444
2445 for value in ["1", "invalid"] {
2446 let pb = PbOption {
2447 key: PRESERVE_ROW_SEQUENCE.to_string(),
2448 value: value.to_string(),
2449 };
2450 assert!(SetRegionOption::try_from(&pb).is_err());
2451 }
2452
2453 assert_eq!(
2454 UnsetRegionOption::PreserveRowSequence,
2455 UnsetRegionOption::try_from(PRESERVE_ROW_SEQUENCE).unwrap()
2456 );
2457 assert_eq!(
2458 SetRegionOption::PreserveRowSequence(false),
2459 (&UnsetRegionOption::PreserveRowSequence).into()
2460 );
2461 }
2462
2463 #[test]
2464 fn test_set_twcs_window_trigger_options_try_from() {
2465 for key in [
2466 "compaction.twcs.active_window.trigger_file_num",
2467 "compaction.twcs.active_window.l1_merge_trigger",
2468 "compaction.twcs.inactive_window.trigger_file_num",
2469 "compaction.twcs.inactive_window.l1_merge_trigger",
2470 ] {
2471 let option = PbOption {
2472 key: key.to_string(),
2473 value: "8".to_string(),
2474 };
2475 assert_eq!(
2476 SetRegionOption::Twsc(key.to_string(), "8".to_string()),
2477 SetRegionOption::try_from(&option).unwrap(),
2478 "{key}"
2479 );
2480 }
2481 }
2482
2483 #[test]
2484 fn test_unset_twcs_window_trigger_options_try_from() {
2485 for (key, expected) in [
2486 (
2487 "compaction.twcs.active_window.trigger_file_num",
2488 UnsetRegionOption::TwcsActiveWindowTriggerFileNum,
2489 ),
2490 (
2491 "compaction.twcs.active_window.l1_merge_trigger",
2492 UnsetRegionOption::TwcsActiveWindowL1MergeTrigger,
2493 ),
2494 (
2495 "compaction.twcs.inactive_window.trigger_file_num",
2496 UnsetRegionOption::TwcsInactiveWindowTriggerFileNum,
2497 ),
2498 ] {
2499 assert_eq!(expected, UnsetRegionOption::try_from(key).unwrap());
2500 assert_eq!(
2501 SetRegionOption::Twsc(key.to_string(), String::new()),
2502 SetRegionOption::from(&expected)
2503 );
2504 }
2505
2506 let key = "compaction.twcs.inactive_window.l1_merge_trigger";
2507 let option = UnsetRegionOption::try_from(key).unwrap();
2508 assert_eq!(key, option.to_string());
2509 assert_eq!(
2510 SetRegionOption::Twsc(key.to_string(), String::new()),
2511 SetRegionOption::from(&option)
2512 );
2513 }
2514
2515 #[test]
2516 fn test_json_settings_path_validation_from_proto() {
2517 for (path, valid_path) in [
2518 (vec!["nested", "leaf"], true),
2519 (vec![""], false),
2520 (vec!["nested", ""], false),
2521 (
2522 vec!["nested", datatypes::json::JSON2_REMAINDER_FIELD_NAME],
2523 false,
2524 ),
2525 ] {
2526 for inverted_index in [false, true] {
2527 let settings = api::v1::SetJsonSettings {
2528 column_name: "payload".into(),
2529 settings: Some(api::v1::JsonSettings {
2530 type_hints: vec![api::v1::JsonTypeHint {
2531 path: path.iter().map(|part| (*part).to_string()).collect(),
2532 data_type: ColumnDataType::String as i32,
2533 inverted_index,
2534 skipping_index: None,
2535 ..Default::default()
2536 }],
2537 max_auto_expanded_paths: Some(0),
2538 }),
2539 };
2540 let result = RegionAlterRequest::try_from(AlterRequest {
2541 region_id: 0,
2542 schema_version: 1,
2543 kind: Some(alter_request::Kind::SetJsonSettings(settings)),
2544 });
2545 assert_eq!(
2546 result.is_ok(),
2547 valid_path,
2548 "path: {path:?}, inverted_index: {inverted_index}"
2549 );
2550 }
2551 }
2552 }
2553
2554 #[test]
2555 fn test_from_proto_alter_request() {
2556 RegionAlterRequest::try_from(AlterRequest {
2557 region_id: 0,
2558 schema_version: 1,
2559 kind: None,
2560 })
2561 .unwrap_err();
2562
2563 let request = RegionAlterRequest::try_from(AlterRequest {
2564 region_id: 0,
2565 schema_version: 1,
2566 kind: Some(alter_request::Kind::AddColumns(v1::region::AddColumns {
2567 add_columns: vec![v1::region::AddColumn {
2568 column_def: Some(RegionColumnDef {
2569 column_def: Some(ColumnDef {
2570 name: "a".to_string(),
2571 data_type: ColumnDataType::String as i32,
2572 is_nullable: true,
2573 default_constraint: vec![],
2574 semantic_type: SemanticType::Field as i32,
2575 comment: String::new(),
2576 ..Default::default()
2577 }),
2578 column_id: 1,
2579 }),
2580 location: Some(v1::AddColumnLocation {
2581 location_type: LocationType::First as i32,
2582 after_column_name: String::default(),
2583 }),
2584 }],
2585 })),
2586 })
2587 .unwrap();
2588
2589 assert_eq!(
2590 request,
2591 RegionAlterRequest {
2592 kind: AlterKind::AddColumns {
2593 columns: vec![AddColumn {
2594 column_metadata: ColumnMetadata {
2595 column_schema: ColumnSchema::new(
2596 "a",
2597 ConcreteDataType::string_datatype(),
2598 true,
2599 ),
2600 semantic_type: SemanticType::Field,
2601 column_id: 1,
2602 },
2603 location: Some(AddColumnLocation::First),
2604 }]
2605 },
2606 }
2607 );
2608
2609 let request = RegionAlterRequest::try_from(AlterRequest {
2610 region_id: 0,
2611 schema_version: 1,
2612 kind: Some(alter_request::Kind::SetJsonSettings(v1::SetJsonSettings {
2613 column_name: "payload".to_string(),
2614 settings: Some(v1::JsonSettings {
2615 type_hints: vec![v1::JsonTypeHint {
2616 path: vec!["service".to_string()],
2617 data_type: ColumnDataType::String as i32,
2618 datatype_extension: None,
2619 inverted_index: true,
2620 skipping_index: Some(v1::JsonSkippingIndexOptions {
2621 granularity: 8,
2622 index_type: 0,
2623 false_positive_rate: 0.05,
2624 }),
2625 }],
2626 max_auto_expanded_paths: Some(10),
2627 }),
2628 })),
2629 })
2630 .unwrap();
2631
2632 let AlterKind::SetJsonSettings {
2633 column_name,
2634 settings,
2635 } = request.kind
2636 else {
2637 unreachable!()
2638 };
2639 assert_eq!("payload", column_name);
2640 assert_eq!(Some(10), settings.max_auto_expanded_paths());
2641 assert_eq!(1, settings.type_hints().len());
2642 assert!(settings.type_hints()[0].inverted_index);
2643 assert_eq!(
2644 settings.type_hints()[0]
2645 .skipping_index
2646 .as_ref()
2647 .unwrap()
2648 .granularity,
2649 8
2650 );
2651 }
2652
2653 #[test]
2654 fn test_write_buffer_size_region_options() {
2655 let option = PbOption {
2656 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2657 value: "128MiB".to_string(),
2658 };
2659 assert_eq!(
2660 SetRegionOption::WriteBufferSize(Some(ReadableSize::mb(128))),
2661 SetRegionOption::try_from(&option).unwrap()
2662 );
2663
2664 let option = PbOption {
2665 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2666 value: "invalid".to_string(),
2667 };
2668 SetRegionOption::try_from(&option).unwrap_err();
2669
2670 let option = PbOption {
2673 key: WRITE_BUFFER_SIZE_KEY.to_string(),
2674 value: String::new(),
2675 };
2676 SetRegionOption::try_from(&option).unwrap_err();
2677
2678 assert_eq!(
2679 UnsetRegionOption::WriteBufferSize,
2680 UnsetRegionOption::try_from(WRITE_BUFFER_SIZE_KEY).unwrap()
2681 );
2682 assert_eq!(
2683 SetRegionOption::WriteBufferSize(None),
2684 SetRegionOption::from(&UnsetRegionOption::WriteBufferSize)
2685 );
2686 }
2687
2688 fn new_metadata() -> RegionMetadata {
2691 let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
2692 builder
2693 .push_column_metadata(ColumnMetadata {
2694 column_schema: ColumnSchema::new(
2695 "ts",
2696 ConcreteDataType::timestamp_millisecond_datatype(),
2697 false,
2698 ),
2699 semantic_type: SemanticType::Timestamp,
2700 column_id: 1,
2701 })
2702 .push_column_metadata(ColumnMetadata {
2703 column_schema: ColumnSchema::new(
2704 "tag_0",
2705 ConcreteDataType::string_datatype(),
2706 true,
2707 ),
2708 semantic_type: SemanticType::Tag,
2709 column_id: 2,
2710 })
2711 .push_column_metadata(ColumnMetadata {
2712 column_schema: ColumnSchema::new(
2713 "field_0",
2714 ConcreteDataType::string_datatype(),
2715 true,
2716 ),
2717 semantic_type: SemanticType::Field,
2718 column_id: 3,
2719 })
2720 .push_column_metadata(ColumnMetadata {
2721 column_schema: ColumnSchema::new(
2722 "field_1",
2723 ConcreteDataType::boolean_datatype(),
2724 true,
2725 ),
2726 semantic_type: SemanticType::Field,
2727 column_id: 4,
2728 })
2729 .primary_key(vec![2]);
2730 builder.build().unwrap()
2731 }
2732
2733 fn json2_column_schema(name: &str, settings: JsonSettings) -> ColumnSchema {
2734 let mut column_schema =
2735 ColumnSchema::new(name, ConcreteDataType::Json(JsonType::null()), true);
2736 column_schema.with_extension_type(&Json2ExtensionType::new(std::sync::Arc::new(
2737 JsonMetadata::new(settings),
2738 )));
2739 column_schema
2740 }
2741
2742 #[test]
2743 fn test_add_column_validate() {
2744 let metadata = new_metadata();
2745 let add_column = AddColumn {
2746 column_metadata: ColumnMetadata {
2747 column_schema: ColumnSchema::new(
2748 "tag_1",
2749 ConcreteDataType::string_datatype(),
2750 true,
2751 ),
2752 semantic_type: SemanticType::Tag,
2753 column_id: 5,
2754 },
2755 location: None,
2756 };
2757 add_column.validate(&metadata).unwrap();
2758 assert!(add_column.need_alter(&metadata));
2759
2760 AddColumn {
2762 column_metadata: ColumnMetadata {
2763 column_schema: ColumnSchema::new(
2764 "tag_1",
2765 ConcreteDataType::string_datatype(),
2766 false,
2767 ),
2768 semantic_type: SemanticType::Tag,
2769 column_id: 5,
2770 },
2771 location: None,
2772 }
2773 .validate(&metadata)
2774 .unwrap_err();
2775
2776 let add_column = AddColumn {
2778 column_metadata: ColumnMetadata {
2779 column_schema: ColumnSchema::new(
2780 "tag_0",
2781 ConcreteDataType::string_datatype(),
2782 true,
2783 ),
2784 semantic_type: SemanticType::Tag,
2785 column_id: 2,
2786 },
2787 location: None,
2788 };
2789 add_column.validate(&metadata).unwrap();
2790 assert!(!add_column.need_alter(&metadata));
2791 }
2792
2793 #[test]
2794 fn test_add_duplicate_columns() {
2795 let kind = AlterKind::AddColumns {
2796 columns: vec![
2797 AddColumn {
2798 column_metadata: ColumnMetadata {
2799 column_schema: ColumnSchema::new(
2800 "tag_1",
2801 ConcreteDataType::string_datatype(),
2802 true,
2803 ),
2804 semantic_type: SemanticType::Tag,
2805 column_id: 5,
2806 },
2807 location: None,
2808 },
2809 AddColumn {
2810 column_metadata: ColumnMetadata {
2811 column_schema: ColumnSchema::new(
2812 "tag_1",
2813 ConcreteDataType::string_datatype(),
2814 true,
2815 ),
2816 semantic_type: SemanticType::Field,
2817 column_id: 6,
2818 },
2819 location: None,
2820 },
2821 ],
2822 };
2823 let metadata = new_metadata();
2824 kind.validate(&metadata).unwrap();
2825 assert!(kind.need_alter(&metadata));
2826 }
2827
2828 #[test]
2829 fn test_add_existing_column_different_metadata() {
2830 let metadata = new_metadata();
2831
2832 let kind = AlterKind::AddColumns {
2834 columns: vec![AddColumn {
2835 column_metadata: ColumnMetadata {
2836 column_schema: ColumnSchema::new(
2837 "tag_0",
2838 ConcreteDataType::string_datatype(),
2839 true,
2840 ),
2841 semantic_type: SemanticType::Tag,
2842 column_id: 4,
2843 },
2844 location: None,
2845 }],
2846 };
2847 kind.validate(&metadata).unwrap_err();
2848
2849 let kind = AlterKind::AddColumns {
2851 columns: vec![AddColumn {
2852 column_metadata: ColumnMetadata {
2853 column_schema: ColumnSchema::new(
2854 "tag_0",
2855 ConcreteDataType::int64_datatype(),
2856 true,
2857 ),
2858 semantic_type: SemanticType::Tag,
2859 column_id: 2,
2860 },
2861 location: None,
2862 }],
2863 };
2864 kind.validate(&metadata).unwrap_err();
2865
2866 let kind = AlterKind::AddColumns {
2868 columns: vec![AddColumn {
2869 column_metadata: ColumnMetadata {
2870 column_schema: ColumnSchema::new(
2871 "tag_1",
2872 ConcreteDataType::string_datatype(),
2873 true,
2874 ),
2875 semantic_type: SemanticType::Tag,
2876 column_id: 2,
2877 },
2878 location: None,
2879 }],
2880 };
2881 kind.validate(&metadata).unwrap_err();
2882 }
2883
2884 #[test]
2885 fn test_add_existing_column_with_location() {
2886 let metadata = new_metadata();
2887 let kind = AlterKind::AddColumns {
2888 columns: vec![AddColumn {
2889 column_metadata: ColumnMetadata {
2890 column_schema: ColumnSchema::new(
2891 "tag_0",
2892 ConcreteDataType::string_datatype(),
2893 true,
2894 ),
2895 semantic_type: SemanticType::Tag,
2896 column_id: 2,
2897 },
2898 location: Some(AddColumnLocation::First),
2899 }],
2900 };
2901 kind.validate(&metadata).unwrap_err();
2902 }
2903
2904 #[test]
2905 fn test_validate_drop_column() {
2906 let metadata = new_metadata();
2907 let kind = AlterKind::DropColumns {
2908 names: vec!["xxxx".to_string()],
2909 };
2910 kind.validate(&metadata).unwrap();
2911 assert!(!kind.need_alter(&metadata));
2912
2913 AlterKind::DropColumns {
2914 names: vec!["tag_0".to_string()],
2915 }
2916 .validate(&metadata)
2917 .unwrap_err();
2918
2919 let kind = AlterKind::DropColumns {
2920 names: vec!["field_0".to_string()],
2921 };
2922 kind.validate(&metadata).unwrap();
2923 assert!(kind.need_alter(&metadata));
2924 }
2925
2926 #[test]
2927 fn test_validate_modify_column_type() {
2928 let metadata = new_metadata();
2929 AlterKind::ModifyColumnTypes {
2930 columns: vec![ModifyColumnType {
2931 column_name: "xxxx".to_string(),
2932 target_type: ConcreteDataType::string_datatype(),
2933 }],
2934 }
2935 .validate(&metadata)
2936 .unwrap_err();
2937
2938 AlterKind::ModifyColumnTypes {
2939 columns: vec![ModifyColumnType {
2940 column_name: "field_1".to_string(),
2941 target_type: ConcreteDataType::date_datatype(),
2942 }],
2943 }
2944 .validate(&metadata)
2945 .unwrap_err();
2946
2947 AlterKind::ModifyColumnTypes {
2948 columns: vec![ModifyColumnType {
2949 column_name: "ts".to_string(),
2950 target_type: ConcreteDataType::date_datatype(),
2951 }],
2952 }
2953 .validate(&metadata)
2954 .unwrap_err();
2955
2956 let kind = AlterKind::ModifyColumnTypes {
2958 columns: vec![ModifyColumnType {
2959 column_name: "ts".to_string(),
2960 target_type: ConcreteDataType::timestamp_microsecond_datatype(),
2961 }],
2962 };
2963 kind.validate(&metadata).unwrap();
2964 assert!(kind.need_alter(&metadata));
2965
2966 let metadata_nano = {
2968 let mut metadata = new_metadata();
2969 for col in metadata.column_metadatas.iter_mut() {
2970 if col.column_schema.name == "ts" {
2971 col.column_schema.data_type = ConcreteDataType::timestamp_nanosecond_datatype();
2972 }
2973 }
2974 metadata
2975 };
2976 AlterKind::ModifyColumnTypes {
2977 columns: vec![ModifyColumnType {
2978 column_name: "ts".to_string(),
2979 target_type: ConcreteDataType::timestamp_millisecond_datatype(),
2980 }],
2981 }
2982 .validate(&metadata_nano)
2983 .unwrap_err();
2984
2985 let same_type = AlterKind::ModifyColumnTypes {
2989 columns: vec![ModifyColumnType {
2990 column_name: "ts".to_string(),
2991 target_type: ConcreteDataType::timestamp_millisecond_datatype(),
2992 }],
2993 };
2994 same_type.validate(&metadata).unwrap();
2995 assert!(!same_type.need_alter(&metadata));
2996
2997 AlterKind::ModifyColumnTypes {
2999 columns: vec![ModifyColumnType {
3000 column_name: "ts".to_string(),
3001 target_type: ConcreteDataType::string_datatype(),
3002 }],
3003 }
3004 .validate(&metadata)
3005 .unwrap_err();
3006
3007 AlterKind::ModifyColumnTypes {
3008 columns: vec![ModifyColumnType {
3009 column_name: "tag_0".to_string(),
3010 target_type: ConcreteDataType::date_datatype(),
3011 }],
3012 }
3013 .validate(&metadata)
3014 .unwrap_err();
3015
3016 let kind = AlterKind::ModifyColumnTypes {
3017 columns: vec![ModifyColumnType {
3018 column_name: "field_0".to_string(),
3019 target_type: ConcreteDataType::int32_datatype(),
3020 }],
3021 };
3022 kind.validate(&metadata).unwrap();
3023 assert!(kind.need_alter(&metadata));
3024 }
3025
3026 #[test]
3027 fn test_validate_set_json_settings() {
3028 let mut metadata = new_metadata();
3029 let current_schema = json2_column_schema("field_0", JsonSettings::new_v2());
3030 let target_settings = JsonSettings::try_new(vec![], Some(10)).unwrap();
3031 metadata
3032 .column_metadatas
3033 .iter_mut()
3034 .find(|column| column.column_schema.name == "field_0")
3035 .unwrap()
3036 .column_schema = current_schema.clone();
3037
3038 let kind = AlterKind::SetJsonSettings {
3039 column_name: "field_0".to_string(),
3040 settings: target_settings.clone(),
3041 };
3042 kind.validate(&metadata).unwrap();
3043 assert!(kind.need_alter(&metadata));
3044
3045 let no_op = AlterKind::SetJsonSettings {
3046 column_name: "field_0".to_string(),
3047 settings: JsonSettings::new_v2(),
3048 };
3049 no_op.validate(&metadata).unwrap();
3050 assert!(!no_op.need_alter(&metadata));
3051
3052 AlterKind::SetJsonSettings {
3053 column_name: "tag_0".to_string(),
3054 settings: target_settings,
3055 }
3056 .validate(&metadata)
3057 .unwrap_err();
3058 }
3059
3060 #[test]
3061 fn test_validate_add_columns() {
3062 let kind = AlterKind::AddColumns {
3063 columns: vec![
3064 AddColumn {
3065 column_metadata: ColumnMetadata {
3066 column_schema: ColumnSchema::new(
3067 "tag_1",
3068 ConcreteDataType::string_datatype(),
3069 true,
3070 ),
3071 semantic_type: SemanticType::Tag,
3072 column_id: 5,
3073 },
3074 location: None,
3075 },
3076 AddColumn {
3077 column_metadata: ColumnMetadata {
3078 column_schema: ColumnSchema::new(
3079 "field_2",
3080 ConcreteDataType::string_datatype(),
3081 true,
3082 ),
3083 semantic_type: SemanticType::Field,
3084 column_id: 6,
3085 },
3086 location: None,
3087 },
3088 ],
3089 };
3090 let request = RegionAlterRequest { kind };
3091 let mut metadata = new_metadata();
3092 metadata.schema_version = 1;
3093 request.validate(&metadata).unwrap();
3094 }
3095
3096 #[test]
3097 fn test_validate_create_region() {
3098 let column_metadatas = vec![
3099 ColumnMetadata {
3100 column_schema: ColumnSchema::new(
3101 "ts",
3102 ConcreteDataType::timestamp_millisecond_datatype(),
3103 false,
3104 ),
3105 semantic_type: SemanticType::Timestamp,
3106 column_id: 1,
3107 },
3108 ColumnMetadata {
3109 column_schema: ColumnSchema::new(
3110 "tag_0",
3111 ConcreteDataType::string_datatype(),
3112 true,
3113 ),
3114 semantic_type: SemanticType::Tag,
3115 column_id: 2,
3116 },
3117 ColumnMetadata {
3118 column_schema: ColumnSchema::new(
3119 "field_0",
3120 ConcreteDataType::string_datatype(),
3121 true,
3122 ),
3123 semantic_type: SemanticType::Field,
3124 column_id: 3,
3125 },
3126 ];
3127 let create = RegionCreateRequest {
3128 engine: "mito".to_string(),
3129 column_metadatas,
3130 primary_key: vec![3, 4],
3131 options: HashMap::new(),
3132 table_dir: "path".to_string(),
3133 path_type: PathType::Bare,
3134 partition_expr_json: Some("".to_string()),
3135 requirements: Default::default(),
3136 };
3137
3138 assert!(create.validate().is_err());
3139 }
3140
3141 #[test]
3142 fn test_parse_create_region_requirements_defaults_to_empty() {
3143 let create = CreateRequest {
3144 region_id: RegionId::new(42, 0).as_u64(),
3145 engine: "mito".to_string(),
3146 column_defs: vec![],
3147 primary_key: vec![],
3148 path: "test".to_string(),
3149 options: HashMap::new(),
3150 partition: None,
3151 requirements: None,
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::empty());
3161 }
3162
3163 #[test]
3164 fn test_parse_create_region_requirements_from_proto() {
3165 let create = CreateRequest {
3166 region_id: RegionId::new(42, 0).as_u64(),
3167 engine: "mito".to_string(),
3168 column_defs: vec![],
3169 primary_key: vec![],
3170 path: "test".to_string(),
3171 options: HashMap::new(),
3172 partition: None,
3173 requirements: Some(api::v1::region::RegionRequirements {
3174 object_storage: true,
3175 }),
3176 };
3177
3178 let requests =
3179 RegionRequest::try_from_request_body(region_request::Body::Create(create)).unwrap();
3180 let RegionRequest::Create(request) = &requests[0].1 else {
3181 unreachable!()
3182 };
3183
3184 assert_eq!(request.requirements, RegionRequirements::object_storage());
3185 }
3186
3187 #[test]
3188 fn test_parse_region_cleanup_from_proto() {
3189 let clean_up = api::v1::region::CleanUpRequest {
3190 region_id: RegionId::new(42, 3).as_u64(),
3191 engine: "mito".to_string(),
3192 path: "test".to_string(),
3193 options: HashMap::from([("k".to_string(), "v".to_string())]),
3194 };
3195
3196 let requests =
3197 RegionRequest::try_from_request_body(region_request::Body::CleanUp(clean_up)).unwrap();
3198 let RegionRequest::CleanUp(request) = &requests[0].1 else {
3199 unreachable!()
3200 };
3201
3202 assert_eq!(requests[0].0, RegionId::new(42, 3));
3203 assert_eq!(request.engine, "mito");
3204 assert_eq!(request.table_dir, "data/test/42/");
3205 assert_eq!(request.path_type, PathType::Bare);
3206 assert_eq!(request.options.get("k"), Some(&"v".to_string()));
3207 }
3208
3209 #[test]
3210 fn test_validate_modify_column_fulltext_options() {
3211 let kind = AlterKind::SetIndexes {
3212 options: vec![SetIndexOption::Fulltext {
3213 column_name: "tag_0".to_string(),
3214 options: FulltextOptions::new_unchecked(
3215 true,
3216 FulltextAnalyzer::Chinese,
3217 false,
3218 FulltextBackend::Bloom,
3219 1000,
3220 0.01,
3221 ),
3222 }],
3223 };
3224 let request = RegionAlterRequest { kind };
3225 let mut metadata = new_metadata();
3226 metadata.schema_version = 1;
3227 request.validate(&metadata).unwrap();
3228
3229 let kind = AlterKind::UnsetIndexes {
3230 options: vec![UnsetIndexOption::Fulltext {
3231 column_name: "tag_0".to_string(),
3232 }],
3233 };
3234 let request = RegionAlterRequest { kind };
3235 let mut metadata = new_metadata();
3236 metadata.schema_version = 1;
3237 request.validate(&metadata).unwrap();
3238 }
3239
3240 #[test]
3241 fn test_validate_sync_columns() {
3242 let metadata = new_metadata();
3243 let kind = AlterKind::SyncColumns {
3244 column_metadatas: vec![
3245 ColumnMetadata {
3246 column_schema: ColumnSchema::new(
3247 "tag_1",
3248 ConcreteDataType::string_datatype(),
3249 true,
3250 ),
3251 semantic_type: SemanticType::Tag,
3252 column_id: 5,
3253 },
3254 ColumnMetadata {
3255 column_schema: ColumnSchema::new(
3256 "field_2",
3257 ConcreteDataType::string_datatype(),
3258 true,
3259 ),
3260 semantic_type: SemanticType::Field,
3261 column_id: 6,
3262 },
3263 ],
3264 };
3265 let err = kind.validate(&metadata).unwrap_err();
3266 assert!(err.to_string().contains("not a primary key"));
3267
3268 let mut column_metadatas_with_different_ts_column = metadata.column_metadatas.clone();
3270 let ts_column = column_metadatas_with_different_ts_column
3271 .iter_mut()
3272 .find(|c| c.semantic_type == SemanticType::Timestamp)
3273 .unwrap();
3274 ts_column.column_schema.name = "ts1".to_string();
3275
3276 let kind = AlterKind::SyncColumns {
3277 column_metadatas: column_metadatas_with_different_ts_column,
3278 };
3279 let err = kind.validate(&metadata).unwrap_err();
3280 assert!(
3281 err.to_string()
3282 .contains("timestamp column ts has different id")
3283 );
3284
3285 let mut column_metadatas_with_different_pk_column = metadata.column_metadatas.clone();
3287 let pk_column = column_metadatas_with_different_pk_column
3288 .iter_mut()
3289 .find(|c| c.column_schema.name == "tag_0")
3290 .unwrap();
3291 pk_column.column_id = 100;
3292 let kind = AlterKind::SyncColumns {
3293 column_metadatas: column_metadatas_with_different_pk_column,
3294 };
3295 let err = kind.validate(&metadata).unwrap_err();
3296 assert!(
3297 err.to_string()
3298 .contains("column with same name tag_0 has different id")
3299 );
3300
3301 let mut column_metadatas_with_new_field_column = metadata.column_metadatas.clone();
3303 column_metadatas_with_new_field_column.push(ColumnMetadata {
3304 column_schema: ColumnSchema::new("field_2", ConcreteDataType::string_datatype(), true),
3305 semantic_type: SemanticType::Field,
3306 column_id: 4,
3307 });
3308 let kind = AlterKind::SyncColumns {
3309 column_metadatas: column_metadatas_with_new_field_column,
3310 };
3311 kind.validate(&metadata).unwrap();
3312 }
3313
3314 #[test]
3315 fn test_cast_path_type_to_primitive() {
3316 assert_eq!(PathType::Bare as u8, 0);
3317 assert_eq!(PathType::Data as u8, 1);
3318 assert_eq!(PathType::Metadata as u8, 2);
3319 assert_eq!(
3320 PathType::try_from(PathType::Bare as u8).unwrap(),
3321 PathType::Bare
3322 );
3323 assert_eq!(
3324 PathType::try_from(PathType::Data as u8).unwrap(),
3325 PathType::Data
3326 );
3327 assert_eq!(
3328 PathType::try_from(PathType::Metadata as u8).unwrap(),
3329 PathType::Metadata
3330 );
3331 }
3332}