1use std::collections::{HashMap, HashSet};
18use std::fmt;
19use std::str::FromStr;
20
21use common_base::readable_size::ReadableSize;
22use common_datasource::object_store::oss::is_supported_in_oss;
23use common_datasource::object_store::s3::is_supported_in_s3;
24use common_query::AddColumnLocation;
25use common_time::TimeToLive;
26use common_time::range::TimestampRange;
27use datatypes::data_type::ConcreteDataType;
28use datatypes::prelude::VectorRef;
29use datatypes::schema::{
30 ColumnDefaultConstraint, ColumnSchema, FulltextOptions, Schema, SkippingIndexOptions,
31};
32use greptime_proto::v1::region::compact_request;
33use once_cell::sync::Lazy;
34use serde::{Deserialize, Serialize};
35use store_api::metric_engine_consts::{
36 LOGICAL_TABLE_METADATA_KEY, PHYSICAL_TABLE_METADATA_KEY, is_metric_engine_option_key,
37};
38use store_api::mito_engine_options::{
39 APPEND_MODE_KEY, COMPACTION_TYPE, MEMTABLE_BULK_ENCODE_BYTES_THRESHOLD,
40 MEMTABLE_BULK_ENCODE_ROW_THRESHOLD, MEMTABLE_BULK_MAX_MERGE_GROUPS,
41 MEMTABLE_BULK_MERGE_THRESHOLD, MEMTABLE_TYPE, MERGE_MODE_KEY, SST_FORMAT_KEY,
42 TWCS_FALLBACK_TO_LOCAL, TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW, TWCS_TRIGGER_FILE_NUM,
43 is_mito_engine_option_key,
44};
45use store_api::region_request::{SetRegionOption, UnsetRegionOption};
46
47use crate::error::{ParseTableOptionSnafu, Result};
48use crate::metadata::{TableId, TableVersion};
49use crate::table_reference::TableReference;
50
51mod semantic;
52pub use semantic::*;
53
54pub const FILE_TABLE_META_KEY: &str = "__private.file_table_meta";
55pub const FILE_TABLE_LOCATION_KEY: &str = "location";
56pub const FILE_TABLE_PATTERN_KEY: &str = "pattern";
57pub const FILE_TABLE_FORMAT_KEY: &str = "format";
58
59pub const TABLE_DATA_MODEL: &str = "table_data_model";
60pub const TABLE_DATA_MODEL_TRACE_V1: &str = "greptime_trace_v1";
61
62pub fn is_trace_v1_table(table_info: &crate::metadata::TableInfo) -> bool {
66 table_info
67 .meta
68 .options
69 .extra_options
70 .get(TABLE_DATA_MODEL)
71 .map(|v| v == TABLE_DATA_MODEL_TRACE_V1)
72 .unwrap_or(false)
73}
74
75pub const OTLP_METRIC_COMPAT_KEY: &str = "otlp_metric_compat";
76pub const OTLP_METRIC_COMPAT_PROM: &str = "prom";
77
78pub const VALID_TABLE_OPTION_KEYS: [&str; 14] = [
79 WRITE_BUFFER_SIZE_KEY,
81 TTL_KEY,
82 STORAGE_KEY,
83 COMMENT_KEY,
84 SKIP_WAL_KEY,
85 SST_FORMAT_KEY,
86 FILE_TABLE_LOCATION_KEY,
88 FILE_TABLE_FORMAT_KEY,
89 FILE_TABLE_PATTERN_KEY,
90 PHYSICAL_TABLE_METADATA_KEY,
92 LOGICAL_TABLE_METADATA_KEY,
93 TABLE_DATA_MODEL,
95 OTLP_METRIC_COMPAT_KEY,
96 REPARTITION_COLUMN_HINT_KEY,
97];
98
99pub const DDL_TIMEOUT: &str = "timeout";
100pub const DDL_WAIT: &str = "wait";
101
102pub const VALID_DDL_OPTION_KEYS: [&str; 2] = [DDL_TIMEOUT, DDL_WAIT];
103
104static VALID_DB_OPT_KEYS: Lazy<HashSet<&str>> = Lazy::new(|| {
106 let mut set = HashSet::new();
107 set.insert(TTL_KEY);
108 set.insert(STORAGE_KEY);
109 set.insert(MEMTABLE_TYPE);
110 set.insert(MEMTABLE_BULK_MERGE_THRESHOLD);
111 set.insert(MEMTABLE_BULK_ENCODE_ROW_THRESHOLD);
112 set.insert(MEMTABLE_BULK_ENCODE_BYTES_THRESHOLD);
113 set.insert(MEMTABLE_BULK_MAX_MERGE_GROUPS);
114 set.insert(APPEND_MODE_KEY);
115 set.insert(MERGE_MODE_KEY);
116 set.insert(SKIP_WAL_KEY);
117 set.insert(COMPACTION_TYPE);
118 set.insert(TWCS_FALLBACK_TO_LOCAL);
119 set.insert(TWCS_TIME_WINDOW);
120 set.insert(TWCS_TRIGGER_FILE_NUM);
121 set.insert(TWCS_MAX_OUTPUT_FILE_SIZE);
122 set.insert(SST_FORMAT_KEY);
123 set
124});
125
126pub fn validate_database_option(key: &str) -> bool {
128 VALID_DB_OPT_KEYS.contains(&key)
129}
130
131pub fn validate_table_option(key: &str) -> bool {
133 if is_supported_in_s3(key) {
134 return true;
135 }
136
137 if is_supported_in_oss(key) {
138 return true;
139 }
140
141 if is_mito_engine_option_key(key) {
142 return true;
143 }
144
145 if is_metric_engine_option_key(key) {
146 return true;
147 }
148
149 if is_semantic_option_key(key) {
152 return true;
153 }
154
155 VALID_TABLE_OPTION_KEYS.contains(&key) || VALID_DDL_OPTION_KEYS.contains(&key)
156}
157
158#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
159#[serde(default)]
160pub struct TableOptions {
161 pub write_buffer_size: Option<ReadableSize>,
163 pub ttl: Option<TimeToLive>,
165 pub skip_wal: bool,
167 pub extra_options: HashMap<String, String>,
169}
170
171pub const WRITE_BUFFER_SIZE_KEY: &str = store_api::mito_engine_options::WRITE_BUFFER_SIZE_KEY;
172pub const TTL_KEY: &str = store_api::mito_engine_options::TTL_KEY;
173pub const STORAGE_KEY: &str = "storage";
174pub const COMMENT_KEY: &str = "comment";
175pub const AUTO_CREATE_TABLE_KEY: &str = "auto_create_table";
176pub const SKIP_WAL_KEY: &str = store_api::mito_engine_options::SKIP_WAL_KEY;
177pub const TRACE_TABLE_PARTITIONS_HINT_KEY: &str = "trace_table_partitions";
178pub const REPARTITION_COLUMN_HINT_KEY: &str = "repartition.column.hint";
179
180impl TableOptions {
181 pub fn try_from_iter<T: ToString, U: IntoIterator<Item = (T, T)>>(
182 iter: U,
183 ) -> Result<TableOptions> {
184 let mut options = TableOptions::default();
185
186 let kvs: HashMap<String, String> = iter
187 .into_iter()
188 .map(|(k, v)| (k.to_string(), v.to_string()))
189 .collect();
190
191 if let Some(write_buffer_size) = kvs.get(WRITE_BUFFER_SIZE_KEY) {
192 let size = ReadableSize::from_str(write_buffer_size).map_err(|_| {
193 ParseTableOptionSnafu {
194 key: WRITE_BUFFER_SIZE_KEY,
195 value: write_buffer_size,
196 }
197 .build()
198 })?;
199 options.write_buffer_size = Some(size)
200 }
201
202 if let Some(ttl) = kvs.get(TTL_KEY) {
203 let ttl_value = TimeToLive::from_humantime_or_str(ttl).map_err(|_| {
204 ParseTableOptionSnafu {
205 key: TTL_KEY,
206 value: ttl,
207 }
208 .build()
209 })?;
210 options.ttl = Some(ttl_value);
211 }
212
213 if let Some(skip_wal) = kvs.get(SKIP_WAL_KEY) {
214 options.skip_wal = skip_wal.parse().map_err(|_| {
215 ParseTableOptionSnafu {
216 key: SKIP_WAL_KEY,
217 value: skip_wal,
218 }
219 .build()
220 })?;
221 }
222
223 options.extra_options = HashMap::from_iter(
224 kvs.into_iter()
225 .filter(|(k, _)| k != WRITE_BUFFER_SIZE_KEY && k != TTL_KEY),
226 );
227
228 Ok(options)
229 }
230}
231
232impl fmt::Display for TableOptions {
233 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234 let mut key_vals = vec![];
235 if let Some(size) = self.write_buffer_size {
236 key_vals.push(format!("{}={}", WRITE_BUFFER_SIZE_KEY, size));
237 }
238
239 if let Some(ttl) = self.ttl.map(|ttl| ttl.to_string()) {
240 key_vals.push(format!("{}={}", TTL_KEY, ttl));
241 }
242
243 if self.skip_wal && !self.extra_options.contains_key(SKIP_WAL_KEY) {
244 key_vals.push(format!("{}={}", SKIP_WAL_KEY, self.skip_wal));
245 }
246
247 for (k, v) in &self.extra_options {
248 key_vals.push(format!("{}={}", k, v));
249 }
250
251 write!(f, "{}", key_vals.join(" "))
252 }
253}
254
255impl From<&TableOptions> for HashMap<String, String> {
256 fn from(opts: &TableOptions) -> Self {
257 let mut res = HashMap::with_capacity(3 + opts.extra_options.len());
258 if let Some(write_buffer_size) = opts.write_buffer_size {
259 let _ = res.insert(
260 WRITE_BUFFER_SIZE_KEY.to_string(),
261 write_buffer_size.to_string(),
262 );
263 }
264 if let Some(ttl_str) = opts.ttl.map(|ttl| ttl.to_string()) {
265 let _ = res.insert(TTL_KEY.to_string(), ttl_str);
266 }
267 if opts.skip_wal {
268 let _ = res.insert(SKIP_WAL_KEY.to_string(), true.to_string());
269 }
270 res.extend(opts.extra_options.clone());
271 res
272 }
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct AlterTableRequest {
278 pub catalog_name: String,
279 pub schema_name: String,
280 pub table_name: String,
281 pub table_id: TableId,
282 pub alter_kind: AlterKind,
283 pub table_version: Option<TableVersion>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct AddColumnRequest {
290 pub column_schema: ColumnSchema,
291 pub is_key: bool,
292 pub location: Option<AddColumnLocation>,
293 pub add_if_not_exists: bool,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct ModifyColumnTypeRequest {
300 pub column_name: String,
301 pub target_type: ConcreteDataType,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308pub enum AnnotationFamily {
309 Semantic,
311 RepartitionHint,
313}
314
315impl AnnotationFamily {
316 pub fn namespace(self) -> &'static str {
319 match self {
320 Self::Semantic => SEMANTIC_PREFIX,
321 Self::RepartitionHint => REPARTITION_COLUMN_HINT_KEY,
322 }
323 }
324
325 pub fn of_key(key: &str) -> Option<Self> {
326 if key.starts_with(SEMANTIC_PREFIX) {
327 Some(Self::Semantic)
328 } else if key == REPARTITION_COLUMN_HINT_KEY {
329 Some(Self::RepartitionHint)
330 } else {
331 None
332 }
333 }
334
335 pub fn allows_logical_tables(self) -> bool {
339 match self {
340 Self::Semantic => true,
341 Self::RepartitionHint => false,
342 }
343 }
344
345 pub fn requires_single_key(self) -> bool {
349 matches!(self, Self::RepartitionHint)
350 }
351
352 pub fn mixed_batch_error(self) -> String {
354 match self {
355 Self::Semantic => format!(
356 "`{SEMANTIC_PREFIX}*` options must be altered separately from other table options"
357 ),
358 Self::RepartitionHint => {
359 format!("{REPARTITION_COLUMN_HINT_KEY} must be altered separately")
360 }
361 }
362 }
363}
364
365pub struct AnnotationContext<'a> {
367 pub schema: &'a Schema,
368 pub partition_key_indices: &'a [usize],
369}
370
371#[derive(Debug, Clone, PartialEq, Eq)]
376pub enum AnnotationValidationError {
377 UnknownKey {
378 key: String,
379 },
380 InvalidValue {
381 key: String,
382 value: String,
383 },
384 ColumnNotFound {
385 column: String,
386 },
387 ColumnNotStringForm {
388 key: String,
389 column: String,
390 ty: ConcreteDataType,
391 },
392 NotSingleColumn,
393 PartitionMetadataConflict,
394 TimeIndexConflict,
395}
396
397impl fmt::Display for AnnotationValidationError {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 match self {
400 Self::UnknownKey { key } => write!(f, "unknown semantic option `{key}`"),
401 Self::InvalidValue { key, value } => {
402 write!(f, "invalid value `{value}` for semantic option `{key}`")
403 }
404 Self::ColumnNotFound { column } => write!(f, "column `{column}` not found"),
405 Self::ColumnNotStringForm { key, column, ty } => write!(
406 f,
407 "entity column `{column}` (option `{key}`) has type `{ty}`, \
408 which cannot render as a string"
409 ),
410 Self::NotSingleColumn => write!(
411 f,
412 "{REPARTITION_COLUMN_HINT_KEY} expects exactly one column name"
413 ),
414 Self::PartitionMetadataConflict => write!(
415 f,
416 "cannot set {REPARTITION_COLUMN_HINT_KEY} on a table with partition metadata"
417 ),
418 Self::TimeIndexConflict => write!(
419 f,
420 "cannot set {REPARTITION_COLUMN_HINT_KEY} to the time index column"
421 ),
422 }
423 }
424}
425
426pub(crate) fn validate_and_normalize_annotation(
430 family: AnnotationFamily,
431 cx: &AnnotationContext<'_>,
432 key: &str,
433 value: &str,
434) -> std::result::Result<String, AnnotationValidationError> {
435 match family {
436 AnnotationFamily::Semantic => {
437 if !is_semantic_option_key(key) {
438 return Err(AnnotationValidationError::UnknownKey {
439 key: key.to_string(),
440 });
441 }
442 if !validate_semantic_option(key, value) {
443 return Err(AnnotationValidationError::InvalidValue {
444 key: key.to_string(),
445 value: value.to_string(),
446 });
447 }
448 if parse_entity_option_key(key).is_some() {
449 for column in parse_entity_columns(value) {
450 let schema = cx.schema.column_schema_by_name(&column).ok_or_else(|| {
451 AnnotationValidationError::ColumnNotFound {
452 column: column.clone(),
453 }
454 })?;
455 if !has_stable_string_form(&schema.data_type) {
456 return Err(AnnotationValidationError::ColumnNotStringForm {
457 key: key.to_string(),
458 column,
459 ty: schema.data_type.clone(),
460 });
461 }
462 }
463 }
464 Ok(value.to_string())
465 }
466 AnnotationFamily::RepartitionHint => {
467 let column_name = value.trim();
468 if column_name.is_empty() || column_name.contains(',') {
469 return Err(AnnotationValidationError::NotSingleColumn);
470 }
471 if !cx.partition_key_indices.is_empty() {
472 return Err(AnnotationValidationError::PartitionMetadataConflict);
473 }
474 let column_index = cx.schema.column_index_by_name(column_name).ok_or_else(|| {
475 AnnotationValidationError::ColumnNotFound {
476 column: column_name.to_string(),
477 }
478 })?;
479 if cx.schema.timestamp_index() == Some(column_index) {
480 return Err(AnnotationValidationError::TimeIndexConflict);
481 }
482 Ok(column_name.to_string())
483 }
484 }
485}
486
487pub fn validate_and_normalize_annotation_options(
490 options: &mut TableOptions,
491 cx: &AnnotationContext<'_>,
492) -> std::result::Result<(), AnnotationValidationError> {
493 let mut normalized = Vec::new();
494 for (key, value) in &options.extra_options {
495 let Some(family) = AnnotationFamily::of_key(key) else {
496 continue;
497 };
498 let checked = validate_and_normalize_annotation(family, cx, key, value)?;
499 if checked != *value {
500 normalized.push((key.clone(), checked));
501 }
502 }
503 for (key, value) in normalized {
504 options.extra_options.insert(key, value);
505 }
506 Ok(())
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
510pub enum AlterKind {
511 AddColumns {
512 columns: Vec<AddColumnRequest>,
513 },
514 DropColumns {
515 names: Vec<String>,
516 },
517 ModifyColumnTypes {
518 columns: Vec<ModifyColumnTypeRequest>,
519 },
520 RenameTable {
521 new_table_name: String,
522 },
523 SetTableOptions {
524 options: Vec<SetRegionOption>,
525 },
526 UnsetTableOptions {
527 keys: Vec<UnsetRegionOption>,
528 },
529 SetAnnotations {
530 family: AnnotationFamily,
531 options: Vec<(String, String)>,
532 },
533 UnsetAnnotations {
534 family: AnnotationFamily,
535 keys: Vec<String>,
536 },
537 SetIndexes {
538 options: Vec<SetIndexOption>,
539 },
540 UnsetIndexes {
541 options: Vec<UnsetIndexOption>,
542 },
543 DropDefaults {
544 names: Vec<String>,
545 },
546 SetDefaults {
547 defaults: Vec<SetDefaultRequest>,
548 },
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
552pub struct SetDefaultRequest {
553 pub column_name: String,
554 pub default_constraint: Option<ColumnDefaultConstraint>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
558pub enum SetIndexOption {
559 Fulltext {
560 column_name: String,
561 options: FulltextOptions,
562 },
563 Inverted {
564 column_name: String,
565 },
566 Skipping {
567 column_name: String,
568 options: SkippingIndexOptions,
569 },
570}
571
572impl SetIndexOption {
573 pub fn column_name(&self) -> &str {
575 match self {
576 SetIndexOption::Fulltext { column_name, .. } => column_name,
577 SetIndexOption::Inverted { column_name, .. } => column_name,
578 SetIndexOption::Skipping { column_name, .. } => column_name,
579 }
580 }
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
584pub enum UnsetIndexOption {
585 Fulltext { column_name: String },
586 Inverted { column_name: String },
587 Skipping { column_name: String },
588}
589
590impl UnsetIndexOption {
591 pub fn column_name(&self) -> &str {
593 match self {
594 UnsetIndexOption::Fulltext { column_name, .. } => column_name,
595 UnsetIndexOption::Inverted { column_name, .. } => column_name,
596 UnsetIndexOption::Skipping { column_name, .. } => column_name,
597 }
598 }
599}
600
601#[derive(Debug)]
602pub struct InsertRequest {
603 pub catalog_name: String,
604 pub schema_name: String,
605 pub table_name: String,
606 pub columns_values: HashMap<String, VectorRef>,
607}
608
609#[derive(Debug)]
611pub struct DeleteRequest {
612 pub catalog_name: String,
613 pub schema_name: String,
614 pub table_name: String,
615 pub key_column_values: HashMap<String, VectorRef>,
619}
620
621#[derive(Debug)]
622pub enum CopyDirection {
623 Export,
624 Import,
625}
626
627#[derive(Debug)]
629pub struct CopyTableRequest {
630 pub catalog_name: String,
631 pub schema_name: String,
632 pub table_name: String,
633 pub location: String,
634 pub with: HashMap<String, String>,
635 pub connection: HashMap<String, String>,
636 pub pattern: Option<String>,
637 pub direction: CopyDirection,
638 pub timestamp_range: Option<TimestampRange>,
639 pub limit: Option<u64>,
640}
641
642#[derive(Debug, Clone, Default)]
643pub struct FlushTableRequest {
644 pub catalog_name: String,
645 pub schema_name: String,
646 pub table_name: String,
647}
648
649#[derive(Debug, Clone, Default)]
650pub struct BuildIndexTableRequest {
651 pub catalog_name: String,
652 pub schema_name: String,
653 pub table_name: String,
654}
655
656#[derive(Debug, Clone, PartialEq)]
657pub struct CompactTableRequest {
658 pub catalog_name: String,
659 pub schema_name: String,
660 pub table_name: String,
661 pub compact_options: compact_request::Options,
662 pub parallelism: u32,
663 pub time_range: Option<TimestampRange>,
664}
665
666impl Default for CompactTableRequest {
667 fn default() -> Self {
668 Self {
669 catalog_name: Default::default(),
670 schema_name: Default::default(),
671 table_name: Default::default(),
672 compact_options: compact_request::Options::Regular(Default::default()),
673 parallelism: 1,
674 time_range: None,
675 }
676 }
677}
678
679#[derive(Debug, Clone, Serialize, Deserialize)]
681pub struct TruncateTableRequest {
682 pub catalog_name: String,
683 pub schema_name: String,
684 pub table_name: String,
685 pub table_id: TableId,
686}
687
688impl TruncateTableRequest {
689 pub fn table_ref(&self) -> TableReference<'_> {
690 TableReference {
691 catalog: &self.catalog_name,
692 schema: &self.schema_name,
693 table: &self.table_name,
694 }
695 }
696}
697
698#[derive(Debug, Clone, Default, Deserialize, Serialize)]
699pub struct CopyDatabaseRequest {
700 pub catalog_name: String,
701 pub schema_name: String,
702 pub location: String,
703 pub with: HashMap<String, String>,
704 pub connection: HashMap<String, String>,
705 pub time_range: Option<TimestampRange>,
706}
707
708#[derive(Debug, Clone, Default, Deserialize, Serialize)]
709pub struct CopyQueryToRequest {
710 pub location: String,
711 pub with: HashMap<String, String>,
712 pub connection: HashMap<String, String>,
713}
714
715#[cfg(test)]
716mod tests {
717 use std::time::Duration;
718
719 use super::*;
720
721 #[test]
722 fn test_validate_table_option() {
723 assert!(validate_table_option(FILE_TABLE_LOCATION_KEY));
724 assert!(validate_table_option(FILE_TABLE_FORMAT_KEY));
725 assert!(validate_table_option(FILE_TABLE_PATTERN_KEY));
726 assert!(validate_table_option(TTL_KEY));
727 assert!(validate_table_option(WRITE_BUFFER_SIZE_KEY));
728 assert!(validate_table_option(STORAGE_KEY));
729 assert!(validate_table_option(MEMTABLE_BULK_MERGE_THRESHOLD));
730 assert!(validate_table_option(REPARTITION_COLUMN_HINT_KEY));
731 assert!(!validate_table_option("foo"));
732
733 assert!(validate_table_option(SEMANTIC_SIGNAL_TYPE));
735 assert!(validate_table_option(SEMANTIC_METRIC_TYPE));
736 assert!(!validate_table_option("greptime.semantic.future.key"));
738 assert!(!validate_table_option("greptime.semanticx"));
739 assert!(!validate_table_option(SEMANTIC_PER_TABLE_INDEX_KEY));
740 }
741
742 #[test]
743 fn test_validate_database_option() {
744 assert!(validate_database_option(MEMTABLE_TYPE));
745 assert!(validate_database_option(MEMTABLE_BULK_MERGE_THRESHOLD));
746 assert!(validate_database_option(MEMTABLE_BULK_ENCODE_ROW_THRESHOLD));
747 assert!(validate_database_option(
748 MEMTABLE_BULK_ENCODE_BYTES_THRESHOLD
749 ));
750 assert!(validate_database_option(MEMTABLE_BULK_MAX_MERGE_GROUPS));
751 assert!(!validate_database_option("foo"));
752 }
753
754 #[test]
755 fn test_serialize_table_options() {
756 let options = TableOptions {
757 write_buffer_size: None,
758 ttl: Some(Duration::from_secs(1000).into()),
759 extra_options: HashMap::new(),
760 skip_wal: false,
761 };
762 let serialized = serde_json::to_string(&options).unwrap();
763 let deserialized: TableOptions = serde_json::from_str(&serialized).unwrap();
764 assert_eq!(options, deserialized);
765 }
766
767 #[test]
768 fn test_convert_hashmap_between_table_options() {
769 let options = TableOptions {
770 write_buffer_size: Some(ReadableSize::mb(128)),
771 ttl: Some(Duration::from_secs(1000).into()),
772 extra_options: HashMap::new(),
773 skip_wal: false,
774 };
775 let serialized_map = HashMap::from(&options);
776 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
777 assert_eq!(options, serialized);
778
779 let options = TableOptions {
780 write_buffer_size: None,
781 ttl: None,
782 extra_options: HashMap::from([(SKIP_WAL_KEY.to_string(), true.to_string())]),
783 skip_wal: true,
784 };
785 let serialized_map = HashMap::from(&options);
786 assert_eq!(
787 Some("true"),
788 serialized_map.get(SKIP_WAL_KEY).map(String::as_str)
789 );
790 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
791 assert_eq!(options, serialized);
792
793 let options = TableOptions {
794 write_buffer_size: None,
795 ttl: Default::default(),
796 extra_options: HashMap::new(),
797 skip_wal: false,
798 };
799 let serialized_map = HashMap::from(&options);
800 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
801 assert_eq!(options, serialized);
802
803 let options = TableOptions {
804 write_buffer_size: Some(ReadableSize::mb(128)),
805 ttl: Some(Duration::from_secs(1000).into()),
806 extra_options: HashMap::from([("a".to_string(), "A".to_string())]),
807 skip_wal: false,
808 };
809 let serialized_map = HashMap::from(&options);
810 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
811 assert_eq!(options, serialized);
812
813 let options = TableOptions {
814 extra_options: HashMap::from([(SKIP_WAL_KEY.to_string(), false.to_string())]),
815 skip_wal: false,
816 ..Default::default()
817 };
818 let serialized_map = HashMap::from(&options);
819 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
820 assert_eq!(options, serialized);
821 }
822
823 #[test]
824 fn test_table_options_to_string() {
825 let options = TableOptions {
826 write_buffer_size: Some(ReadableSize::mb(128)),
827 ttl: Some(Duration::from_secs(1000).into()),
828 extra_options: HashMap::new(),
829 skip_wal: false,
830 };
831
832 assert_eq!(
833 "write_buffer_size=128.0MiB ttl=16m 40s",
834 options.to_string()
835 );
836
837 let options = TableOptions {
838 write_buffer_size: Some(ReadableSize::mb(128)),
839 ttl: Some(Duration::from_secs(1000).into()),
840 extra_options: HashMap::from([("a".to_string(), "A".to_string())]),
841 skip_wal: false,
842 };
843
844 assert_eq!(
845 "write_buffer_size=128.0MiB ttl=16m 40s a=A",
846 options.to_string()
847 );
848
849 let options = TableOptions {
850 write_buffer_size: Some(ReadableSize::mb(128)),
851 ttl: Some(Duration::from_secs(1000).into()),
852 extra_options: HashMap::new(),
853 skip_wal: true,
854 };
855 assert_eq!(
856 "write_buffer_size=128.0MiB ttl=16m 40s skip_wal=true",
857 options.to_string()
858 );
859
860 let options = TableOptions {
861 write_buffer_size: None,
862 ttl: None,
863 extra_options: HashMap::from([(SKIP_WAL_KEY.to_string(), "false".to_string())]),
864 skip_wal: false,
865 };
866 assert_eq!("skip_wal=false", options.to_string());
867 }
868}