1use std::fmt::Display;
16use std::str::FromStr;
17
18use common_base::readable_size::ReadableSize;
19use common_query::AddColumnLocation;
20use common_time::{Duration, FOREVER, INSTANT};
21use derive_builder::Builder;
22use serde::{Deserialize, Serialize};
23use store_api::mito_engine_options::{
24 APPEND_MODE_KEY, COMPACTION_TYPE, TTL_KEY, TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW,
25 TWCS_TRIGGER_FILE_NUM,
26};
27use strum::EnumIter;
28
29use crate::error::{self, Result};
30use crate::ir::{Column, Ident};
31
32#[derive(Debug, Builder, Clone, Serialize, Deserialize)]
33pub struct AlterTableExpr {
34 pub table_name: Ident,
35 pub alter_kinds: AlterTableOperation,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub enum AlterTableOperation {
40 AddColumn {
42 column: Column,
43 location: Option<AddColumnLocation>,
44 },
45 DropColumn { name: Ident },
47 RenameTable { new_table_name: Ident },
49 ModifyDataType { column: Column },
51 SetTableOptions { options: Vec<AlterTableOption> },
53 UnsetTableOptions { keys: Vec<String> },
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
58pub enum Ttl {
59 Duration(Duration),
60 Instant,
61 #[default]
62 Forever,
63}
64
65impl Display for Ttl {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Ttl::Duration(d) => write!(f, "{}", d),
69 Ttl::Instant => write!(f, "{}", INSTANT),
70 Ttl::Forever => write!(f, "{}", FOREVER),
71 }
72 }
73}
74
75#[derive(Debug, EnumIter, Clone, Serialize, Deserialize, PartialEq, Eq)]
76pub enum AlterTableOption {
77 Ttl(Ttl),
78 TwcsTimeWindow(Duration),
79 TwcsMaxOutputFileSize(ReadableSize),
80 TwcsTriggerFileNum(u64),
81}
82
83impl AlterTableOption {
84 pub fn key(&self) -> &str {
85 match self {
86 AlterTableOption::Ttl(_) => TTL_KEY,
87 AlterTableOption::TwcsTimeWindow(_) => TWCS_TIME_WINDOW,
88 AlterTableOption::TwcsMaxOutputFileSize(_) => TWCS_MAX_OUTPUT_FILE_SIZE,
89 AlterTableOption::TwcsTriggerFileNum(_) => TWCS_TRIGGER_FILE_NUM,
90 }
91 }
92
93 fn parse_kv(key: &str, value: &str) -> Result<Self> {
95 match key {
96 TTL_KEY => {
97 let ttl = if value.to_lowercase() == INSTANT {
98 Ttl::Instant
99 } else if value.to_lowercase() == FOREVER {
100 Ttl::Forever
101 } else {
102 let duration = humantime::parse_duration(value).unwrap();
103 Ttl::Duration(duration.into())
104 };
105 Ok(AlterTableOption::Ttl(ttl))
106 }
107 TWCS_TRIGGER_FILE_NUM => {
108 let files = value.parse().unwrap();
109 Ok(AlterTableOption::TwcsTriggerFileNum(files))
110 }
111 TWCS_MAX_OUTPUT_FILE_SIZE => {
112 let value = if value.ends_with("B") {
114 value.to_string()
115 } else {
116 format!("{}B", value)
117 };
118 let size = ReadableSize::from_str(&value).unwrap();
119 Ok(AlterTableOption::TwcsMaxOutputFileSize(size))
120 }
121 TWCS_TIME_WINDOW => {
122 let time = humantime::parse_duration(value).unwrap();
123 Ok(AlterTableOption::TwcsTimeWindow(time.into()))
124 }
125 _ => error::UnexpectedSnafu {
126 violated: format!("Unknown table option key: {}", key),
127 }
128 .fail(),
129 }
130 }
131
132 pub fn parse_kv_pairs(option_string: &str) -> Result<Vec<Self>> {
134 let mut options = vec![];
135 for pair in option_string.split(',') {
136 let pair = pair.trim();
137 let (key, value) = pair.split_once('=').unwrap();
138 let key = key.trim().replace("\'", "");
139 let value = value.trim().replace('\'', "");
140 if key == COMPACTION_TYPE || key == APPEND_MODE_KEY {
143 continue;
144 } else {
145 let option = AlterTableOption::parse_kv(&key, &value)?;
146 options.push(option);
147 }
148 }
149 Ok(options)
150 }
151}
152
153impl Display for AlterTableOption {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 AlterTableOption::Ttl(d) => write!(f, "'{}' = '{}'", TTL_KEY, d),
157 AlterTableOption::TwcsTimeWindow(d) => write!(f, "'{}' = '{}'", TWCS_TIME_WINDOW, d),
158 AlterTableOption::TwcsMaxOutputFileSize(s) => {
159 write!(f, "'{}' = '{}'", TWCS_MAX_OUTPUT_FILE_SIZE, s)
161 }
162 AlterTableOption::TwcsTriggerFileNum(u) => {
163 write!(f, "'{}' = '{}'", TWCS_TRIGGER_FILE_NUM, u)
164 }
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_parse_kv_pairs() {
175 let option_string =
176 "compaction.twcs.max_output_file_size = '1M', compaction.type = 'twcs', ttl = 'forever'";
177 let options = AlterTableOption::parse_kv_pairs(option_string).unwrap();
178 assert_eq!(options.len(), 2);
179 assert_eq!(
180 options,
181 vec![
182 AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("1MB").unwrap()),
183 AlterTableOption::Ttl(Ttl::Forever),
184 ]
185 );
186
187 let option_string = "compaction.twcs.trigger_file_num = '5030469694939972912',
188 compaction.twcs.max_output_file_size = '15686.4PiB',
189 compaction.twcs.time_window = '2061999256ms',
190 compaction.type = 'twcs',
191 ttl = '1month 3days 15h 49m 8s 279ms'";
192 let options = AlterTableOption::parse_kv_pairs(option_string).unwrap();
193 assert_eq!(options.len(), 4);
194 let expected = vec![
195 AlterTableOption::TwcsTriggerFileNum(5030469694939972912),
196 AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("15686.4PiB").unwrap()),
197 AlterTableOption::TwcsTimeWindow(Duration::new_nanosecond(2_061_999_256_000_000)),
198 AlterTableOption::Ttl(Ttl::Duration(Duration::new_millisecond(
199 2_630_016 * 1000
201 + 3 * 24 * 60 * 60 * 1000
202 + 15 * 60 * 60 * 1000
203 + 49 * 60 * 1000
204 + 8 * 1000
205 + 279,
206 ))),
207 ];
208 assert_eq!(options, expected);
209 }
210}