1use std::collections::{BTreeMap, 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::json::JsonSettings;
29use datatypes::prelude::VectorRef;
30use datatypes::schema::{
31 ColumnDefaultConstraint, ColumnSchema, FulltextOptions, Schema, SkippingIndexOptions,
32};
33use greptime_proto::v1::region::{build_index_request, compact_request};
34use once_cell::sync::Lazy;
35use serde::{Deserialize, Serialize};
36use store_api::metric_engine_consts::{
37 LOGICAL_TABLE_METADATA_KEY, PHYSICAL_TABLE_METADATA_KEY, is_metric_engine_option_key,
38};
39use store_api::mito_engine_options::{
40 APPEND_MODE_KEY, COMPACTION_TYPE, EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING, FloatFieldEncoding,
41 MEMTABLE_BULK_ENCODE_BYTES_THRESHOLD, MEMTABLE_BULK_ENCODE_ROW_THRESHOLD,
42 MEMTABLE_BULK_MAX_MERGE_GROUPS, MEMTABLE_BULK_MERGE_THRESHOLD, MEMTABLE_TYPE, MERGE_MODE_KEY,
43 SST_FORMAT_KEY, TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER, TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM,
44 TWCS_FALLBACK_TO_LOCAL, TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER,
45 TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM, TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW,
46 TWCS_TRIGGER_FILE_NUM, is_mito_engine_option_key, normalize_twcs_trigger_options,
47};
48use store_api::region_request::{SetRegionOption, UnsetRegionOption};
49
50use crate::error::{ConflictingTableOptionsSnafu, ParseTableOptionSnafu, Result};
51use crate::metadata::{TableId, TableVersion};
52
53mod semantic;
54pub use semantic::*;
55
56pub const FILE_TABLE_META_KEY: &str = "__private.file_table_meta";
57pub const RELATIONAL_META_KEY: &str = "__private.relational_meta";
58pub const FILE_TABLE_LOCATION_KEY: &str = "location";
59pub const FILE_TABLE_PATTERN_KEY: &str = "pattern";
60pub const FILE_TABLE_FORMAT_KEY: &str = "format";
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68pub struct RelationalOptions {
69 pub connection: String,
71 pub remote_table: String,
72}
73
74pub const TABLE_DATA_MODEL: &str = "table_data_model";
75pub const TABLE_DATA_MODEL_TRACE_V1: &str = "greptime_trace_v1";
76pub const TABLE_DATA_MODEL_TRACE_V2: &str = "greptime_trace_v2";
78pub const TABLE_DATA_MODEL_LOG_V1: &str = "greptime_log_v1";
81
82pub fn is_trace_table(table_info: &crate::metadata::TableInfo) -> bool {
85 let table_data_model = table_info.meta.options.data_model();
86 matches!(
87 table_data_model,
88 Some(TABLE_DATA_MODEL_TRACE_V1 | TABLE_DATA_MODEL_TRACE_V2)
89 )
90}
91
92pub const OTLP_METRIC_COMPAT_KEY: &str = "otlp_metric_compat";
94pub const OTLP_METRIC_COMPAT_PROM: &str = "prom";
95
96pub const VALID_TABLE_OPTION_KEYS: [&str; 15] = [
97 WRITE_BUFFER_SIZE_KEY,
99 TTL_KEY,
100 STORAGE_KEY,
101 COMMENT_KEY,
102 SKIP_WAL_KEY,
103 SST_FORMAT_KEY,
104 FILE_TABLE_LOCATION_KEY,
106 FILE_TABLE_FORMAT_KEY,
107 FILE_TABLE_PATTERN_KEY,
108 PHYSICAL_TABLE_METADATA_KEY,
110 LOGICAL_TABLE_METADATA_KEY,
111 TABLE_DATA_MODEL,
113 OTLP_METRIC_COMPAT_KEY,
114 REPARTITION_COLUMN_HINT_KEY,
115 REPARTITION_PARTITION_NUM_HINT_KEY,
116];
117
118pub const DDL_TIMEOUT: &str = "timeout";
119pub const DDL_WAIT: &str = "wait";
120
121pub const VALID_DDL_OPTION_KEYS: [&str; 2] = [DDL_TIMEOUT, DDL_WAIT];
122
123pub const INGEST_ROWS_RATE_LIMIT_KEY: &str = "ingest_rows_rate_limit";
125
126static VALID_DB_OPT_KEYS: Lazy<HashSet<&str>> = Lazy::new(|| {
128 let mut set = HashSet::new();
129 set.insert(TTL_KEY);
130 set.insert(STORAGE_KEY);
131 set.insert(MEMTABLE_TYPE);
132 set.insert(MEMTABLE_BULK_MERGE_THRESHOLD);
133 set.insert(MEMTABLE_BULK_ENCODE_ROW_THRESHOLD);
134 set.insert(MEMTABLE_BULK_ENCODE_BYTES_THRESHOLD);
135 set.insert(MEMTABLE_BULK_MAX_MERGE_GROUPS);
136 set.insert(APPEND_MODE_KEY);
137 set.insert(MERGE_MODE_KEY);
138 set.insert(SKIP_WAL_KEY);
139 set.insert(COMPACTION_TYPE);
140 set.insert(TWCS_FALLBACK_TO_LOCAL);
141 set.insert(TWCS_TIME_WINDOW);
142 set.insert(TWCS_TRIGGER_FILE_NUM);
143 set.insert(TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM);
144 set.insert(TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER);
145 set.insert(TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM);
146 set.insert(TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER);
147 set.insert(TWCS_MAX_OUTPUT_FILE_SIZE);
148 set.insert(SST_FORMAT_KEY);
149 set.insert(INGEST_ROWS_RATE_LIMIT_KEY);
150 set
151});
152
153pub fn validate_database_option(key: &str) -> bool {
155 VALID_DB_OPT_KEYS.contains(&key)
156}
157
158pub fn validate_database_option_value(
160 key: &str,
161 value: Option<&str>,
162) -> std::result::Result<(), &'static str> {
163 if key == INGEST_ROWS_RATE_LIMIT_KEY {
164 return value
165 .and_then(|value| value.parse::<u64>().ok())
166 .map(|_| ())
167 .ok_or("expected a non-negative integer fitting in u64");
168 }
169 let (minimum, constraint) = match key {
170 TWCS_TRIGGER_FILE_NUM
171 | TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM
172 | TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM => {
173 (0, "expected a non-negative integer fitting in usize")
174 }
175 TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER | TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER => {
176 (2, "expected an integer greater than or equal to 2")
177 }
178 _ => return Ok(()),
179 };
180 if value
181 .and_then(|value| value.parse::<usize>().ok())
182 .is_some_and(|files| files >= minimum)
183 {
184 Ok(())
185 } else {
186 Err(constraint)
187 }
188}
189
190pub fn validate_table_option(key: &str) -> bool {
192 if is_supported_in_s3(key) {
193 return true;
194 }
195
196 if is_supported_in_oss(key) {
197 return true;
198 }
199
200 if is_mito_engine_option_key(key) {
201 return true;
202 }
203
204 if is_metric_engine_option_key(key) {
205 return true;
206 }
207
208 if is_semantic_option_key(key) {
211 return true;
212 }
213
214 VALID_TABLE_OPTION_KEYS.contains(&key) || VALID_DDL_OPTION_KEYS.contains(&key)
215}
216
217#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
218#[serde(default)]
219pub struct TableOptions {
220 pub write_buffer_size: Option<ReadableSize>,
222 pub ttl: Option<TimeToLive>,
224 pub skip_wal: bool,
226 pub extra_options: HashMap<String, String>,
228}
229
230pub const WRITE_BUFFER_SIZE_KEY: &str = store_api::mito_engine_options::WRITE_BUFFER_SIZE_KEY;
231pub const TTL_KEY: &str = store_api::mito_engine_options::TTL_KEY;
232pub const STORAGE_KEY: &str = "storage";
233pub const COMMENT_KEY: &str = "comment";
234pub const AUTO_CREATE_TABLE_KEY: &str = "auto_create_table";
235pub const SKIP_WAL_KEY: &str = store_api::mito_engine_options::SKIP_WAL_KEY;
236pub const TRACE_TABLE_PARTITIONS_HINT_KEY: &str = "trace_table_partitions";
237pub const REPARTITION_COLUMN_HINT_KEY: &str = "repartition.column.hint";
238
239pub const REPARTITION_PARTITION_NUM_HINT_KEY: &str = "repartition.partition.num.hint";
241
242impl TableOptions {
243 pub fn data_model(&self) -> Option<&str> {
245 self.extra_options.get(TABLE_DATA_MODEL).map(String::as_str)
246 }
247
248 pub fn try_from_iter<T: ToString, U: IntoIterator<Item = (T, T)>>(
249 iter: U,
250 ) -> Result<TableOptions> {
251 let mut options = TableOptions::default();
252
253 let mut kvs: HashMap<String, String> = iter
254 .into_iter()
255 .map(|(k, v)| (k.to_string(), v.to_string()))
256 .collect();
257
258 normalize_twcs_trigger_options(&mut kvs).map_err(|conflict| {
259 ConflictingTableOptionsSnafu {
260 first_key: TWCS_TRIGGER_FILE_NUM,
261 first_value: conflict.legacy_value,
262 second_key: TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM,
263 second_value: conflict.canonical_value,
264 }
265 .build()
266 })?;
267
268 if let Some(write_buffer_size) = kvs.get(WRITE_BUFFER_SIZE_KEY) {
269 let size = ReadableSize::from_str(write_buffer_size).map_err(|_| {
270 ParseTableOptionSnafu {
271 key: WRITE_BUFFER_SIZE_KEY,
272 value: write_buffer_size,
273 }
274 .build()
275 })?;
276 options.write_buffer_size = Some(size)
277 }
278
279 if let Some(ttl) = kvs.get(TTL_KEY) {
280 let ttl_value = TimeToLive::from_humantime_or_str(ttl).map_err(|_| {
281 ParseTableOptionSnafu {
282 key: TTL_KEY,
283 value: ttl,
284 }
285 .build()
286 })?;
287 options.ttl = Some(ttl_value);
288 }
289
290 if let Some(encoding) = kvs.get(EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING) {
291 encoding.parse::<FloatFieldEncoding>().map_err(|_| {
292 ParseTableOptionSnafu {
293 key: EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING,
294 value: encoding,
295 }
296 .build()
297 })?;
298 }
299
300 if let Some(skip_wal) = kvs.get(SKIP_WAL_KEY) {
301 options.skip_wal = skip_wal.parse().map_err(|_| {
302 ParseTableOptionSnafu {
303 key: SKIP_WAL_KEY,
304 value: skip_wal,
305 }
306 .build()
307 })?;
308 }
309
310 options.extra_options = HashMap::from_iter(
311 kvs.into_iter()
312 .filter(|(k, _)| k != WRITE_BUFFER_SIZE_KEY && k != TTL_KEY),
313 );
314
315 Ok(options)
316 }
317}
318
319const CREDENTIAL_OPTION_KEYS: [&str; 4] = [
321 "access_key_id",
322 "secret_access_key",
323 "session_token",
324 "access_key_secret",
325];
326
327pub const REDACTED_OPTION_VALUE: &str = "******";
329
330pub fn is_credential_option(key: &str) -> bool {
331 CREDENTIAL_OPTION_KEYS.contains(&key)
332}
333
334impl fmt::Display for TableOptions {
337 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
338 let mut key_vals = vec![];
339 if let Some(size) = self.write_buffer_size {
340 key_vals.push(format!("{}={}", WRITE_BUFFER_SIZE_KEY, size));
341 }
342
343 if let Some(ttl) = self.ttl.map(|ttl| ttl.to_string()) {
344 key_vals.push(format!("{}={}", TTL_KEY, ttl));
345 }
346
347 if self.skip_wal && !self.extra_options.contains_key(SKIP_WAL_KEY) {
348 key_vals.push(format!("{}={}", SKIP_WAL_KEY, self.skip_wal));
349 }
350
351 let extra_options: BTreeMap<_, _> = self.extra_options.iter().collect();
353 for (k, v) in extra_options {
354 let v = if is_credential_option(k) {
355 REDACTED_OPTION_VALUE
356 } else {
357 v
358 };
359 key_vals.push(format!("{}={}", k, v));
360 }
361
362 write!(f, "{}", key_vals.join(" "))
363 }
364}
365
366impl From<&TableOptions> for HashMap<String, String> {
367 fn from(opts: &TableOptions) -> Self {
368 let mut res = HashMap::with_capacity(3 + opts.extra_options.len());
369 if let Some(write_buffer_size) = opts.write_buffer_size {
370 let _ = res.insert(
371 WRITE_BUFFER_SIZE_KEY.to_string(),
372 write_buffer_size.to_string(),
373 );
374 }
375 if let Some(ttl_str) = opts.ttl.map(|ttl| ttl.to_string()) {
376 let _ = res.insert(TTL_KEY.to_string(), ttl_str);
377 }
378 if opts.skip_wal {
379 let _ = res.insert(SKIP_WAL_KEY.to_string(), true.to_string());
380 }
381 res.extend(opts.extra_options.clone());
382 res
383 }
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct AlterTableRequest {
389 pub catalog_name: String,
390 pub schema_name: String,
391 pub table_name: String,
392 pub table_id: TableId,
393 pub alter_kind: AlterKind,
394 pub table_version: Option<TableVersion>,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct AddColumnRequest {
401 pub column_schema: ColumnSchema,
402 pub is_key: bool,
403 pub location: Option<AddColumnLocation>,
404 pub add_if_not_exists: bool,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct ModifyColumnTypeRequest {
411 pub column_name: String,
412 pub target_type: ConcreteDataType,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct SetJsonSettingsRequest {
418 pub column_name: String,
419 pub settings: JsonSettings,
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
426pub enum AnnotationFamily {
427 Semantic,
429 RepartitionHint,
431}
432
433impl AnnotationFamily {
434 pub fn namespace(self) -> &'static str {
436 match self {
437 Self::Semantic => SEMANTIC_PREFIX,
438 Self::RepartitionHint => "repartition.",
439 }
440 }
441
442 pub fn of_key(key: &str) -> Option<Self> {
443 if key.starts_with(SEMANTIC_PREFIX) {
444 Some(Self::Semantic)
445 } else if matches!(
446 key,
447 REPARTITION_COLUMN_HINT_KEY | REPARTITION_PARTITION_NUM_HINT_KEY
448 ) {
449 Some(Self::RepartitionHint)
450 } else {
451 None
452 }
453 }
454
455 pub fn allows_logical_tables(self) -> bool {
459 match self {
460 Self::Semantic => true,
461 Self::RepartitionHint => false,
462 }
463 }
464
465 pub fn requires_unique_keys(self) -> bool {
467 matches!(self, Self::RepartitionHint)
468 }
469
470 pub fn mixed_batch_error(self) -> String {
472 match self {
473 Self::Semantic => format!(
474 "`{SEMANTIC_PREFIX}*` options must be altered separately from other table options"
475 ),
476 Self::RepartitionHint => {
477 "repartition hints must be altered separately from other table options".to_string()
478 }
479 }
480 }
481}
482
483#[derive(Debug, Clone, PartialEq, Eq)]
485pub enum AnnotationKeyError {
486 MixedFamilies { family: AnnotationFamily },
487 DuplicateKey,
488}
489
490impl fmt::Display for AnnotationKeyError {
491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492 match self {
493 Self::MixedFamilies { family } => f.write_str(&family.mixed_batch_error()),
494 Self::DuplicateKey => f.write_str("duplicate repartition hint keys"),
495 }
496 }
497}
498
499pub fn validate_annotation_keys<'a>(
505 keys: impl IntoIterator<Item = &'a str>,
506) -> std::result::Result<Option<AnnotationFamily>, AnnotationKeyError> {
507 let mut keys = keys.into_iter();
508 let Some(first) = keys.next() else {
509 return Ok(None);
510 };
511 let family = AnnotationFamily::of_key(first);
512 let reject_duplicates = family.is_some_and(|family| family.requires_unique_keys());
513 let mut seen = HashSet::new();
514 if reject_duplicates {
515 seen.insert(first);
516 }
517 for key in keys {
518 let this = AnnotationFamily::of_key(key);
519 if this != family
520 && let Some(family) = family.or(this)
521 {
522 return Err(AnnotationKeyError::MixedFamilies { family });
523 }
524 if reject_duplicates && !seen.insert(key) {
525 return Err(AnnotationKeyError::DuplicateKey);
526 }
527 }
528 Ok(family)
529}
530
531pub struct AnnotationContext<'a> {
533 pub data_model: Option<&'a str>,
534 pub schema: &'a Schema,
535 pub partition_key_indices: &'a [usize],
536}
537
538#[derive(Debug, Clone, PartialEq, Eq)]
543pub enum AnnotationValidationError {
544 UnknownKey {
545 key: String,
546 },
547 InvalidValue {
548 key: String,
549 value: String,
550 },
551 ColumnNotFound {
552 column: String,
553 },
554 ColumnNotStringForm {
555 key: String,
556 column: String,
557 ty: ConcreteDataType,
558 },
559 InvalidPartitionNumHint {
560 value: String,
561 },
562 NotSingleColumn,
563 PartitionMetadataConflict,
564 TimeIndexConflict,
565}
566
567impl fmt::Display for AnnotationValidationError {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 match self {
570 Self::UnknownKey { key } => write!(f, "unknown semantic option `{key}`"),
571 Self::InvalidValue { key, value } => {
572 write!(f, "invalid value `{value}` for semantic option `{key}`")
573 }
574 Self::ColumnNotFound { column } => write!(f, "column `{column}` not found"),
575 Self::ColumnNotStringForm { key, column, ty } => write!(
576 f,
577 "entity column `{column}` (option `{key}`) has type `{ty}`, \
578 which cannot render as a string"
579 ),
580 Self::InvalidPartitionNumHint { value } => write!(
581 f,
582 "{REPARTITION_PARTITION_NUM_HINT_KEY} expects a positive integer within u32 range, got `{value}`"
583 ),
584 Self::NotSingleColumn => write!(
585 f,
586 "{REPARTITION_COLUMN_HINT_KEY} expects exactly one column name"
587 ),
588 Self::PartitionMetadataConflict => write!(
589 f,
590 "cannot set {REPARTITION_COLUMN_HINT_KEY} on a table with partition metadata"
591 ),
592 Self::TimeIndexConflict => write!(
593 f,
594 "cannot set {REPARTITION_COLUMN_HINT_KEY} to the time index column"
595 ),
596 }
597 }
598}
599
600pub(crate) fn validate_and_normalize_annotation(
604 family: AnnotationFamily,
605 cx: &AnnotationContext<'_>,
606 key: &str,
607 value: &str,
608) -> std::result::Result<String, AnnotationValidationError> {
609 match family {
610 AnnotationFamily::Semantic => {
611 if !is_semantic_option_key(key) {
612 return Err(AnnotationValidationError::UnknownKey {
613 key: key.to_string(),
614 });
615 }
616 if !validate_semantic_option(key, value) {
617 return Err(AnnotationValidationError::InvalidValue {
618 key: key.to_string(),
619 value: value.to_string(),
620 });
621 }
622 if parse_entity_option_key(key).is_some() {
623 for column in parse_entity_columns(value) {
624 if trace_v2_attribute(cx.schema, cx.data_model, &column).is_some() {
625 continue;
626 }
627 let schema = cx.schema.column_schema_by_name(&column).ok_or_else(|| {
628 AnnotationValidationError::ColumnNotFound {
629 column: column.clone(),
630 }
631 })?;
632 if !has_stable_string_form(&schema.data_type) {
633 return Err(AnnotationValidationError::ColumnNotStringForm {
634 key: key.to_string(),
635 column,
636 ty: schema.data_type.clone(),
637 });
638 }
639 }
640 }
641 Ok(value.to_string())
642 }
643 AnnotationFamily::RepartitionHint if key == REPARTITION_PARTITION_NUM_HINT_KEY => {
644 let value = value.trim();
645 if !matches!(value.parse::<u32>(), Ok(1..)) {
646 return Err(AnnotationValidationError::InvalidPartitionNumHint {
647 value: value.to_string(),
648 });
649 }
650 Ok(value.to_string())
651 }
652 AnnotationFamily::RepartitionHint => {
653 let column_name = value.trim();
654 if column_name.is_empty() || column_name.contains(',') {
655 return Err(AnnotationValidationError::NotSingleColumn);
656 }
657 if !cx.partition_key_indices.is_empty() {
658 return Err(AnnotationValidationError::PartitionMetadataConflict);
659 }
660 let column_index = cx.schema.column_index_by_name(column_name).ok_or_else(|| {
661 AnnotationValidationError::ColumnNotFound {
662 column: column_name.to_string(),
663 }
664 })?;
665 if cx.schema.timestamp_index() == Some(column_index) {
666 return Err(AnnotationValidationError::TimeIndexConflict);
667 }
668 Ok(column_name.to_string())
669 }
670 }
671}
672
673pub fn validate_and_normalize_annotation_options(
676 options: &mut TableOptions,
677 schema: &Schema,
678 partition_key_indices: &[usize],
679) -> std::result::Result<(), AnnotationValidationError> {
680 let cx = AnnotationContext {
681 data_model: options.data_model(),
682 schema,
683 partition_key_indices,
684 };
685 let mut normalized = Vec::new();
686 for (key, value) in &options.extra_options {
687 let Some(family) = AnnotationFamily::of_key(key) else {
688 continue;
689 };
690 let checked = validate_and_normalize_annotation(family, &cx, key, value)?;
691 if checked != *value {
692 normalized.push((key.clone(), checked));
693 }
694 }
695 for (key, value) in normalized {
696 options.extra_options.insert(key, value);
697 }
698 Ok(())
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
702pub enum AlterKind {
703 AddColumns {
704 columns: Vec<AddColumnRequest>,
705 },
706 DropColumns {
707 names: Vec<String>,
708 },
709 ModifyColumnTypes {
710 columns: Vec<ModifyColumnTypeRequest>,
711 },
712 SetJsonSettings {
713 request: SetJsonSettingsRequest,
714 },
715 RenameTable {
716 new_table_name: String,
717 },
718 SetTableOptions {
719 options: Vec<SetRegionOption>,
720 },
721 UnsetTableOptions {
722 keys: Vec<UnsetRegionOption>,
723 },
724 SetAnnotations {
725 family: AnnotationFamily,
726 options: Vec<(String, String)>,
727 },
728 UnsetAnnotations {
729 family: AnnotationFamily,
730 keys: Vec<String>,
731 },
732 SetIndexes {
733 options: Vec<SetIndexOption>,
734 },
735 UnsetIndexes {
736 options: Vec<UnsetIndexOption>,
737 },
738 DropDefaults {
739 names: Vec<String>,
740 },
741 SetDefaults {
742 defaults: Vec<SetDefaultRequest>,
743 },
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize)]
747pub struct SetDefaultRequest {
748 pub column_name: String,
749 pub default_constraint: Option<ColumnDefaultConstraint>,
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize)]
753pub enum SetIndexOption {
754 Fulltext {
755 column_name: String,
756 options: FulltextOptions,
757 },
758 Inverted {
759 column_name: String,
760 },
761 Skipping {
762 column_name: String,
763 options: SkippingIndexOptions,
764 },
765}
766
767impl SetIndexOption {
768 pub fn column_name(&self) -> &str {
770 match self {
771 SetIndexOption::Fulltext { column_name, .. } => column_name,
772 SetIndexOption::Inverted { column_name, .. } => column_name,
773 SetIndexOption::Skipping { column_name, .. } => column_name,
774 }
775 }
776}
777
778#[derive(Debug, Clone, Serialize, Deserialize)]
779pub enum UnsetIndexOption {
780 Fulltext { column_name: String },
781 Inverted { column_name: String },
782 Skipping { column_name: String },
783}
784
785impl UnsetIndexOption {
786 pub fn column_name(&self) -> &str {
788 match self {
789 UnsetIndexOption::Fulltext { column_name, .. } => column_name,
790 UnsetIndexOption::Inverted { column_name, .. } => column_name,
791 UnsetIndexOption::Skipping { column_name, .. } => column_name,
792 }
793 }
794}
795
796#[derive(Debug)]
797pub struct InsertRequest {
798 pub catalog_name: String,
799 pub schema_name: String,
800 pub table_name: String,
801 pub columns_values: HashMap<String, VectorRef>,
802 pub skip_wal: bool,
804}
805
806#[derive(Debug)]
808pub struct DeleteRequest {
809 pub catalog_name: String,
810 pub schema_name: String,
811 pub table_name: String,
812 pub key_column_values: HashMap<String, VectorRef>,
816}
817
818#[derive(Debug)]
819pub enum CopyDirection {
820 Export,
821 Import,
822}
823
824#[derive(Debug)]
826pub struct CopyTableRequest {
827 pub catalog_name: String,
828 pub schema_name: String,
829 pub table_name: String,
830 pub location: String,
831 pub with: HashMap<String, String>,
832 pub connection: HashMap<String, String>,
833 pub pattern: Option<String>,
834 pub direction: CopyDirection,
835 pub timestamp_range: Option<TimestampRange>,
836 pub limit: Option<u64>,
837}
838
839#[derive(Debug, Clone, Default)]
840pub struct FlushTableRequest {
841 pub catalog_name: String,
842 pub schema_name: String,
843 pub table_name: String,
844}
845
846#[derive(Debug, Clone, Default)]
847pub struct BuildIndexTableRequest {
848 pub options: Option<build_index_request::Options>,
850 pub catalog_name: String,
851 pub schema_name: String,
852 pub table_name: String,
853}
854
855#[derive(Debug, Clone, PartialEq)]
856pub struct CompactTableRequest {
857 pub catalog_name: String,
858 pub schema_name: String,
859 pub table_name: String,
860 pub compact_options: compact_request::Options,
861 pub parallelism: u32,
862 pub time_range: Option<TimestampRange>,
863}
864
865impl Default for CompactTableRequest {
866 fn default() -> Self {
867 Self {
868 catalog_name: Default::default(),
869 schema_name: Default::default(),
870 table_name: Default::default(),
871 compact_options: compact_request::Options::Regular(Default::default()),
872 parallelism: 1,
873 time_range: None,
874 }
875 }
876}
877
878#[derive(Debug, Clone, Default, Deserialize, Serialize)]
879pub struct CopyDatabaseRequest {
880 pub catalog_name: String,
881 pub schema_name: String,
882 pub location: String,
883 pub with: HashMap<String, String>,
884 pub connection: HashMap<String, String>,
885 pub time_range: Option<TimestampRange>,
886}
887
888#[derive(Debug, Clone, Default, Deserialize, Serialize)]
889pub struct CopyQueryToRequest {
890 pub location: String,
891 pub with: HashMap<String, String>,
892 pub connection: HashMap<String, String>,
893}
894
895#[cfg(test)]
896mod tests {
897 use std::time::Duration;
898
899 use common_error::ext::ErrorExt;
900 use common_error::status_code::StatusCode;
901
902 use super::*;
903
904 #[test]
905 fn test_validate_annotation_keys() {
906 let column = REPARTITION_COLUMN_HINT_KEY;
907 let count = REPARTITION_PARTITION_NUM_HINT_KEY;
908 for (keys, expected) in [
909 (vec![], None),
910 (vec![TTL_KEY, TTL_KEY], None),
911 (vec!["repartition.unknown.hint"], None),
912 (vec![column], Some(AnnotationFamily::RepartitionHint)),
913 (vec![count], Some(AnnotationFamily::RepartitionHint)),
914 (vec![column, count], Some(AnnotationFamily::RepartitionHint)),
915 (vec![count, column], Some(AnnotationFamily::RepartitionHint)),
916 (
917 vec!["greptime.semantic.source", "greptime.semantic.source"],
918 Some(AnnotationFamily::Semantic),
919 ),
920 ] {
921 assert_eq!(validate_annotation_keys(keys), Ok(expected));
922 }
923 for keys in [
924 vec![column, column],
925 vec![count, count],
926 vec![column, count, column],
927 ] {
928 assert_eq!(
929 validate_annotation_keys(keys),
930 Err(AnnotationKeyError::DuplicateKey)
931 );
932 }
933 for keys in [
934 vec![column, TTL_KEY],
935 vec![TTL_KEY, count],
936 vec![column, "greptime.semantic.source"],
937 ] {
938 assert_eq!(
939 validate_annotation_keys(keys),
940 Err(AnnotationKeyError::MixedFamilies {
941 family: AnnotationFamily::RepartitionHint,
942 })
943 );
944 }
945 }
946
947 #[test]
948 fn test_validate_table_option() {
949 assert!(validate_table_option(FILE_TABLE_LOCATION_KEY));
950 assert!(validate_table_option(FILE_TABLE_FORMAT_KEY));
951 assert!(validate_table_option(FILE_TABLE_PATTERN_KEY));
952 assert!(validate_table_option(TTL_KEY));
953 assert!(validate_table_option(WRITE_BUFFER_SIZE_KEY));
954 assert!(validate_table_option(STORAGE_KEY));
955 assert!(validate_table_option(MEMTABLE_BULK_MERGE_THRESHOLD));
956 assert!(validate_table_option(EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING));
957 assert!(validate_table_option(REPARTITION_COLUMN_HINT_KEY));
958 assert!(validate_table_option(REPARTITION_PARTITION_NUM_HINT_KEY));
959 assert_eq!(AnnotationFamily::of_key("repartition.unknown.hint"), None);
960 assert!(!validate_table_option("foo"));
961
962 assert!(validate_table_option(SEMANTIC_SIGNAL_TYPE));
964 assert!(validate_table_option(SEMANTIC_METRIC_TYPE));
965 assert!(!validate_table_option("greptime.semantic.future.key"));
967 assert!(!validate_table_option("greptime.semanticx"));
968 assert!(!validate_table_option(SEMANTIC_PER_TABLE_INDEX_KEY));
969 }
970
971 #[test]
972 fn test_validate_database_option() {
973 assert!(validate_database_option(MEMTABLE_TYPE));
974 assert!(validate_database_option(MEMTABLE_BULK_MERGE_THRESHOLD));
975 assert!(validate_database_option(MEMTABLE_BULK_ENCODE_ROW_THRESHOLD));
976 assert!(validate_database_option(
977 MEMTABLE_BULK_ENCODE_BYTES_THRESHOLD
978 ));
979 assert!(validate_database_option(MEMTABLE_BULK_MAX_MERGE_GROUPS));
980 assert!(validate_database_option(
981 "compaction.twcs.active_window.trigger_file_num"
982 ));
983 assert!(validate_database_option(
984 "compaction.twcs.active_window.l1_merge_trigger"
985 ));
986 assert!(validate_database_option(
987 "compaction.twcs.inactive_window.trigger_file_num"
988 ));
989 assert!(validate_database_option(
990 "compaction.twcs.inactive_window.l1_merge_trigger"
991 ));
992 assert!(validate_database_option(INGEST_ROWS_RATE_LIMIT_KEY));
993 assert!(validate_database_option("ingest_rows_rate_limit"));
994 assert!(!validate_database_option("foo"));
995 }
996
997 #[test]
998 fn test_parse_float_field_encoding_option() {
999 let options = TableOptions::try_from_iter([(
1000 EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING,
1001 "byte_stream_split",
1002 )])
1003 .unwrap();
1004 assert_eq!(
1005 options
1006 .extra_options
1007 .get(EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING),
1008 Some(&"byte_stream_split".to_string())
1009 );
1010 assert!(
1011 TableOptions::try_from_iter([(EXPERIMENTAL_SST_FLOAT_FIELD_ENCODING, "invalid",)])
1012 .is_err()
1013 );
1014 }
1015
1016 #[test]
1017 fn test_database_trigger_value_boundaries() {
1018 let maximum = usize::MAX.to_string();
1019 let overflow = format!("{maximum}0");
1020 for (key, minimum) in [
1021 (TWCS_TRIGGER_FILE_NUM, 0),
1022 (TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM, 0),
1023 (TWCS_INACTIVE_WINDOW_TRIGGER_FILE_NUM, 0),
1024 (TWCS_ACTIVE_WINDOW_L1_MERGE_TRIGGER, 2),
1025 (TWCS_INACTIVE_WINDOW_L1_MERGE_TRIGGER, 2),
1026 ] {
1027 for invalid in [
1028 None,
1029 Some(""),
1030 Some("invalid"),
1031 Some("-1"),
1032 Some(overflow.as_str()),
1033 ] {
1034 assert!(
1035 validate_database_option_value(key, invalid).is_err(),
1036 "{key}: {invalid:?}"
1037 );
1038 }
1039 for valid in ["2", maximum.as_str()] {
1040 assert!(
1041 validate_database_option_value(key, Some(valid)).is_ok(),
1042 "{key}: {valid}"
1043 );
1044 }
1045 for boundary in ["0", "1"] {
1046 assert_eq!(
1047 validate_database_option_value(key, Some(boundary)).is_ok(),
1048 minimum == 0,
1049 "{key}: {boundary}"
1050 );
1051 }
1052 }
1053 }
1054
1055 #[test]
1056 fn test_database_ingest_rate_limit_value_boundaries() {
1057 for invalid in [
1058 None,
1059 Some(""),
1060 Some("abc"),
1061 Some("1000/s"),
1062 Some("-1"),
1063 Some("1.5"),
1064 Some("18446744073709551616"),
1065 ] {
1066 assert!(validate_database_option_value(INGEST_ROWS_RATE_LIMIT_KEY, invalid).is_err());
1067 }
1068 let maximum = u64::MAX.to_string();
1069 for valid in ["0", "1", maximum.as_str()] {
1070 assert!(
1071 validate_database_option_value(INGEST_ROWS_RATE_LIMIT_KEY, Some(valid)).is_ok()
1072 );
1073 }
1074 }
1075
1076 #[test]
1077 fn test_serialize_table_options() {
1078 let options = TableOptions {
1079 write_buffer_size: None,
1080 ttl: Some(Duration::from_secs(1000).into()),
1081 extra_options: HashMap::new(),
1082 skip_wal: false,
1083 };
1084 let serialized = serde_json::to_string(&options).unwrap();
1085 let deserialized: TableOptions = serde_json::from_str(&serialized).unwrap();
1086 assert_eq!(options, deserialized);
1087 }
1088
1089 #[test]
1090 fn test_convert_hashmap_between_table_options() {
1091 let options = TableOptions {
1092 write_buffer_size: Some(ReadableSize::mb(128)),
1093 ttl: Some(Duration::from_secs(1000).into()),
1094 extra_options: HashMap::new(),
1095 skip_wal: false,
1096 };
1097 let serialized_map = HashMap::from(&options);
1098 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
1099 assert_eq!(options, serialized);
1100
1101 let options = TableOptions {
1102 write_buffer_size: None,
1103 ttl: None,
1104 extra_options: HashMap::from([(SKIP_WAL_KEY.to_string(), true.to_string())]),
1105 skip_wal: true,
1106 };
1107 let serialized_map = HashMap::from(&options);
1108 assert_eq!(
1109 Some("true"),
1110 serialized_map.get(SKIP_WAL_KEY).map(String::as_str)
1111 );
1112 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
1113 assert_eq!(options, serialized);
1114
1115 let options = TableOptions {
1116 write_buffer_size: None,
1117 ttl: Default::default(),
1118 extra_options: HashMap::new(),
1119 skip_wal: false,
1120 };
1121 let serialized_map = HashMap::from(&options);
1122 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
1123 assert_eq!(options, serialized);
1124
1125 let options = TableOptions {
1126 write_buffer_size: Some(ReadableSize::mb(128)),
1127 ttl: Some(Duration::from_secs(1000).into()),
1128 extra_options: HashMap::from([("a".to_string(), "A".to_string())]),
1129 skip_wal: false,
1130 };
1131 let serialized_map = HashMap::from(&options);
1132 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
1133 assert_eq!(options, serialized);
1134
1135 let options = TableOptions {
1136 extra_options: HashMap::from([(SKIP_WAL_KEY.to_string(), false.to_string())]),
1137 skip_wal: false,
1138 ..Default::default()
1139 };
1140 let serialized_map = HashMap::from(&options);
1141 let serialized = TableOptions::try_from_iter(&serialized_map).unwrap();
1142 assert_eq!(options, serialized);
1143 }
1144
1145 #[test]
1146 fn test_table_options_normalizes_twcs_trigger_aliases() {
1147 for options in [
1148 vec![(TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM, "4")],
1149 vec![
1150 (TWCS_TRIGGER_FILE_NUM, "4"),
1151 (TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM, "4"),
1152 ],
1153 ] {
1154 let table_options = TableOptions::try_from_iter(options).unwrap();
1155 assert_eq!(
1156 HashMap::from([(TWCS_TRIGGER_FILE_NUM.to_string(), "4".to_string())]),
1157 table_options.extra_options
1158 );
1159 }
1160 }
1161
1162 #[test]
1163 fn test_table_options_rejects_conflicting_twcs_trigger_aliases() {
1164 let error = TableOptions::try_from_iter([
1165 (TWCS_TRIGGER_FILE_NUM, "4"),
1166 (TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM, "8"),
1167 ])
1168 .unwrap_err();
1169 assert_eq!(StatusCode::InvalidArguments, error.status_code());
1170 assert_eq!(
1171 "Conflicting table options: compaction.twcs.trigger_file_num=4 and compaction.twcs.active_window.trigger_file_num=8",
1172 error.to_string()
1173 );
1174 }
1175
1176 #[test]
1177 fn test_table_options_to_string() {
1178 let options = TableOptions {
1179 write_buffer_size: Some(ReadableSize::mb(128)),
1180 ttl: Some(Duration::from_secs(1000).into()),
1181 extra_options: HashMap::new(),
1182 skip_wal: false,
1183 };
1184
1185 assert_eq!(
1186 "write_buffer_size=128.0MiB ttl=16m 40s",
1187 options.to_string()
1188 );
1189
1190 let options = TableOptions {
1191 write_buffer_size: Some(ReadableSize::mb(128)),
1192 ttl: Some(Duration::from_secs(1000).into()),
1193 extra_options: HashMap::from([("a".to_string(), "A".to_string())]),
1194 skip_wal: false,
1195 };
1196
1197 assert_eq!(
1198 "write_buffer_size=128.0MiB ttl=16m 40s a=A",
1199 options.to_string()
1200 );
1201
1202 let options = TableOptions {
1203 write_buffer_size: Some(ReadableSize::mb(128)),
1204 ttl: Some(Duration::from_secs(1000).into()),
1205 extra_options: HashMap::new(),
1206 skip_wal: true,
1207 };
1208 assert_eq!(
1209 "write_buffer_size=128.0MiB ttl=16m 40s skip_wal=true",
1210 options.to_string()
1211 );
1212
1213 let options = TableOptions {
1214 write_buffer_size: None,
1215 ttl: None,
1216 extra_options: HashMap::from([(SKIP_WAL_KEY.to_string(), "false".to_string())]),
1217 skip_wal: false,
1218 };
1219 assert_eq!("skip_wal=false", options.to_string());
1220 }
1221}