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::schema::{ColumnSchema, FulltextOptions, Schema, SkippingIndexOptions};
28use snafu::{OptionExt, ResultExt, ensure};
29use store_api::region_request::{SetRegionOption, UnsetRegionOption};
30use table::metadata::{TableId, TableMeta};
31use table::requests::{
32 AddColumnRequest, AlterKind, AlterTableRequest, AnnotationFamily, ModifyColumnTypeRequest,
33 SetDefaultRequest, SetIndexOption, UnsetIndexOption,
34};
35
36use crate::error::{
37 self, ColumnNotFoundSnafu, InvalidColumnDefSnafu, InvalidIndexOptionSnafu,
38 InvalidSetFulltextOptionRequestSnafu, InvalidSetSkippingIndexOptionRequestSnafu,
39 InvalidSetTableOptionRequestSnafu, InvalidUnsetTableOptionRequestSnafu,
40 MissingAlterIndexOptionSnafu, MissingFieldSnafu, MissingTableMetaSnafu,
41 MissingTimestampColumnSnafu, Result, UnknownLocationTypeSnafu,
42};
43
44const LOCATION_TYPE_FIRST: i32 = LocationType::First as i32;
45const LOCATION_TYPE_AFTER: i32 = LocationType::After as i32;
46
47fn annotation_family_of_keys<'a>(
52 mut keys: impl Iterator<Item = &'a str>,
53) -> Result<Option<AnnotationFamily>> {
54 let Some(first) = keys.next() else {
55 return Ok(None);
56 };
57 let family = AnnotationFamily::of_key(first);
58 let mut count = 1usize;
59 for key in keys {
60 count += 1;
61 let this = AnnotationFamily::of_key(key);
62 if this != family {
63 return error::InvalidTableOptionRequestSnafu {
64 err_msg: family.or(this).unwrap().mixed_batch_error(),
65 }
66 .fail();
67 }
68 }
69 if let Some(family) = family
70 && family.requires_single_key()
71 && count > 1
72 {
73 return error::InvalidTableOptionRequestSnafu {
74 err_msg: family.mixed_batch_error(),
75 }
76 .fail();
77 }
78 Ok(family)
79}
80
81pub fn annotation_alter_family(kind: &Kind) -> Result<Option<AnnotationFamily>> {
87 match kind {
88 Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
89 annotation_family_of_keys(table_options.iter().map(|option| option.key.as_str()))
90 }
91 Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
92 annotation_family_of_keys(keys.iter().map(|key| key.as_str()))
93 }
94 _ => Ok(None),
95 }
96}
97
98fn set_index_option_from_proto(set_index: api::v1::SetIndex) -> Result<SetIndexOption> {
99 let options = set_index.options.context(MissingAlterIndexOptionSnafu)?;
100 Ok(match options {
101 api::v1::set_index::Options::Fulltext(f) => SetIndexOption::Fulltext {
102 column_name: f.column_name.clone(),
103 options: FulltextOptions::new(
104 f.enable,
105 as_fulltext_option_analyzer(
106 Analyzer::try_from(f.analyzer).context(InvalidSetFulltextOptionRequestSnafu)?,
107 ),
108 f.case_sensitive,
109 as_fulltext_option_backend(
110 PbFulltextBackend::try_from(f.backend)
111 .context(InvalidSetFulltextOptionRequestSnafu)?,
112 ),
113 f.granularity as u32,
114 f.false_positive_rate,
115 )
116 .context(InvalidIndexOptionSnafu)?,
117 },
118 api::v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
119 column_name: i.column_name,
120 },
121 api::v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
122 column_name: s.column_name,
123 options: SkippingIndexOptions::new(
124 s.granularity as u32,
125 s.false_positive_rate,
126 as_skipping_index_type(
127 PbSkippingIndexType::try_from(s.skipping_index_type)
128 .context(InvalidSetSkippingIndexOptionRequestSnafu)?,
129 ),
130 )
131 .context(InvalidIndexOptionSnafu)?,
132 },
133 })
134}
135
136fn unset_index_option_from_proto(unset_index: api::v1::UnsetIndex) -> Result<UnsetIndexOption> {
137 let options = unset_index.options.context(MissingAlterIndexOptionSnafu)?;
138 Ok(match options {
139 api::v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
140 column_name: f.column_name,
141 },
142 api::v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
143 column_name: i.column_name,
144 },
145 api::v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
146 column_name: s.column_name,
147 },
148 })
149}
150
151pub fn alter_expr_to_request(
155 table_id: TableId,
156 expr: AlterTableExpr,
157 table_meta: Option<&TableMeta>,
158) -> Result<AlterTableRequest> {
159 let catalog_name = expr.catalog_name;
160 let schema_name = expr.schema_name;
161 let kind = expr.kind.context(MissingFieldSnafu { field: "kind" })?;
162 let alter_kind = match kind {
163 Kind::AddColumns(add_columns) => {
164 let add_column_requests = add_columns
165 .add_columns
166 .into_iter()
167 .map(|ac| {
168 let column_def = ac.column_def.context(MissingFieldSnafu {
169 field: "column_def",
170 })?;
171
172 let schema = column_def::try_as_column_schema(&column_def).context(
173 InvalidColumnDefSnafu {
174 column: &column_def.name,
175 },
176 )?;
177 Ok(AddColumnRequest {
178 column_schema: schema,
179 is_key: column_def.semantic_type == SemanticType::Tag as i32,
180 location: parse_location(ac.location)?,
181 add_if_not_exists: ac.add_if_not_exists,
182 })
183 })
184 .collect::<Result<Vec<_>>>()?;
185
186 AlterKind::AddColumns {
187 columns: add_column_requests,
188 }
189 }
190 Kind::ModifyColumnTypes(ModifyColumnTypes {
191 modify_column_types,
192 }) => {
193 let modify_column_type_requests = modify_column_types
194 .into_iter()
195 .map(|cct| {
196 let target_type =
197 ColumnDataTypeWrapper::new(cct.target_type(), cct.target_type_extension)
198 .into();
199
200 Ok(ModifyColumnTypeRequest {
201 column_name: cct.column_name,
202 target_type,
203 })
204 })
205 .collect::<Result<Vec<_>>>()?;
206
207 AlterKind::ModifyColumnTypes {
208 columns: modify_column_type_requests,
209 }
210 }
211 Kind::DropColumns(DropColumns { drop_columns }) => AlterKind::DropColumns {
212 names: drop_columns.into_iter().map(|c| c.name).collect(),
213 },
214 Kind::RenameTable(RenameTable { new_table_name }) => {
215 AlterKind::RenameTable { new_table_name }
216 }
217 Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
218 if let Some(family) = annotation_family_of_keys(
219 table_options.iter().map(|option| option.key.as_str()),
220 )? {
221 AlterKind::SetAnnotations {
222 family,
223 options: table_options
224 .into_iter()
225 .map(|option| (option.key, option.value))
226 .collect(),
227 }
228 } else {
229 AlterKind::SetTableOptions {
230 options: table_options
231 .iter()
232 .map(SetRegionOption::try_from)
233 .collect::<std::result::Result<Vec<_>, _>>()
234 .context(InvalidSetTableOptionRequestSnafu)?,
235 }
236 }
237 }
238 Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
239 if let Some(family) = annotation_family_of_keys(keys.iter().map(|key| key.as_str()))? {
240 AlterKind::UnsetAnnotations { family, keys }
241 } else {
242 AlterKind::UnsetTableOptions {
243 keys: keys
244 .iter()
245 .map(|key| UnsetRegionOption::try_from(key.as_str()))
246 .collect::<std::result::Result<Vec<_>, _>>()
247 .context(InvalidUnsetTableOptionRequestSnafu)?,
248 }
249 }
250 }
251 Kind::SetIndex(o) => {
252 let option = set_index_option_from_proto(o)?;
253 AlterKind::SetIndexes {
254 options: vec![option],
255 }
256 }
257 Kind::UnsetIndex(o) => {
258 let option = unset_index_option_from_proto(o)?;
259 AlterKind::UnsetIndexes {
260 options: vec![option],
261 }
262 }
263 Kind::SetIndexes(o) => {
264 let options = o
265 .set_indexes
266 .into_iter()
267 .map(set_index_option_from_proto)
268 .collect::<Result<Vec<_>>>()?;
269 AlterKind::SetIndexes { options }
270 }
271 Kind::UnsetIndexes(o) => {
272 let options = o
273 .unset_indexes
274 .into_iter()
275 .map(unset_index_option_from_proto)
276 .collect::<Result<Vec<_>>>()?;
277 AlterKind::UnsetIndexes { options }
278 }
279 Kind::DropDefaults(o) => {
280 let names = o
281 .drop_defaults
282 .into_iter()
283 .map(|col| {
284 ensure!(
285 !col.column_name.is_empty(),
286 MissingFieldSnafu {
287 field: "column_name"
288 }
289 );
290 Ok(col.column_name)
291 })
292 .collect::<Result<Vec<_>>>()?;
293 AlterKind::DropDefaults { names }
294 }
295 Kind::SetDefaults(o) => {
296 let table_meta = table_meta.context(MissingTableMetaSnafu { table_id })?;
297 let defaults = o
298 .set_defaults
299 .into_iter()
300 .map(|col| {
301 let column_scheme = table_meta
302 .schema
303 .column_schema_by_name(&col.column_name)
304 .context(ColumnNotFoundSnafu {
305 column_name: &col.column_name,
306 })?;
307 let default_constraint = common_sql::convert::deserialize_default_constraint(
308 col.default_constraint.as_slice(),
309 &col.column_name,
310 &column_scheme.data_type,
311 )
312 .context(crate::error::SqlCommonSnafu)?;
313 Ok(SetDefaultRequest {
314 column_name: col.column_name,
315 default_constraint,
316 })
317 })
318 .collect::<Result<Vec<_>>>()?;
319 AlterKind::SetDefaults { defaults }
320 }
321 Kind::Repartition(_) => error::UnexpectedSnafu {
322 err_msg: "Repartition operation should be handled through DdlManager and not converted to AlterTableRequest",
323 }
324 .fail()?,
325 };
326
327 let request = AlterTableRequest {
328 catalog_name,
329 schema_name,
330 table_name: expr.table_name,
331 table_id,
332 alter_kind,
333 table_version: None,
334 };
335 Ok(request)
336}
337
338pub fn create_table_schema(expr: &CreateTableExpr, require_time_index: bool) -> Result<Schema> {
339 let column_schemas = expr
340 .column_defs
341 .iter()
342 .map(|x| {
343 column_def::try_as_column_schema(x).context(InvalidColumnDefSnafu { column: &x.name })
344 })
345 .collect::<Result<Vec<ColumnSchema>>>()?;
346
347 if require_time_index {
349 ensure!(
350 column_schemas
351 .iter()
352 .any(|column| column.name == expr.time_index),
353 MissingTimestampColumnSnafu {
354 msg: format!("CreateExpr: {expr:?}")
355 }
356 );
357 }
358
359 let column_schemas = column_schemas
360 .into_iter()
361 .map(|column_schema| {
362 if column_schema.name == expr.time_index {
363 column_schema.with_time_index(true)
364 } else {
365 column_schema
366 }
367 })
368 .collect::<Vec<_>>();
369
370 Ok(Schema::new(column_schemas))
371}
372
373fn parse_location(location: Option<Location>) -> Result<Option<AddColumnLocation>> {
374 match location {
375 Some(Location {
376 location_type: LOCATION_TYPE_FIRST,
377 ..
378 }) => Ok(Some(AddColumnLocation::First)),
379 Some(Location {
380 location_type: LOCATION_TYPE_AFTER,
381 after_column_name,
382 }) => Ok(Some(AddColumnLocation::After {
383 column_name: after_column_name,
384 })),
385 Some(Location { location_type, .. }) => UnknownLocationTypeSnafu { location_type }.fail(),
386 None => Ok(None),
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use api::v1::{
393 AddColumn, AddColumns, ColumnDataType, ColumnDef, DropColumn, ModifyColumnType,
394 Option as PbOption, SemanticType, SetTableOptions, UnsetTableOptions,
395 };
396 use datatypes::prelude::ConcreteDataType;
397 use table::requests::REPARTITION_COLUMN_HINT_KEY;
398
399 use super::*;
400
401 #[test]
402 fn test_alter_expr_to_request() {
403 let expr = AlterTableExpr {
404 catalog_name: String::default(),
405 schema_name: String::default(),
406 table_name: "monitor".to_string(),
407
408 kind: Some(Kind::AddColumns(AddColumns {
409 add_columns: vec![AddColumn {
410 column_def: Some(ColumnDef {
411 name: "mem_usage".to_string(),
412 data_type: ColumnDataType::Float64 as i32,
413 is_nullable: false,
414 default_constraint: vec![],
415 semantic_type: SemanticType::Field as i32,
416 comment: String::new(),
417 ..Default::default()
418 }),
419 location: None,
420 add_if_not_exists: true,
421 }],
422 })),
423 };
424
425 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
426 assert_eq!(alter_request.catalog_name, "");
427 assert_eq!(alter_request.schema_name, "");
428 assert_eq!("monitor".to_string(), alter_request.table_name);
429 let add_column = match alter_request.alter_kind {
430 AlterKind::AddColumns { mut columns } => columns.pop().unwrap(),
431 _ => unreachable!(),
432 };
433
434 assert!(!add_column.is_key);
435 assert_eq!("mem_usage", add_column.column_schema.name);
436 assert_eq!(
437 ConcreteDataType::float64_datatype(),
438 add_column.column_schema.data_type
439 );
440 assert_eq!(None, add_column.location);
441 assert!(add_column.add_if_not_exists);
442 }
443
444 #[test]
445 fn test_alter_expr_with_location_to_request() {
446 let expr = AlterTableExpr {
447 catalog_name: String::default(),
448 schema_name: String::default(),
449 table_name: "monitor".to_string(),
450
451 kind: Some(Kind::AddColumns(AddColumns {
452 add_columns: vec![
453 AddColumn {
454 column_def: Some(ColumnDef {
455 name: "mem_usage".to_string(),
456 data_type: ColumnDataType::Float64 as i32,
457 is_nullable: false,
458 default_constraint: vec![],
459 semantic_type: SemanticType::Field as i32,
460 comment: String::new(),
461 ..Default::default()
462 }),
463 location: Some(Location {
464 location_type: LocationType::First.into(),
465 after_column_name: String::default(),
466 }),
467 add_if_not_exists: false,
468 },
469 AddColumn {
470 column_def: Some(ColumnDef {
471 name: "cpu_usage".to_string(),
472 data_type: ColumnDataType::Float64 as i32,
473 is_nullable: false,
474 default_constraint: vec![],
475 semantic_type: SemanticType::Field as i32,
476 comment: String::new(),
477 ..Default::default()
478 }),
479 location: Some(Location {
480 location_type: LocationType::After.into(),
481 after_column_name: "ts".to_string(),
482 }),
483 add_if_not_exists: true,
484 },
485 ],
486 })),
487 };
488
489 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
490 assert_eq!(alter_request.catalog_name, "");
491 assert_eq!(alter_request.schema_name, "");
492 assert_eq!("monitor".to_string(), alter_request.table_name);
493
494 let mut add_columns = match alter_request.alter_kind {
495 AlterKind::AddColumns { columns } => columns,
496 _ => unreachable!(),
497 };
498
499 let add_column = add_columns.pop().unwrap();
500 assert!(!add_column.is_key);
501 assert_eq!("cpu_usage", add_column.column_schema.name);
502 assert_eq!(
503 ConcreteDataType::float64_datatype(),
504 add_column.column_schema.data_type
505 );
506 assert_eq!(
507 Some(AddColumnLocation::After {
508 column_name: "ts".to_string()
509 }),
510 add_column.location
511 );
512 assert!(add_column.add_if_not_exists);
513
514 let add_column = add_columns.pop().unwrap();
515 assert!(!add_column.is_key);
516 assert_eq!("mem_usage", add_column.column_schema.name);
517 assert_eq!(
518 ConcreteDataType::float64_datatype(),
519 add_column.column_schema.data_type
520 );
521 assert_eq!(Some(AddColumnLocation::First), add_column.location);
522 assert!(!add_column.add_if_not_exists);
523 }
524
525 #[test]
526 fn test_modify_column_type_expr() {
527 let expr = AlterTableExpr {
528 catalog_name: "test_catalog".to_string(),
529 schema_name: "test_schema".to_string(),
530 table_name: "monitor".to_string(),
531
532 kind: Some(Kind::ModifyColumnTypes(ModifyColumnTypes {
533 modify_column_types: vec![ModifyColumnType {
534 column_name: "mem_usage".to_string(),
535 target_type: ColumnDataType::String as i32,
536 target_type_extension: None,
537 }],
538 })),
539 };
540
541 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
542 assert_eq!(alter_request.catalog_name, "test_catalog");
543 assert_eq!(alter_request.schema_name, "test_schema");
544 assert_eq!("monitor".to_string(), alter_request.table_name);
545
546 let mut modify_column_types = match alter_request.alter_kind {
547 AlterKind::ModifyColumnTypes { columns } => columns,
548 _ => unreachable!(),
549 };
550
551 let modify_column_type = modify_column_types.pop().unwrap();
552 assert_eq!("mem_usage", modify_column_type.column_name);
553 assert_eq!(
554 ConcreteDataType::string_datatype(),
555 modify_column_type.target_type
556 );
557 }
558
559 #[test]
560 fn test_drop_column_expr() {
561 let expr = AlterTableExpr {
562 catalog_name: "test_catalog".to_string(),
563 schema_name: "test_schema".to_string(),
564 table_name: "monitor".to_string(),
565
566 kind: Some(Kind::DropColumns(DropColumns {
567 drop_columns: vec![DropColumn {
568 name: "mem_usage".to_string(),
569 }],
570 })),
571 };
572
573 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
574 assert_eq!(alter_request.catalog_name, "test_catalog");
575 assert_eq!(alter_request.schema_name, "test_schema");
576 assert_eq!("monitor".to_string(), alter_request.table_name);
577
578 let mut drop_names = match alter_request.alter_kind {
579 AlterKind::DropColumns { names } => names,
580 _ => unreachable!(),
581 };
582 assert_eq!(1, drop_names.len());
583 assert_eq!("mem_usage".to_string(), drop_names.pop().unwrap());
584 }
585
586 #[test]
587 fn test_set_repartition_column_hint_expr() {
588 let expr = AlterTableExpr {
589 catalog_name: "test_catalog".to_string(),
590 schema_name: "test_schema".to_string(),
591 table_name: "monitor".to_string(),
592 kind: Some(Kind::SetTableOptions(SetTableOptions {
593 table_options: vec![PbOption {
594 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
595 value: "host".to_string(),
596 }],
597 })),
598 };
599
600 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
601 match alter_request.alter_kind {
602 AlterKind::SetAnnotations { family, options } => {
603 assert_eq!(AnnotationFamily::RepartitionHint, family);
604 assert_eq!(
605 vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "host".to_string())],
606 options
607 );
608 }
609 _ => unreachable!(),
610 }
611 }
612
613 #[test]
614 fn test_set_repartition_column_hint_rejects_mixed_options() {
615 let expr = AlterTableExpr {
616 catalog_name: "test_catalog".to_string(),
617 schema_name: "test_schema".to_string(),
618 table_name: "monitor".to_string(),
619 kind: Some(Kind::SetTableOptions(SetTableOptions {
620 table_options: vec![
621 PbOption {
622 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
623 value: "host".to_string(),
624 },
625 PbOption {
626 key: table::requests::TTL_KEY.to_string(),
627 value: "7d".to_string(),
628 },
629 ],
630 })),
631 };
632
633 let err = alter_expr_to_request(1, expr, None).unwrap_err();
634 assert!(
635 err.to_string()
636 .contains("repartition.column.hint must be altered separately")
637 );
638
639 let dup = AlterTableExpr {
641 catalog_name: "test_catalog".to_string(),
642 schema_name: "test_schema".to_string(),
643 table_name: "monitor".to_string(),
644 kind: Some(Kind::SetTableOptions(SetTableOptions {
645 table_options: vec![
646 PbOption {
647 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
648 value: "host".to_string(),
649 },
650 PbOption {
651 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
652 value: "region".to_string(),
653 },
654 ],
655 })),
656 };
657 let err = alter_expr_to_request(1, dup, None).unwrap_err();
658 assert!(
659 err.to_string()
660 .contains("repartition.column.hint must be altered separately")
661 );
662 }
663
664 #[test]
665 fn test_unset_repartition_column_hint_expr() {
666 let expr = AlterTableExpr {
667 catalog_name: "test_catalog".to_string(),
668 schema_name: "test_schema".to_string(),
669 table_name: "monitor".to_string(),
670 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
671 keys: vec![REPARTITION_COLUMN_HINT_KEY.to_string()],
672 })),
673 };
674
675 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
676 match alter_request.alter_kind {
677 AlterKind::UnsetAnnotations { family, keys } => {
678 assert_eq!(AnnotationFamily::RepartitionHint, family);
679 assert_eq!(vec![REPARTITION_COLUMN_HINT_KEY.to_string()], keys);
680 }
681 _ => unreachable!(),
682 }
683 }
684
685 #[test]
686 fn test_semantic_options_classified_as_annotations() {
687 let expr = AlterTableExpr {
688 catalog_name: "test_catalog".to_string(),
689 schema_name: "test_schema".to_string(),
690 table_name: "monitor".to_string(),
691 kind: Some(Kind::SetTableOptions(SetTableOptions {
692 table_options: vec![
693 PbOption {
694 key: "greptime.semantic.signal_type".to_string(),
695 value: "trace".to_string(),
696 },
697 PbOption {
698 key: "greptime.semantic.entity.host.id".to_string(),
699 value: "host".to_string(),
700 },
701 ],
702 })),
703 };
704
705 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
706 let AlterKind::SetAnnotations { family, options } = alter_request.alter_kind else {
707 panic!(
708 "expected SetAnnotations, got {:?}",
709 alter_request.alter_kind
710 );
711 };
712 assert_eq!(family, AnnotationFamily::Semantic);
713 assert_eq!(options.len(), 2);
714
715 let expr = AlterTableExpr {
716 catalog_name: "test_catalog".to_string(),
717 schema_name: "test_schema".to_string(),
718 table_name: "monitor".to_string(),
719 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
720 keys: vec!["greptime.semantic.signal_type".to_string()],
721 })),
722 };
723 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
724 assert!(matches!(
725 alter_request.alter_kind,
726 AlterKind::UnsetAnnotations {
727 family: AnnotationFamily::Semantic,
728 ..
729 }
730 ));
731 }
732
733 #[test]
734 fn test_semantic_options_reject_mixed_batch() {
735 let mixed_set = AlterTableExpr {
736 catalog_name: "test_catalog".to_string(),
737 schema_name: "test_schema".to_string(),
738 table_name: "monitor".to_string(),
739 kind: Some(Kind::SetTableOptions(SetTableOptions {
740 table_options: vec![
741 PbOption {
742 key: "greptime.semantic.signal_type".to_string(),
743 value: "trace".to_string(),
744 },
745 PbOption {
746 key: table::requests::TTL_KEY.to_string(),
747 value: "7d".to_string(),
748 },
749 ],
750 })),
751 };
752 let err = alter_expr_to_request(1, mixed_set, None).unwrap_err();
753 assert!(err.to_string().contains("altered separately"), "{err}");
754
755 let mixed_unset = AlterTableExpr {
756 catalog_name: "test_catalog".to_string(),
757 schema_name: "test_schema".to_string(),
758 table_name: "monitor".to_string(),
759 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
760 keys: vec![
761 "ttl".to_string(),
762 "greptime.semantic.signal_type".to_string(),
763 ],
764 })),
765 };
766 let err = alter_expr_to_request(1, mixed_unset, None).unwrap_err();
767 assert!(err.to_string().contains("altered separately"), "{err}");
768 }
769
770 #[test]
771 fn test_annotation_alter_family() {
772 let mixed = Kind::SetTableOptions(SetTableOptions {
773 table_options: vec![
774 PbOption {
775 key: "greptime.semantic.signal_type".to_string(),
776 value: "trace".to_string(),
777 },
778 PbOption {
779 key: "ttl".to_string(),
780 value: "7d".to_string(),
781 },
782 ],
783 });
784 let err = annotation_alter_family(&mixed).unwrap_err();
785 assert!(
786 err.to_string().contains("must be altered separately"),
787 "{err}"
788 );
789
790 let cross = Kind::SetTableOptions(SetTableOptions {
792 table_options: vec![
793 PbOption {
794 key: "greptime.semantic.signal_type".to_string(),
795 value: "trace".to_string(),
796 },
797 PbOption {
798 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
799 value: "host".to_string(),
800 },
801 ],
802 });
803 let err = annotation_alter_family(&cross).unwrap_err();
804 assert!(
805 err.to_string().contains("must be altered separately"),
806 "{err}"
807 );
808 }
809}