1use api::helper::ColumnDataTypeWrapper;
16use api::v1::add_column_location::LocationType;
17use api::v1::alter_table_expr::Kind;
18use api::v1::column_def::{
19 as_fulltext_option_analyzer, as_fulltext_option_backend, as_skipping_index_type,
20};
21use api::v1::{
22 AddColumnLocation as Location, AlterTableExpr, Analyzer, CreateTableExpr, DropColumns,
23 FulltextBackend as PbFulltextBackend, ModifyColumnTypes, RenameTable, SemanticType,
24 SkippingIndexType as PbSkippingIndexType, column_def,
25};
26use common_query::AddColumnLocation;
27use datatypes::json::{JsonSettings, JsonTypeHint};
28use datatypes::prelude::ConcreteDataType;
29use datatypes::schema::{ColumnSchema, FulltextOptions, Schema, SkippingIndexOptions};
30use snafu::{OptionExt, ResultExt, ensure};
31use store_api::region_request::{SetRegionOption, UnsetRegionOption};
32use table::metadata::{TableId, TableMeta};
33use table::requests::{
34 AddColumnRequest, AlterKind, AlterTableRequest, AnnotationFamily, ModifyColumnTypeRequest,
35 SetDefaultRequest, SetIndexOption, SetJsonSettingsRequest, UnsetIndexOption,
36};
37
38use crate::error::{
39 self, ColumnNotFoundSnafu, InvalidColumnDefSnafu, InvalidIndexOptionSnafu,
40 InvalidJsonSettingsSnafu, InvalidSetFulltextOptionRequestSnafu,
41 InvalidSetSkippingIndexOptionRequestSnafu, InvalidSetTableOptionRequestSnafu,
42 InvalidUnsetTableOptionRequestSnafu, MissingAlterIndexOptionSnafu, MissingFieldSnafu,
43 MissingTableMetaSnafu, MissingTimestampColumnSnafu, Result, UnknownLocationTypeSnafu,
44};
45
46const LOCATION_TYPE_FIRST: i32 = LocationType::First as i32;
47const LOCATION_TYPE_AFTER: i32 = LocationType::After as i32;
48
49fn annotation_family_of_keys<'a>(
54 keys: impl Iterator<Item = &'a str>,
55) -> Result<Option<AnnotationFamily>> {
56 table::requests::validate_annotation_keys(keys).map_err(|err| {
57 error::InvalidTableOptionRequestSnafu {
58 err_msg: err.to_string(),
59 }
60 .build()
61 })
62}
63
64fn json_settings_from_proto(settings: api::v1::JsonSettings) -> Result<JsonSettings> {
65 let type_hints = settings
66 .type_hints
67 .into_iter()
68 .map(|hint| {
69 let data_type = ConcreteDataType::from(
70 ColumnDataTypeWrapper::try_new(hint.data_type, hint.datatype_extension)
71 .context(error::ColumnDataTypeSnafu)?,
72 );
73
74 Ok(JsonTypeHint {
75 path: hint.path,
76 data_type,
77 inverted_index: hint.inverted_index,
78 skipping_index: hint
79 .skipping_index
80 .map(api::v1::column_def::json_skipping_options_from_proto)
81 .transpose()
82 .map_err(|err| {
83 InvalidJsonSettingsSnafu {
84 err: err.to_string(),
85 }
86 .build()
87 })?,
88 })
89 })
90 .collect::<Result<Vec<_>>>()?;
91
92 JsonSettings::try_new(type_hints, settings.max_auto_expanded_paths).map_err(|err| {
93 InvalidJsonSettingsSnafu {
94 err: err.to_string(),
95 }
96 .build()
97 })
98}
99
100pub fn annotation_alter_family(kind: &Kind) -> Result<Option<AnnotationFamily>> {
106 match kind {
107 Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
108 annotation_family_of_keys(table_options.iter().map(|option| option.key.as_str()))
109 }
110 Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
111 annotation_family_of_keys(keys.iter().map(|key| key.as_str()))
112 }
113 _ => Ok(None),
114 }
115}
116
117fn set_index_option_from_proto(set_index: api::v1::SetIndex) -> Result<SetIndexOption> {
118 let options = set_index.options.context(MissingAlterIndexOptionSnafu)?;
119 Ok(match options {
120 api::v1::set_index::Options::Fulltext(f) => SetIndexOption::Fulltext {
121 column_name: f.column_name.clone(),
122 options: FulltextOptions::new(
123 f.enable,
124 as_fulltext_option_analyzer(
125 Analyzer::try_from(f.analyzer).context(InvalidSetFulltextOptionRequestSnafu)?,
126 ),
127 f.case_sensitive,
128 as_fulltext_option_backend(
129 PbFulltextBackend::try_from(f.backend)
130 .context(InvalidSetFulltextOptionRequestSnafu)?,
131 ),
132 f.granularity as u32,
133 f.false_positive_rate,
134 )
135 .context(InvalidIndexOptionSnafu)?,
136 },
137 api::v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
138 column_name: i.column_name,
139 },
140 api::v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
141 column_name: s.column_name,
142 options: SkippingIndexOptions::new(
143 s.granularity as u32,
144 s.false_positive_rate,
145 as_skipping_index_type(
146 PbSkippingIndexType::try_from(s.skipping_index_type)
147 .context(InvalidSetSkippingIndexOptionRequestSnafu)?,
148 ),
149 )
150 .context(InvalidIndexOptionSnafu)?,
151 },
152 })
153}
154
155fn unset_index_option_from_proto(unset_index: api::v1::UnsetIndex) -> Result<UnsetIndexOption> {
156 let options = unset_index.options.context(MissingAlterIndexOptionSnafu)?;
157 Ok(match options {
158 api::v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
159 column_name: f.column_name,
160 },
161 api::v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
162 column_name: i.column_name,
163 },
164 api::v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
165 column_name: s.column_name,
166 },
167 })
168}
169
170pub fn alter_expr_to_request(
174 table_id: TableId,
175 expr: AlterTableExpr,
176 table_meta: Option<&TableMeta>,
177) -> Result<AlterTableRequest> {
178 let catalog_name = expr.catalog_name;
179 let schema_name = expr.schema_name;
180 let kind = expr.kind.context(MissingFieldSnafu { field: "kind" })?;
181 let alter_kind = match kind {
182 Kind::AddColumns(add_columns) => {
183 let add_column_requests = add_columns
184 .add_columns
185 .into_iter()
186 .map(|ac| {
187 let column_def = ac.column_def.context(MissingFieldSnafu {
188 field: "column_def",
189 })?;
190
191 let schema = column_def::try_as_column_schema(&column_def).context(
192 InvalidColumnDefSnafu {
193 column: &column_def.name,
194 },
195 )?;
196 Ok(AddColumnRequest {
197 column_schema: schema,
198 is_key: column_def.semantic_type == SemanticType::Tag as i32,
199 location: parse_location(ac.location)?,
200 add_if_not_exists: ac.add_if_not_exists,
201 })
202 })
203 .collect::<Result<Vec<_>>>()?;
204
205 AlterKind::AddColumns {
206 columns: add_column_requests,
207 }
208 }
209 Kind::ModifyColumnTypes(ModifyColumnTypes {
210 modify_column_types,
211 }) => {
212 let modify_column_type_requests = modify_column_types
213 .into_iter()
214 .map(|cct| {
215 let target_type =
216 ColumnDataTypeWrapper::new(cct.target_type(), cct.target_type_extension)
217 .into();
218
219 Ok(ModifyColumnTypeRequest {
220 column_name: cct.column_name,
221 target_type,
222 })
223 })
224 .collect::<Result<Vec<_>>>()?;
225
226 AlterKind::ModifyColumnTypes {
227 columns: modify_column_type_requests,
228 }
229 }
230 Kind::SetJsonSettings(set_json_settings) => {
231 let settings = set_json_settings
232 .settings
233 .context(MissingFieldSnafu { field: "settings" })?;
234 AlterKind::SetJsonSettings {
235 request: SetJsonSettingsRequest {
236 column_name: set_json_settings.column_name,
237 settings: json_settings_from_proto(settings)?,
238 },
239 }
240 }
241 Kind::DropColumns(DropColumns { drop_columns }) => AlterKind::DropColumns {
242 names: drop_columns.into_iter().map(|c| c.name).collect(),
243 },
244 Kind::RenameTable(RenameTable { new_table_name }) => {
245 AlterKind::RenameTable { new_table_name }
246 }
247 Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
248 if let Some(family) = annotation_family_of_keys(
249 table_options.iter().map(|option| option.key.as_str()),
250 )? {
251 AlterKind::SetAnnotations {
252 family,
253 options: table_options
254 .into_iter()
255 .map(|option| (option.key, option.value))
256 .collect(),
257 }
258 } else {
259 AlterKind::SetTableOptions {
260 options: table_options
261 .iter()
262 .map(SetRegionOption::try_from)
263 .collect::<std::result::Result<Vec<_>, _>>()
264 .context(InvalidSetTableOptionRequestSnafu)?,
265 }
266 }
267 }
268 Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
269 if let Some(family) = annotation_family_of_keys(keys.iter().map(|key| key.as_str()))? {
270 AlterKind::UnsetAnnotations { family, keys }
271 } else {
272 AlterKind::UnsetTableOptions {
273 keys: keys
274 .iter()
275 .map(|key| UnsetRegionOption::try_from(key.as_str()))
276 .collect::<std::result::Result<Vec<_>, _>>()
277 .context(InvalidUnsetTableOptionRequestSnafu)?,
278 }
279 }
280 }
281 Kind::SetIndex(o) => {
282 let option = set_index_option_from_proto(o)?;
283 AlterKind::SetIndexes {
284 options: vec![option],
285 }
286 }
287 Kind::UnsetIndex(o) => {
288 let option = unset_index_option_from_proto(o)?;
289 AlterKind::UnsetIndexes {
290 options: vec![option],
291 }
292 }
293 Kind::SetIndexes(o) => {
294 let options = o
295 .set_indexes
296 .into_iter()
297 .map(set_index_option_from_proto)
298 .collect::<Result<Vec<_>>>()?;
299 AlterKind::SetIndexes { options }
300 }
301 Kind::UnsetIndexes(o) => {
302 let options = o
303 .unset_indexes
304 .into_iter()
305 .map(unset_index_option_from_proto)
306 .collect::<Result<Vec<_>>>()?;
307 AlterKind::UnsetIndexes { options }
308 }
309 Kind::DropDefaults(o) => {
310 let names = o
311 .drop_defaults
312 .into_iter()
313 .map(|col| {
314 ensure!(
315 !col.column_name.is_empty(),
316 MissingFieldSnafu {
317 field: "column_name"
318 }
319 );
320 Ok(col.column_name)
321 })
322 .collect::<Result<Vec<_>>>()?;
323 AlterKind::DropDefaults { names }
324 }
325 Kind::SetDefaults(o) => {
326 let table_meta = table_meta.context(MissingTableMetaSnafu { table_id })?;
327 let defaults = o
328 .set_defaults
329 .into_iter()
330 .map(|col| {
331 let column_scheme = table_meta
332 .schema
333 .column_schema_by_name(&col.column_name)
334 .context(ColumnNotFoundSnafu {
335 column_name: &col.column_name,
336 })?;
337 let default_constraint = common_sql::convert::deserialize_default_constraint(
338 col.default_constraint.as_slice(),
339 &col.column_name,
340 &column_scheme.data_type,
341 )
342 .context(crate::error::SqlCommonSnafu)?;
343 Ok(SetDefaultRequest {
344 column_name: col.column_name,
345 default_constraint,
346 })
347 })
348 .collect::<Result<Vec<_>>>()?;
349 AlterKind::SetDefaults { defaults }
350 }
351 Kind::Repartition(_) => error::UnexpectedSnafu {
352 err_msg: "Repartition operation should be handled through DdlManager and not converted to AlterTableRequest",
353 }
354 .fail()?,
355 };
356
357 let request = AlterTableRequest {
358 catalog_name,
359 schema_name,
360 table_name: expr.table_name,
361 table_id,
362 alter_kind,
363 table_version: None,
364 };
365 Ok(request)
366}
367
368pub fn create_table_schema(expr: &CreateTableExpr, require_time_index: bool) -> Result<Schema> {
369 let column_schemas = expr
370 .column_defs
371 .iter()
372 .map(|x| {
373 column_def::try_as_column_schema(x).context(InvalidColumnDefSnafu { column: &x.name })
374 })
375 .collect::<Result<Vec<ColumnSchema>>>()?;
376
377 if require_time_index {
379 ensure!(
380 column_schemas
381 .iter()
382 .any(|column| column.name == expr.time_index),
383 MissingTimestampColumnSnafu {
384 msg: format!("CreateExpr: {expr:?}")
385 }
386 );
387 }
388
389 let column_schemas = column_schemas
390 .into_iter()
391 .map(|column_schema| {
392 if column_schema.name == expr.time_index {
393 column_schema.with_time_index(true)
394 } else {
395 column_schema
396 }
397 })
398 .collect::<Vec<_>>();
399
400 Ok(Schema::new(column_schemas))
401}
402
403fn parse_location(location: Option<Location>) -> Result<Option<AddColumnLocation>> {
404 match location {
405 Some(Location {
406 location_type: LOCATION_TYPE_FIRST,
407 ..
408 }) => Ok(Some(AddColumnLocation::First)),
409 Some(Location {
410 location_type: LOCATION_TYPE_AFTER,
411 after_column_name,
412 }) => Ok(Some(AddColumnLocation::After {
413 column_name: after_column_name,
414 })),
415 Some(Location { location_type, .. }) => UnknownLocationTypeSnafu { location_type }.fail(),
416 None => Ok(None),
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use api::v1::{
423 AddColumn, AddColumns, ColumnDataType, ColumnDef, DropColumn, ModifyColumnType,
424 Option as PbOption, SemanticType, SetTableOptions, UnsetTableOptions,
425 };
426 use datatypes::prelude::ConcreteDataType;
427 use table::requests::REPARTITION_COLUMN_HINT_KEY;
428
429 use super::*;
430
431 #[test]
432 fn test_json_settings_preserve_inverted_index() {
433 for enabled in [false, true] {
434 let settings = json_settings_from_proto(api::v1::JsonSettings {
435 type_hints: vec![api::v1::JsonTypeHint {
436 path: vec!["service".into()],
437 data_type: ColumnDataType::String as i32,
438 inverted_index: enabled,
439 skipping_index: None,
440 ..Default::default()
441 }],
442 max_auto_expanded_paths: Some(0),
443 })
444 .unwrap();
445 assert_eq!(enabled, settings.type_hints()[0].inverted_index);
446 }
447 }
448
449 #[test]
450 fn test_json_settings_skipping_options_from_proto() {
451 for (granularity, rate, index_type, valid) in [
452 (8, 0.05, 0, true),
453 (0, 0.05, 0, false),
454 (8, 0.0, 0, false),
455 (8, 0.05, 9, false),
456 ] {
457 let settings = json_settings_from_proto(api::v1::JsonSettings {
458 type_hints: vec![api::v1::JsonTypeHint {
459 path: vec!["service".into()],
460 data_type: ColumnDataType::String as i32,
461 skipping_index: Some(api::v1::JsonSkippingIndexOptions {
462 granularity,
463 index_type,
464 false_positive_rate: rate,
465 }),
466 ..Default::default()
467 }],
468 max_auto_expanded_paths: Some(0),
469 });
470 assert_eq!(settings.is_ok(), valid);
471 if let Ok(settings) = settings {
472 let options = settings.type_hints()[0].skipping_index.as_ref().unwrap();
473 assert_eq!(options.granularity, granularity);
474 assert_eq!(options.false_positive_rate(), rate);
475 }
476 }
477 }
478
479 #[test]
480 fn test_json_settings_path_validation_from_proto() {
481 for (path, valid_path) in [
482 (vec!["nested", "leaf"], true),
483 (vec![""], false),
484 (vec!["nested", ""], false),
485 (
486 vec!["nested", datatypes::json::JSON2_REMAINDER_FIELD_NAME],
487 false,
488 ),
489 ] {
490 for inverted_index in [false, true] {
491 let settings = api::v1::SetJsonSettings {
492 column_name: "payload".into(),
493 settings: Some(api::v1::JsonSettings {
494 type_hints: vec![api::v1::JsonTypeHint {
495 path: path.iter().map(|part| (*part).to_string()).collect(),
496 data_type: ColumnDataType::String as i32,
497 inverted_index,
498 skipping_index: None,
499 ..Default::default()
500 }],
501 max_auto_expanded_paths: Some(0),
502 }),
503 };
504 let result = alter_expr_to_request(
505 1,
506 AlterTableExpr {
507 catalog_name: "greptime".into(),
508 schema_name: "public".into(),
509 table_name: "logs".into(),
510 kind: Some(Kind::SetJsonSettings(settings)),
511 },
512 None,
513 );
514 assert_eq!(
515 result.is_ok(),
516 valid_path,
517 "path: {path:?}, inverted_index: {inverted_index}"
518 );
519 }
520 }
521 }
522
523 #[test]
524 fn test_alter_expr_to_request() {
525 let expr = AlterTableExpr {
526 catalog_name: String::default(),
527 schema_name: String::default(),
528 table_name: "monitor".to_string(),
529
530 kind: Some(Kind::AddColumns(AddColumns {
531 add_columns: vec![AddColumn {
532 column_def: Some(ColumnDef {
533 name: "mem_usage".to_string(),
534 data_type: ColumnDataType::Float64 as i32,
535 is_nullable: false,
536 default_constraint: vec![],
537 semantic_type: SemanticType::Field as i32,
538 comment: String::new(),
539 ..Default::default()
540 }),
541 location: None,
542 add_if_not_exists: true,
543 }],
544 })),
545 };
546
547 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
548 assert_eq!(alter_request.catalog_name, "");
549 assert_eq!(alter_request.schema_name, "");
550 assert_eq!("monitor".to_string(), alter_request.table_name);
551 let add_column = match alter_request.alter_kind {
552 AlterKind::AddColumns { mut columns } => columns.pop().unwrap(),
553 _ => unreachable!(),
554 };
555
556 assert!(!add_column.is_key);
557 assert_eq!("mem_usage", add_column.column_schema.name);
558 assert_eq!(
559 ConcreteDataType::float64_datatype(),
560 add_column.column_schema.data_type
561 );
562 assert_eq!(None, add_column.location);
563 assert!(add_column.add_if_not_exists);
564 }
565
566 #[test]
567 fn test_alter_expr_with_location_to_request() {
568 let expr = AlterTableExpr {
569 catalog_name: String::default(),
570 schema_name: String::default(),
571 table_name: "monitor".to_string(),
572
573 kind: Some(Kind::AddColumns(AddColumns {
574 add_columns: vec![
575 AddColumn {
576 column_def: Some(ColumnDef {
577 name: "mem_usage".to_string(),
578 data_type: ColumnDataType::Float64 as i32,
579 is_nullable: false,
580 default_constraint: vec![],
581 semantic_type: SemanticType::Field as i32,
582 comment: String::new(),
583 ..Default::default()
584 }),
585 location: Some(Location {
586 location_type: LocationType::First.into(),
587 after_column_name: String::default(),
588 }),
589 add_if_not_exists: false,
590 },
591 AddColumn {
592 column_def: Some(ColumnDef {
593 name: "cpu_usage".to_string(),
594 data_type: ColumnDataType::Float64 as i32,
595 is_nullable: false,
596 default_constraint: vec![],
597 semantic_type: SemanticType::Field as i32,
598 comment: String::new(),
599 ..Default::default()
600 }),
601 location: Some(Location {
602 location_type: LocationType::After.into(),
603 after_column_name: "ts".to_string(),
604 }),
605 add_if_not_exists: true,
606 },
607 ],
608 })),
609 };
610
611 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
612 assert_eq!(alter_request.catalog_name, "");
613 assert_eq!(alter_request.schema_name, "");
614 assert_eq!("monitor".to_string(), alter_request.table_name);
615
616 let mut add_columns = match alter_request.alter_kind {
617 AlterKind::AddColumns { columns } => columns,
618 _ => unreachable!(),
619 };
620
621 let add_column = add_columns.pop().unwrap();
622 assert!(!add_column.is_key);
623 assert_eq!("cpu_usage", add_column.column_schema.name);
624 assert_eq!(
625 ConcreteDataType::float64_datatype(),
626 add_column.column_schema.data_type
627 );
628 assert_eq!(
629 Some(AddColumnLocation::After {
630 column_name: "ts".to_string()
631 }),
632 add_column.location
633 );
634 assert!(add_column.add_if_not_exists);
635
636 let add_column = add_columns.pop().unwrap();
637 assert!(!add_column.is_key);
638 assert_eq!("mem_usage", add_column.column_schema.name);
639 assert_eq!(
640 ConcreteDataType::float64_datatype(),
641 add_column.column_schema.data_type
642 );
643 assert_eq!(Some(AddColumnLocation::First), add_column.location);
644 assert!(!add_column.add_if_not_exists);
645 }
646
647 #[test]
648 fn test_modify_column_type_expr() {
649 let expr = AlterTableExpr {
650 catalog_name: "test_catalog".to_string(),
651 schema_name: "test_schema".to_string(),
652 table_name: "monitor".to_string(),
653
654 kind: Some(Kind::ModifyColumnTypes(ModifyColumnTypes {
655 modify_column_types: vec![ModifyColumnType {
656 column_name: "mem_usage".to_string(),
657 target_type: ColumnDataType::String as i32,
658 target_type_extension: None,
659 }],
660 })),
661 };
662
663 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
664 assert_eq!(alter_request.catalog_name, "test_catalog");
665 assert_eq!(alter_request.schema_name, "test_schema");
666 assert_eq!("monitor".to_string(), alter_request.table_name);
667
668 let mut modify_column_types = match alter_request.alter_kind {
669 AlterKind::ModifyColumnTypes { columns } => columns,
670 _ => unreachable!(),
671 };
672
673 let modify_column_type = modify_column_types.pop().unwrap();
674 assert_eq!("mem_usage", modify_column_type.column_name);
675 assert_eq!(
676 ConcreteDataType::string_datatype(),
677 modify_column_type.target_type
678 );
679 }
680
681 #[test]
682 fn test_drop_column_expr() {
683 let expr = AlterTableExpr {
684 catalog_name: "test_catalog".to_string(),
685 schema_name: "test_schema".to_string(),
686 table_name: "monitor".to_string(),
687
688 kind: Some(Kind::DropColumns(DropColumns {
689 drop_columns: vec![DropColumn {
690 name: "mem_usage".to_string(),
691 }],
692 })),
693 };
694
695 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
696 assert_eq!(alter_request.catalog_name, "test_catalog");
697 assert_eq!(alter_request.schema_name, "test_schema");
698 assert_eq!("monitor".to_string(), alter_request.table_name);
699
700 let mut drop_names = match alter_request.alter_kind {
701 AlterKind::DropColumns { names } => names,
702 _ => unreachable!(),
703 };
704 assert_eq!(1, drop_names.len());
705 assert_eq!("mem_usage".to_string(), drop_names.pop().unwrap());
706 }
707
708 #[test]
709 fn test_repartition_hints_together() {
710 let keys = [
711 REPARTITION_COLUMN_HINT_KEY,
712 table::requests::REPARTITION_PARTITION_NUM_HINT_KEY,
713 ];
714 let options = vec![
715 (keys[0].to_string(), "host".to_string()),
716 (keys[1].to_string(), "10".to_string()),
717 ];
718 let set = Kind::SetTableOptions(SetTableOptions {
719 table_options: options
720 .iter()
721 .map(|(key, value)| PbOption {
722 key: key.clone(),
723 value: value.clone(),
724 })
725 .collect(),
726 });
727 let unset = Kind::UnsetTableOptions(UnsetTableOptions {
728 keys: keys.map(str::to_string).to_vec(),
729 });
730 for kind in [set, unset] {
731 assert_eq!(
732 annotation_alter_family(&kind).unwrap(),
733 Some(AnnotationFamily::RepartitionHint)
734 );
735 let request = alter_expr_to_request(
736 1,
737 AlterTableExpr {
738 kind: Some(kind),
739 ..Default::default()
740 },
741 None,
742 )
743 .unwrap();
744 match request.alter_kind {
745 AlterKind::SetAnnotations {
746 family,
747 options: actual,
748 } => {
749 assert_eq!(family, AnnotationFamily::RepartitionHint);
750 assert_eq!(actual, options);
751 }
752 AlterKind::UnsetAnnotations {
753 family,
754 keys: actual,
755 } => {
756 assert_eq!(family, AnnotationFamily::RepartitionHint);
757 assert_eq!(actual, keys);
758 }
759 other => panic!("unexpected alter kind: {other:?}"),
760 }
761 }
762 }
763
764 #[test]
765 fn test_repartition_partition_num_hint_expr() {
766 let key = table::requests::REPARTITION_PARTITION_NUM_HINT_KEY;
767 let set = Kind::SetTableOptions(SetTableOptions {
768 table_options: vec![PbOption {
769 key: key.to_string(),
770 value: "8".to_string(),
771 }],
772 });
773 let unset = Kind::UnsetTableOptions(UnsetTableOptions {
774 keys: vec![key.to_string()],
775 });
776 for kind in [set, unset] {
777 assert_eq!(
778 annotation_alter_family(&kind).unwrap(),
779 Some(AnnotationFamily::RepartitionHint)
780 );
781 let request = alter_expr_to_request(
782 1,
783 AlterTableExpr {
784 kind: Some(kind),
785 ..Default::default()
786 },
787 None,
788 )
789 .unwrap();
790 match request.alter_kind {
791 AlterKind::SetAnnotations { family, options } => {
792 assert_eq!(family, AnnotationFamily::RepartitionHint);
793 assert_eq!(options, vec![(key.to_string(), "8".to_string())]);
794 }
795 AlterKind::UnsetAnnotations { family, keys } => {
796 assert_eq!(family, AnnotationFamily::RepartitionHint);
797 assert_eq!(keys, vec![key.to_string()]);
798 }
799 other => panic!("unexpected alter kind: {other:?}"),
800 }
801 }
802 for other in [key, table::requests::TTL_KEY] {
803 for kind in [
804 Kind::SetTableOptions(SetTableOptions {
805 table_options: [key, other]
806 .into_iter()
807 .map(|key| PbOption {
808 key: key.to_string(),
809 value: "8".to_string(),
810 })
811 .collect(),
812 }),
813 Kind::UnsetTableOptions(UnsetTableOptions {
814 keys: vec![key.to_string(), other.to_string()],
815 }),
816 ] {
817 assert!(annotation_alter_family(&kind).is_err());
818 assert!(
819 alter_expr_to_request(
820 1,
821 AlterTableExpr {
822 kind: Some(kind),
823 ..Default::default()
824 },
825 None
826 )
827 .is_err()
828 );
829 }
830 }
831 }
832
833 #[test]
834 fn test_set_repartition_column_hint_expr() {
835 let expr = AlterTableExpr {
836 catalog_name: "test_catalog".to_string(),
837 schema_name: "test_schema".to_string(),
838 table_name: "monitor".to_string(),
839 kind: Some(Kind::SetTableOptions(SetTableOptions {
840 table_options: vec![PbOption {
841 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
842 value: "host".to_string(),
843 }],
844 })),
845 };
846
847 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
848 match alter_request.alter_kind {
849 AlterKind::SetAnnotations { family, options } => {
850 assert_eq!(AnnotationFamily::RepartitionHint, family);
851 assert_eq!(
852 vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "host".to_string())],
853 options
854 );
855 }
856 _ => unreachable!(),
857 }
858 }
859
860 #[test]
861 fn test_set_repartition_column_hint_rejects_mixed_options() {
862 let expr = AlterTableExpr {
863 catalog_name: "test_catalog".to_string(),
864 schema_name: "test_schema".to_string(),
865 table_name: "monitor".to_string(),
866 kind: Some(Kind::SetTableOptions(SetTableOptions {
867 table_options: vec![
868 PbOption {
869 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
870 value: "host".to_string(),
871 },
872 PbOption {
873 key: table::requests::TTL_KEY.to_string(),
874 value: "7d".to_string(),
875 },
876 ],
877 })),
878 };
879
880 let err = alter_expr_to_request(1, expr, None).unwrap_err();
881 assert!(
882 err.to_string()
883 .contains("repartition hints must be altered separately")
884 );
885
886 let dup = AlterTableExpr {
888 catalog_name: "test_catalog".to_string(),
889 schema_name: "test_schema".to_string(),
890 table_name: "monitor".to_string(),
891 kind: Some(Kind::SetTableOptions(SetTableOptions {
892 table_options: vec![
893 PbOption {
894 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
895 value: "host".to_string(),
896 },
897 PbOption {
898 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
899 value: "region".to_string(),
900 },
901 ],
902 })),
903 };
904 let err = alter_expr_to_request(1, dup, None).unwrap_err();
905 assert!(err.to_string().contains("duplicate repartition hint keys"));
906 }
907
908 #[test]
909 fn test_unset_repartition_column_hint_expr() {
910 let expr = AlterTableExpr {
911 catalog_name: "test_catalog".to_string(),
912 schema_name: "test_schema".to_string(),
913 table_name: "monitor".to_string(),
914 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
915 keys: vec![REPARTITION_COLUMN_HINT_KEY.to_string()],
916 })),
917 };
918
919 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
920 match alter_request.alter_kind {
921 AlterKind::UnsetAnnotations { family, keys } => {
922 assert_eq!(AnnotationFamily::RepartitionHint, family);
923 assert_eq!(vec![REPARTITION_COLUMN_HINT_KEY.to_string()], keys);
924 }
925 _ => unreachable!(),
926 }
927 }
928
929 #[test]
930 fn test_semantic_options_classified_as_annotations() {
931 let expr = AlterTableExpr {
932 catalog_name: "test_catalog".to_string(),
933 schema_name: "test_schema".to_string(),
934 table_name: "monitor".to_string(),
935 kind: Some(Kind::SetTableOptions(SetTableOptions {
936 table_options: vec![
937 PbOption {
938 key: "greptime.semantic.signal_type".to_string(),
939 value: "trace".to_string(),
940 },
941 PbOption {
942 key: "greptime.semantic.entity.host.id".to_string(),
943 value: "host".to_string(),
944 },
945 ],
946 })),
947 };
948
949 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
950 let AlterKind::SetAnnotations { family, options } = alter_request.alter_kind else {
951 panic!(
952 "expected SetAnnotations, got {:?}",
953 alter_request.alter_kind
954 );
955 };
956 assert_eq!(family, AnnotationFamily::Semantic);
957 assert_eq!(options.len(), 2);
958
959 let expr = AlterTableExpr {
960 catalog_name: "test_catalog".to_string(),
961 schema_name: "test_schema".to_string(),
962 table_name: "monitor".to_string(),
963 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
964 keys: vec!["greptime.semantic.signal_type".to_string()],
965 })),
966 };
967 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
968 assert!(matches!(
969 alter_request.alter_kind,
970 AlterKind::UnsetAnnotations {
971 family: AnnotationFamily::Semantic,
972 ..
973 }
974 ));
975 }
976
977 #[test]
978 fn test_semantic_options_reject_mixed_batch() {
979 let mixed_set = AlterTableExpr {
980 catalog_name: "test_catalog".to_string(),
981 schema_name: "test_schema".to_string(),
982 table_name: "monitor".to_string(),
983 kind: Some(Kind::SetTableOptions(SetTableOptions {
984 table_options: vec![
985 PbOption {
986 key: "greptime.semantic.signal_type".to_string(),
987 value: "trace".to_string(),
988 },
989 PbOption {
990 key: table::requests::TTL_KEY.to_string(),
991 value: "7d".to_string(),
992 },
993 ],
994 })),
995 };
996 let err = alter_expr_to_request(1, mixed_set, None).unwrap_err();
997 assert!(err.to_string().contains("altered separately"), "{err}");
998
999 let mixed_unset = AlterTableExpr {
1000 catalog_name: "test_catalog".to_string(),
1001 schema_name: "test_schema".to_string(),
1002 table_name: "monitor".to_string(),
1003 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
1004 keys: vec![
1005 "ttl".to_string(),
1006 "greptime.semantic.signal_type".to_string(),
1007 ],
1008 })),
1009 };
1010 let err = alter_expr_to_request(1, mixed_unset, None).unwrap_err();
1011 assert!(err.to_string().contains("altered separately"), "{err}");
1012 }
1013
1014 #[test]
1015 fn test_annotation_alter_family() {
1016 let mixed = Kind::SetTableOptions(SetTableOptions {
1017 table_options: vec![
1018 PbOption {
1019 key: "greptime.semantic.signal_type".to_string(),
1020 value: "trace".to_string(),
1021 },
1022 PbOption {
1023 key: "ttl".to_string(),
1024 value: "7d".to_string(),
1025 },
1026 ],
1027 });
1028 let err = annotation_alter_family(&mixed).unwrap_err();
1029 assert!(
1030 err.to_string().contains("must be altered separately"),
1031 "{err}"
1032 );
1033
1034 let cross = Kind::SetTableOptions(SetTableOptions {
1036 table_options: vec![
1037 PbOption {
1038 key: "greptime.semantic.signal_type".to_string(),
1039 value: "trace".to_string(),
1040 },
1041 PbOption {
1042 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
1043 value: "host".to_string(),
1044 },
1045 ],
1046 });
1047 let err = annotation_alter_family(&cross).unwrap_err();
1048 assert!(
1049 err.to_string().contains("must be altered separately"),
1050 "{err}"
1051 );
1052 }
1053}