Skip to main content

table/
requests.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Table and TableEngine requests
16
17use 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
62/// Returns true if the table stores spans in the `greptime_trace_v1` data model
63/// (fixed span columns), the shape the Jaeger query path and the entity-graph
64/// derivation rely on.
65pub 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    // common keys:
80    WRITE_BUFFER_SIZE_KEY,
81    TTL_KEY,
82    STORAGE_KEY,
83    COMMENT_KEY,
84    SKIP_WAL_KEY,
85    SST_FORMAT_KEY,
86    // file engine keys:
87    FILE_TABLE_LOCATION_KEY,
88    FILE_TABLE_FORMAT_KEY,
89    FILE_TABLE_PATTERN_KEY,
90    // metric engine keys:
91    PHYSICAL_TABLE_METADATA_KEY,
92    LOGICAL_TABLE_METADATA_KEY,
93    // table model info
94    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
104// Valid option keys when creating a db.
105static 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
126/// Returns true if the `key` is a valid key for database.
127pub fn validate_database_option(key: &str) -> bool {
128    VALID_DB_OPT_KEYS.contains(&key)
129}
130
131/// Returns true if the `key` is a valid key for any engine or storage.
132pub 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    // Semantic-layer keys share a reserved prefix instead of a fixed allowlist so
150    // the vocabulary can grow without touching this gate. See `semantic` module.
151    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    /// Per-region write buffer stall threshold. Writes are rejected at twice this size.
162    pub write_buffer_size: Option<ReadableSize>,
163    /// Time-to-live of table. Expired data will be automatically purged.
164    pub ttl: Option<TimeToLive>,
165    /// Skip wal write for this table.
166    pub skip_wal: bool,
167    /// Extra options that may not applicable to all table engines.
168    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/// Alter table request
276#[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    // None in standalone.
284    pub table_version: Option<TableVersion>,
285}
286
287/// Add column request
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct AddColumnRequest {
290    pub column_schema: ColumnSchema,
291    pub is_key: bool,
292    pub location: Option<AddColumnLocation>,
293    /// Add column if not exists.
294    pub add_if_not_exists: bool,
295}
296
297/// Change column datatype request
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct ModifyColumnTypeRequest {
300    pub column_name: String,
301    pub target_type: ConcreteDataType,
302}
303
304/// A family of annotation table options: pure metadata markers that no region
305/// consumes. Setting or unsetting them only rewrites the table's
306/// `extra_options`, so the alter skips region dispatch entirely.
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308pub enum AnnotationFamily {
309    /// `greptime.semantic.*` options (see the [`semantic`] module).
310    Semantic,
311    /// `repartition.column.hint`, consumed by the auto-repartition planner.
312    RepartitionHint,
313}
314
315impl AnnotationFamily {
316    /// The key namespace: a prefix for [`Self::Semantic`], the exact key for
317    /// [`Self::RepartitionHint`].
318    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    /// Whether this family may be altered on logical metric tables. Only
336    /// families whose values nothing on the physical side consumes qualify;
337    /// the repartition hint drives physical region repartitioning.
338    pub fn allows_logical_tables(self) -> bool {
339        match self {
340            Self::Semantic => true,
341            Self::RepartitionHint => false,
342        }
343    }
344
345    /// Whether this family's SET/UNSET batch must contain exactly one key.
346    /// The repartition hint is a single marker; a batch with several hint
347    /// entries (duplicates included) has no meaningful order.
348    pub fn requires_single_key(self) -> bool {
349        matches!(self, Self::RepartitionHint)
350    }
351
352    /// The error for a SET/UNSET batch mixing this family with other options.
353    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
365/// Table shape an annotation option is validated against.
366pub struct AnnotationContext<'a> {
367    pub schema: &'a Schema,
368    pub partition_key_indices: &'a [usize],
369}
370
371/// Why an annotation option was rejected. Typed so each DDL entry point maps
372/// rules onto its existing error variants and status codes: ALTER keeps
373/// missing columns as `TableColumnNotFound` (4002), CREATE keeps its
374/// `InvalidArguments` family — the rules converge, the contracts do not.
375#[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
426/// Validates one annotation option and returns the value to store — the
427/// repartition hint is trimmed to the bare column name, semantic values pass
428/// through unchanged.
429pub(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
487/// CREATE-side entry: validates every annotation option present in `options`
488/// and writes normalized values back in place.
489pub 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    /// Returns the column name of the index option.
574    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    /// Returns the column name of the index option.
592    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/// Delete (by primary key) request
610#[derive(Debug)]
611pub struct DeleteRequest {
612    pub catalog_name: String,
613    pub schema_name: String,
614    pub table_name: String,
615    /// Values of each column in this table's primary key and time index.
616    ///
617    /// The key is the column name, and the value is the column value.
618    pub key_column_values: HashMap<String, VectorRef>,
619}
620
621#[derive(Debug)]
622pub enum CopyDirection {
623    Export,
624    Import,
625}
626
627/// Copy table request
628#[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/// Truncate table request
680#[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        // Only whitelisted semantic keys are accepted.
734        assert!(validate_table_option(SEMANTIC_SIGNAL_TYPE));
735        assert!(validate_table_option(SEMANTIC_METRIC_TYPE));
736        // Unknown semantic key, near-miss, and the internal transport key are rejected.
737        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}