1use std::collections::HashMap;
20use std::time::Duration;
21
22use common_base::readable_size::ReadableSize;
23use common_stat::get_total_memory_readable;
24use common_time::TimeToLive;
25use common_wal::options::{WAL_OPTIONS_KEY, WalOptions};
26use serde::de::Error as _;
27use serde::{Deserialize, Deserializer, Serialize};
28use serde_json::Value;
29use serde_with::{DisplayFromStr, NoneAsEmptyString, serde_as, with_prefix};
30use snafu::{ResultExt, ensure};
31use store_api::codec::PrimaryKeyEncoding;
32use store_api::mito_engine_options::COMPACTION_OVERRIDE;
33use store_api::storage::ColumnId;
34use strum::EnumString;
35
36use crate::error::{Error, InvalidRegionOptionsSnafu, JsonOptionsSnafu, Result};
37use crate::memtable::partition_tree::{DEFAULT_FREEZE_THRESHOLD, DEFAULT_MAX_KEYS_PER_SHARD};
38use crate::sst::FormatType;
39
40const DEFAULT_INDEX_SEGMENT_ROW_COUNT: usize = 1024;
41const COMPACTION_TWCS_PREFIX: &str = "compaction.twcs.";
42const MEMTABLE_PARTITION_TREE_PREFIX: &str = "memtable.partition_tree.";
43
44pub(crate) fn parse_wal_options(
45 options_map: &HashMap<String, String>,
46) -> std::result::Result<WalOptions, serde_json::Error> {
47 options_map
48 .get(WAL_OPTIONS_KEY)
49 .map_or(Ok(WalOptions::default()), |encoded_wal_options| {
50 serde_json::from_str(encoded_wal_options)
51 })
52}
53
54#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString)]
56#[serde(rename_all = "snake_case")]
57#[strum(serialize_all = "snake_case")]
58pub enum MergeMode {
59 #[default]
61 LastRow,
62 LastNonNull,
64}
65
66#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(default)]
73pub struct RegionOptions {
74 pub ttl: Option<TimeToLive>,
76 pub compaction: CompactionOptions,
78 pub compaction_override: bool,
79 pub storage: Option<String>,
81 pub append_mode: bool,
83 pub wal_options: WalOptions,
85 pub index_options: IndexOptions,
87 pub memtable: Option<MemtableOptions>,
89 pub merge_mode: Option<MergeMode>,
92 pub sst_format: Option<FormatType>,
94}
95
96impl RegionOptions {
97 pub fn validate(&self) -> Result<()> {
99 if self.append_mode {
100 ensure!(
101 self.merge_mode
102 .is_none_or(|mode| mode == MergeMode::LastRow),
103 InvalidRegionOptionsSnafu {
104 reason: "only last_row merge_mode is allowed when append_mode is enabled",
105 }
106 );
107 }
108 Ok(())
109 }
110
111 pub fn need_dedup(&self) -> bool {
113 !self.append_mode
114 }
115
116 pub fn merge_mode(&self) -> MergeMode {
118 self.merge_mode.unwrap_or_default()
119 }
120
121 pub fn primary_key_encoding(&self) -> PrimaryKeyEncoding {
123 self.memtable
124 .as_ref()
125 .map_or(PrimaryKeyEncoding::default(), |memtable| {
126 memtable.primary_key_encoding()
127 })
128 }
129}
130
131impl TryFrom<&HashMap<String, String>> for RegionOptions {
132 type Error = Error;
133
134 fn try_from(options_map: &HashMap<String, String>) -> Result<Self> {
135 let value = options_map_to_value(options_map);
136 let json = serde_json::to_string(&value).context(JsonOptionsSnafu)?;
137
138 let options: RegionOptionsWithoutEnum =
142 serde_json::from_str(&json).context(JsonOptionsSnafu)?;
143 let has_compaction_type =
144 validate_enum_options(options_map, "compaction.type", &[COMPACTION_TWCS_PREFIX])?;
145 let compaction = if has_compaction_type {
146 serde_json::from_str(&json).context(JsonOptionsSnafu)?
147 } else {
148 CompactionOptions::default()
149 };
150
151 let wal_options = parse_wal_options(options_map).context(JsonOptionsSnafu)?;
152
153 let index_options: IndexOptions = serde_json::from_str(&json).context(JsonOptionsSnafu)?;
154 let memtable = if validate_enum_options(
155 options_map,
156 "memtable.type",
157 &[MEMTABLE_PARTITION_TREE_PREFIX],
158 )? {
159 Some(serde_json::from_str(&json).context(JsonOptionsSnafu)?)
160 } else {
161 None
162 };
163
164 let compaction_override_flag = options_map
165 .get(COMPACTION_OVERRIDE)
166 .map(|v| matches!(v.to_lowercase().as_str(), "true" | "1"))
167 .unwrap_or(false);
168 let compaction_override = has_compaction_type || compaction_override_flag;
169
170 let opts = RegionOptions {
171 ttl: options.ttl,
172 compaction,
173 compaction_override,
174 storage: options.storage,
175 append_mode: options.append_mode,
176 wal_options,
177 index_options,
178 memtable,
179 merge_mode: options.merge_mode,
180 sst_format: options.sst_format,
181 };
182 opts.validate()?;
183
184 Ok(opts)
185 }
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(tag = "compaction.type")]
191#[serde(rename_all = "snake_case")]
192pub enum CompactionOptions {
193 #[serde(with = "prefix_twcs")]
195 Twcs(TwcsOptions),
196}
197
198impl CompactionOptions {
199 pub(crate) fn time_window(&self) -> Option<Duration> {
200 match self {
201 CompactionOptions::Twcs(opts) => opts.time_window,
202 }
203 }
204
205 pub(crate) fn remote_compaction(&self) -> bool {
206 match self {
207 CompactionOptions::Twcs(opts) => opts.remote_compaction,
208 }
209 }
210
211 pub(crate) fn fallback_to_local(&self) -> bool {
212 match self {
213 CompactionOptions::Twcs(opts) => opts.fallback_to_local,
214 }
215 }
216}
217
218impl Default for CompactionOptions {
219 fn default() -> Self {
220 Self::Twcs(TwcsOptions::default())
221 }
222}
223
224#[serde_as]
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(default)]
228pub struct TwcsOptions {
229 #[serde_as(as = "DisplayFromStr")]
231 pub trigger_file_num: usize,
232 #[serde(with = "humantime_serde")]
234 pub time_window: Option<Duration>,
235 pub max_output_file_size: Option<ReadableSize>,
237 #[serde_as(as = "DisplayFromStr")]
239 pub remote_compaction: bool,
240 #[serde_as(as = "DisplayFromStr")]
242 pub fallback_to_local: bool,
243}
244
245with_prefix!(prefix_twcs "compaction.twcs.");
246
247impl TwcsOptions {
248 pub fn time_window_seconds(&self) -> Option<i64> {
250 self.time_window.and_then(|window| {
251 let window_secs = window.as_secs();
252 if window_secs == 0 {
253 None
254 } else {
255 window_secs.try_into().ok()
256 }
257 })
258 }
259}
260
261impl Default for TwcsOptions {
262 fn default() -> Self {
263 Self {
264 trigger_file_num: 4,
265 time_window: None,
266 max_output_file_size: Some(ReadableSize::mb(512)),
267 remote_compaction: false,
268 fallback_to_local: true,
269 }
270 }
271}
272
273#[serde_as]
276#[derive(Debug, Deserialize)]
277#[serde(default)]
278struct RegionOptionsWithoutEnum {
279 ttl: Option<TimeToLive>,
281 storage: Option<String>,
282 #[serde_as(as = "DisplayFromStr")]
283 append_mode: bool,
284 #[serde_as(as = "NoneAsEmptyString")]
285 merge_mode: Option<MergeMode>,
286 #[serde_as(as = "NoneAsEmptyString")]
287 sst_format: Option<FormatType>,
288}
289
290impl Default for RegionOptionsWithoutEnum {
291 fn default() -> Self {
292 let options = RegionOptions::default();
293 RegionOptionsWithoutEnum {
294 ttl: options.ttl,
295 storage: options.storage,
296 append_mode: options.append_mode,
297 merge_mode: options.merge_mode,
298 sst_format: options.sst_format,
299 }
300 }
301}
302
303with_prefix!(prefix_inverted_index "index.inverted_index.");
304
305#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
307#[serde(default)]
308pub struct IndexOptions {
309 #[serde(flatten, with = "prefix_inverted_index")]
311 pub inverted_index: InvertedIndexOptions,
312}
313
314#[serde_as]
316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317#[serde(default)]
318pub struct InvertedIndexOptions {
319 #[serde(deserialize_with = "deserialize_ignore_column_ids")]
322 #[serde(serialize_with = "serialize_ignore_column_ids")]
323 pub ignore_column_ids: Vec<ColumnId>,
324
325 #[serde_as(as = "DisplayFromStr")]
327 pub segment_row_count: usize,
328}
329
330impl Default for InvertedIndexOptions {
331 fn default() -> Self {
332 Self {
333 ignore_column_ids: Vec::new(),
334 segment_row_count: DEFAULT_INDEX_SEGMENT_ROW_COUNT,
335 }
336 }
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
341#[serde(tag = "memtable.type", rename_all = "snake_case")]
342pub enum MemtableOptions {
343 TimeSeries,
344 #[serde(with = "prefix_partition_tree")]
345 PartitionTree(PartitionTreeOptions),
346}
347
348with_prefix!(prefix_partition_tree "memtable.partition_tree.");
349
350impl MemtableOptions {
351 pub fn primary_key_encoding(&self) -> PrimaryKeyEncoding {
353 match self {
354 MemtableOptions::PartitionTree(opts) => opts.primary_key_encoding,
355 _ => PrimaryKeyEncoding::Dense,
356 }
357 }
358}
359
360#[serde_as]
362#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
363#[serde(default)]
364pub struct PartitionTreeOptions {
365 #[serde_as(as = "DisplayFromStr")]
367 pub index_max_keys_per_shard: usize,
368 #[serde_as(as = "DisplayFromStr")]
370 pub data_freeze_threshold: usize,
371 pub fork_dictionary_bytes: ReadableSize,
373 pub primary_key_encoding: PrimaryKeyEncoding,
375}
376
377impl Default for PartitionTreeOptions {
378 fn default() -> Self {
379 let mut fork_dictionary_bytes = ReadableSize::mb(512);
380 if let Some(total_memory) = get_total_memory_readable() {
381 let adjust_dictionary_bytes = std::cmp::min(
382 total_memory / crate::memtable::partition_tree::DICTIONARY_SIZE_FACTOR,
383 fork_dictionary_bytes,
384 );
385 if adjust_dictionary_bytes.0 > 0 {
386 fork_dictionary_bytes = adjust_dictionary_bytes;
387 }
388 }
389 Self {
390 index_max_keys_per_shard: DEFAULT_MAX_KEYS_PER_SHARD,
391 data_freeze_threshold: DEFAULT_FREEZE_THRESHOLD,
392 fork_dictionary_bytes,
393 primary_key_encoding: PrimaryKeyEncoding::Dense,
394 }
395 }
396}
397
398fn deserialize_ignore_column_ids<'de, D>(deserializer: D) -> Result<Vec<ColumnId>, D::Error>
399where
400 D: Deserializer<'de>,
401{
402 let s: String = Deserialize::deserialize(deserializer)?;
403 let mut column_ids = Vec::new();
404 if s.is_empty() {
405 return Ok(column_ids);
406 }
407 for item in s.split(',') {
408 let column_id = item.parse().map_err(D::Error::custom)?;
409 column_ids.push(column_id);
410 }
411 Ok(column_ids)
412}
413
414fn serialize_ignore_column_ids<S>(column_ids: &[ColumnId], serializer: S) -> Result<S::Ok, S::Error>
415where
416 S: serde::Serializer,
417{
418 let s = column_ids
419 .iter()
420 .map(|id| id.to_string())
421 .collect::<Vec<_>>()
422 .join(",");
423 serializer.serialize_str(&s)
424}
425
426fn options_map_to_value(options: &HashMap<String, String>) -> Value {
430 let map = options
431 .iter()
432 .map(|(key, value)| {
433 if value.eq_ignore_ascii_case("null") {
435 (key.clone(), Value::Null)
436 } else {
437 (key.clone(), Value::from(value.clone()))
438 }
439 })
440 .collect();
441 Value::Object(map)
442}
443
444fn validate_enum_options(
452 options_map: &HashMap<String, String>,
453 enum_tag_key: &str,
454 enum_option_prefixes: &[&str],
455) -> Result<bool> {
456 let mut has_enum_options = false;
457 let mut has_tag = false;
458 for key in options_map.keys() {
459 if key == enum_tag_key {
460 has_tag = true;
461 } else if !has_enum_options
462 && enum_option_prefixes
463 .iter()
464 .any(|prefix| key.starts_with(prefix))
465 {
466 has_enum_options = true;
467 }
468
469 if has_tag && has_enum_options {
470 break;
471 }
472 }
473
474 ensure!(
476 has_tag || !has_enum_options,
477 InvalidRegionOptionsSnafu {
478 reason: format!("missing key {} in options", enum_tag_key),
479 }
480 );
481
482 Ok(has_tag)
483}
484
485#[cfg(test)]
486mod tests {
487 use common_error::ext::ErrorExt;
488 use common_error::status_code::StatusCode;
489 use common_wal::options::KafkaWalOptions;
490
491 use super::*;
492
493 fn make_map(options: &[(&str, &str)]) -> HashMap<String, String> {
494 options
495 .iter()
496 .map(|(k, v)| (k.to_string(), v.to_string()))
497 .collect()
498 }
499
500 #[test]
501 fn test_empty_region_options() {
502 let map = make_map(&[]);
503 let options = RegionOptions::try_from(&map).unwrap();
504 assert_eq!(RegionOptions::default(), options);
505 }
506
507 #[test]
508 fn test_with_ttl() {
509 let map = make_map(&[("ttl", "7d")]);
510 let options = RegionOptions::try_from(&map).unwrap();
511 let expect = RegionOptions {
512 ttl: Some(Duration::from_secs(3600 * 24 * 7).into()),
513 ..Default::default()
514 };
515 assert_eq!(expect, options);
516 }
517
518 #[test]
519 fn test_with_storage() {
520 let map = make_map(&[("storage", "S3")]);
521 let options = RegionOptions::try_from(&map).unwrap();
522 let expect = RegionOptions {
523 storage: Some("S3".to_string()),
524 ..Default::default()
525 };
526 assert_eq!(expect, options);
527 }
528
529 #[test]
530 fn test_without_compaction_type() {
531 let map = make_map(&[
532 ("compaction.twcs.trigger_file_num", "8"),
533 ("compaction.twcs.time_window", "2h"),
534 ]);
535 let err = RegionOptions::try_from(&map).unwrap_err();
536 assert_eq!(StatusCode::InvalidArguments, err.status_code());
537 }
538
539 #[test]
540 fn test_with_compaction_type() {
541 let map = make_map(&[
542 ("compaction.twcs.trigger_file_num", "8"),
543 ("compaction.twcs.time_window", "2h"),
544 ("compaction.type", "twcs"),
545 ]);
546 let options = RegionOptions::try_from(&map).unwrap();
547 let expect = RegionOptions {
548 compaction: CompactionOptions::Twcs(TwcsOptions {
549 trigger_file_num: 8,
550 time_window: Some(Duration::from_secs(3600 * 2)),
551 ..Default::default()
552 }),
553 compaction_override: true,
554 ..Default::default()
555 };
556 assert_eq!(expect, options);
557 }
558
559 #[test]
560 fn test_with_compaction_override_true_without_compaction_type() {
561 let map = make_map(&[(COMPACTION_OVERRIDE, "true")]);
562 let options = RegionOptions::try_from(&map).unwrap();
563 let expect = RegionOptions {
564 compaction_override: true,
565 ..Default::default()
566 };
567 assert_eq!(expect, options);
568 }
569
570 #[test]
571 fn test_with_compaction_override_false_without_compaction_type() {
572 let map = make_map(&[(COMPACTION_OVERRIDE, "false")]);
573 let options = RegionOptions::try_from(&map).unwrap();
574 assert_eq!(RegionOptions::default(), options);
575 }
576
577 #[test]
578 fn test_compaction_twcs_options_still_require_compaction_type_with_override() {
579 let map = make_map(&[
580 (COMPACTION_OVERRIDE, "true"),
581 ("compaction.twcs.time_window", "2h"),
582 ]);
583 let err = RegionOptions::try_from(&map).unwrap_err();
584 assert_eq!(StatusCode::InvalidArguments, err.status_code());
585 }
586
587 fn test_with_wal_options(wal_options: &WalOptions) -> bool {
588 let encoded_wal_options = serde_json::to_string(&wal_options).unwrap();
589 let map = make_map(&[(WAL_OPTIONS_KEY, &encoded_wal_options)]);
590 let got = RegionOptions::try_from(&map).unwrap();
591 let expect = RegionOptions {
592 wal_options: wal_options.clone(),
593 ..Default::default()
594 };
595 expect == got
596 }
597
598 #[test]
599 fn test_with_index() {
600 let map = make_map(&[
601 ("index.inverted_index.ignore_column_ids", "1,2,3"),
602 ("index.inverted_index.segment_row_count", "512"),
603 ]);
604 let options = RegionOptions::try_from(&map).unwrap();
605 let expect = RegionOptions {
606 index_options: IndexOptions {
607 inverted_index: InvertedIndexOptions {
608 ignore_column_ids: vec![1, 2, 3],
609 segment_row_count: 512,
610 },
611 },
612 ..Default::default()
613 };
614 assert_eq!(expect, options);
615 }
616
617 #[test]
619 fn test_with_any_wal_options() {
620 let all_wal_options = [
621 WalOptions::RaftEngine,
622 WalOptions::Kafka(KafkaWalOptions {
623 topic: "test_topic".to_string(),
624 }),
625 ];
626 all_wal_options.iter().all(test_with_wal_options);
627 }
628
629 #[test]
630 fn test_with_memtable() {
631 let map = make_map(&[("memtable.type", "time_series")]);
632 let options = RegionOptions::try_from(&map).unwrap();
633 let expect = RegionOptions {
634 memtable: Some(MemtableOptions::TimeSeries),
635 ..Default::default()
636 };
637 assert_eq!(expect, options);
638
639 let map = make_map(&[("memtable.type", "partition_tree")]);
640 let options = RegionOptions::try_from(&map).unwrap();
641 let expect = RegionOptions {
642 memtable: Some(MemtableOptions::PartitionTree(
643 PartitionTreeOptions::default(),
644 )),
645 ..Default::default()
646 };
647 assert_eq!(expect, options);
648 }
649
650 #[test]
651 fn test_unknown_memtable_type() {
652 let map = make_map(&[("memtable.type", "no_such_memtable")]);
653 let err = RegionOptions::try_from(&map).unwrap_err();
654 assert_eq!(StatusCode::InvalidArguments, err.status_code());
655 }
656
657 #[test]
658 fn test_without_memtable_type() {
659 let map = make_map(&[("memtable.partition_tree.index_max_keys_per_shard", "2048")]);
660 let err = RegionOptions::try_from(&map).unwrap_err();
661 assert_eq!(StatusCode::InvalidArguments, err.status_code());
662 }
663
664 #[test]
665 fn test_with_merge_mode() {
666 let map = make_map(&[("merge_mode", "last_row")]);
667 let options = RegionOptions::try_from(&map).unwrap();
668 assert_eq!(MergeMode::LastRow, options.merge_mode());
669
670 let map = make_map(&[("merge_mode", "last_non_null")]);
671 let options = RegionOptions::try_from(&map).unwrap();
672 assert_eq!(MergeMode::LastNonNull, options.merge_mode());
673
674 let map = make_map(&[("merge_mode", "unknown")]);
675 let err = RegionOptions::try_from(&map).unwrap_err();
676 assert_eq!(StatusCode::InvalidArguments, err.status_code());
677 }
678
679 #[test]
680 fn test_append_mode_allows_last_row_merge_mode() {
681 let map = make_map(&[("append_mode", "true"), ("merge_mode", "last_row")]);
682 let options = RegionOptions::try_from(&map).unwrap();
683 assert!(options.append_mode);
684 assert_eq!(MergeMode::LastRow, options.merge_mode());
685
686 let map = make_map(&[("append_mode", "true"), ("merge_mode", "last_non_null")]);
687 let err = RegionOptions::try_from(&map).unwrap_err();
688 assert_eq!(StatusCode::InvalidArguments, err.status_code());
689 }
690
691 #[test]
692 fn test_with_all() {
693 let wal_options = WalOptions::Kafka(KafkaWalOptions {
694 topic: "test_topic".to_string(),
695 });
696 let map = make_map(&[
697 ("ttl", "7d"),
698 ("compaction.twcs.trigger_file_num", "8"),
699 ("compaction.twcs.max_output_file_size", "1GB"),
700 ("compaction.twcs.time_window", "2h"),
701 ("compaction.type", "twcs"),
702 ("compaction.twcs.remote_compaction", "false"),
703 ("compaction.twcs.fallback_to_local", "true"),
704 ("storage", "S3"),
705 ("append_mode", "false"),
706 ("index.inverted_index.ignore_column_ids", "1,2,3"),
707 ("index.inverted_index.segment_row_count", "512"),
708 (
709 WAL_OPTIONS_KEY,
710 &serde_json::to_string(&wal_options).unwrap(),
711 ),
712 ("memtable.type", "partition_tree"),
713 ("memtable.partition_tree.index_max_keys_per_shard", "2048"),
714 ("memtable.partition_tree.data_freeze_threshold", "2048"),
715 ("memtable.partition_tree.fork_dictionary_bytes", "128M"),
716 ("merge_mode", "last_non_null"),
717 ]);
718 let options = RegionOptions::try_from(&map).unwrap();
719 let expect = RegionOptions {
720 ttl: Some(Duration::from_secs(3600 * 24 * 7).into()),
721 compaction: CompactionOptions::Twcs(TwcsOptions {
722 trigger_file_num: 8,
723 time_window: Some(Duration::from_secs(3600 * 2)),
724 max_output_file_size: Some(ReadableSize::gb(1)),
725 remote_compaction: false,
726 fallback_to_local: true,
727 }),
728 compaction_override: true,
729 storage: Some("S3".to_string()),
730 append_mode: false,
731 wal_options,
732 index_options: IndexOptions {
733 inverted_index: InvertedIndexOptions {
734 ignore_column_ids: vec![1, 2, 3],
735 segment_row_count: 512,
736 },
737 },
738 memtable: Some(MemtableOptions::PartitionTree(PartitionTreeOptions {
739 index_max_keys_per_shard: 2048,
740 data_freeze_threshold: 2048,
741 fork_dictionary_bytes: ReadableSize::mb(128),
742 primary_key_encoding: PrimaryKeyEncoding::Dense,
743 })),
744 merge_mode: Some(MergeMode::LastNonNull),
745 sst_format: None,
746 };
747 assert_eq!(expect, options);
748 }
749
750 #[test]
751 fn test_region_options_serde() {
752 let options = RegionOptions {
753 ttl: Some(Duration::from_secs(3600 * 24 * 7).into()),
754 compaction: CompactionOptions::Twcs(TwcsOptions {
755 trigger_file_num: 8,
756 time_window: Some(Duration::from_secs(3600 * 2)),
757 max_output_file_size: None,
758 remote_compaction: false,
759 fallback_to_local: true,
760 }),
761 compaction_override: false,
762 storage: Some("S3".to_string()),
763 append_mode: false,
764 wal_options: WalOptions::Kafka(KafkaWalOptions {
765 topic: "test_topic".to_string(),
766 }),
767 index_options: IndexOptions {
768 inverted_index: InvertedIndexOptions {
769 ignore_column_ids: vec![1, 2, 3],
770 segment_row_count: 512,
771 },
772 },
773 memtable: Some(MemtableOptions::PartitionTree(PartitionTreeOptions {
774 index_max_keys_per_shard: 2048,
775 data_freeze_threshold: 2048,
776 fork_dictionary_bytes: ReadableSize::mb(128),
777 primary_key_encoding: PrimaryKeyEncoding::Dense,
778 })),
779 merge_mode: Some(MergeMode::LastNonNull),
780 sst_format: None,
781 };
782 let region_options_json_str = serde_json::to_string(&options).unwrap();
783 let got: RegionOptions = serde_json::from_str(®ion_options_json_str).unwrap();
784 assert_eq!(options, got);
785 }
786
787 #[test]
788 fn test_region_options_str_serde() {
789 let region_options_json_str = r#"{
791 "ttl": "7days",
792 "compaction": {
793 "compaction.type": "twcs",
794 "compaction.twcs.trigger_file_num": "8",
795 "compaction.twcs.max_output_file_size": "7MB",
796 "compaction.twcs.time_window": "2h"
797 },
798 "storage": "S3",
799 "append_mode": false,
800 "wal_options": {
801 "wal.provider": "kafka",
802 "wal.kafka.topic": "test_topic"
803 },
804 "index_options": {
805 "index.inverted_index.ignore_column_ids": "",
806 "index.inverted_index.segment_row_count": "512"
807 },
808 "memtable": {
809 "memtable.type": "partition_tree",
810 "memtable.partition_tree.index_max_keys_per_shard": "2048",
811 "memtable.partition_tree.data_freeze_threshold": "2048",
812 "memtable.partition_tree.fork_dictionary_bytes": "128MiB"
813 },
814 "merge_mode": "last_non_null"
815}"#;
816 let got: RegionOptions = serde_json::from_str(region_options_json_str).unwrap();
817 let options = RegionOptions {
818 ttl: Some(Duration::from_secs(3600 * 24 * 7).into()),
819 compaction: CompactionOptions::Twcs(TwcsOptions {
820 trigger_file_num: 8,
821 time_window: Some(Duration::from_secs(3600 * 2)),
822 max_output_file_size: Some(ReadableSize::mb(7)),
823 remote_compaction: false,
824 fallback_to_local: true,
825 }),
826 compaction_override: false,
827 storage: Some("S3".to_string()),
828 append_mode: false,
829 wal_options: WalOptions::Kafka(KafkaWalOptions {
830 topic: "test_topic".to_string(),
831 }),
832 index_options: IndexOptions {
833 inverted_index: InvertedIndexOptions {
834 ignore_column_ids: vec![],
835 segment_row_count: 512,
836 },
837 },
838 memtable: Some(MemtableOptions::PartitionTree(PartitionTreeOptions {
839 index_max_keys_per_shard: 2048,
840 data_freeze_threshold: 2048,
841 fork_dictionary_bytes: ReadableSize::mb(128),
842 primary_key_encoding: PrimaryKeyEncoding::Dense,
843 })),
844 merge_mode: Some(MergeMode::LastNonNull),
845 sst_format: None,
846 };
847 assert_eq!(options, got);
848 }
849}