1use std::any::Any;
16use std::collections::HashMap;
17use std::time::Duration;
18
19use api::v1::value::ValueData;
20use api::v1::{ColumnSchema, Row};
21use common_event_recorder::Event;
22use common_event_recorder::error::{Result, SerializeEventSnafu};
23use common_event_recorder::event_table::{
24 CATALOG_NAME_COLUMN, PARENT_PROCEDURE_ID_COLUMN, REPARTITION_GROUP_ID_COLUMN,
25 SCHEMA_NAME_COLUMN, SOURCE_PARTITION_EXPR_COLUMN, SOURCE_REGION_ID_COLUMN,
26 SOURCE_REGION_NUMBER_COLUMN, TABLE_ID_COLUMN, TABLE_NAME_COLUMN, TARGET_PARTITION_EXPR_COLUMN,
27 TARGET_REGION_ID_COLUMN, TARGET_REGION_NUMBER_COLUMN, column_schemas, nullable_string,
28 nullable_value,
29};
30use serde::Serialize;
31use snafu::ResultExt;
32use store_api::storage::{RegionId, TableId};
33
34use crate::procedure::repartition::PersistentContext as RepartitionPersistentContext;
35use crate::procedure::repartition::group::PersistentContext as GroupPersistentContext;
36use crate::procedure::repartition::plan::{SourceRegionDescriptor, TargetRegionDescriptor};
37use crate::procedure::repartition::repartition_start::RepartitionStart;
38
39pub(crate) const REPARTITION_EVENT_TYPE: &str = "repartition";
40pub(crate) const REPARTITION_GROUP_EVENT_TYPE: &str = "repartition_group";
41
42const PAYLOAD_VERSION: u8 = 2;
43
44#[derive(Debug, Serialize)]
45struct RepartitionSubmittedPayload {
46 version: u8,
47 source_type: &'static str,
48 #[serde(skip_serializing_if = "Vec::is_empty")]
49 source_partition_exprs: Vec<String>,
50 target_partition_exprs: Vec<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 target_partition_columns: Option<Vec<String>>,
53 #[serde(with = "humantime_serde")]
54 timeout: Duration,
55}
56
57#[derive(Debug)]
58pub(crate) struct RepartitionEvent {
59 catalog_name: Option<String>,
60 schema_name: Option<String>,
61 table_name: Option<String>,
62 table_id: Option<TableId>,
63 payload: Option<RepartitionSubmittedPayload>,
64}
65
66impl RepartitionEvent {
67 pub(crate) fn submitted(
68 persistent_ctx: &RepartitionPersistentContext,
69 start: &RepartitionStart,
70 ) -> Self {
71 let intent = start.submitted_intent();
72 Self {
73 catalog_name: Some(persistent_ctx.catalog_name.clone()),
74 schema_name: Some(persistent_ctx.schema_name.clone()),
75 table_name: Some(persistent_ctx.table_name.clone()),
76 table_id: Some(persistent_ctx.table_id),
77 payload: Some(RepartitionSubmittedPayload {
78 version: PAYLOAD_VERSION,
79 source_type: intent.source_type(),
80 source_partition_exprs: intent
81 .source_partition_exprs()
82 .iter()
83 .map(|expr| expr.to_string())
84 .collect(),
85 target_partition_exprs: intent
86 .target_partition_exprs()
87 .iter()
88 .map(|expr| expr.to_string())
89 .collect(),
90 target_partition_columns: intent.target_partition_columns().map(ToOwned::to_owned),
91 timeout: persistent_ctx.timeout,
92 }),
93 }
94 }
95
96 pub(crate) fn lifecycle(persistent_ctx: &RepartitionPersistentContext) -> Self {
97 Self {
98 catalog_name: Some(persistent_ctx.catalog_name.clone()),
99 schema_name: Some(persistent_ctx.schema_name.clone()),
100 table_name: Some(persistent_ctx.table_name.clone()),
101 table_id: Some(persistent_ctx.table_id),
102 payload: None,
103 }
104 }
105
106 fn schema() -> Vec<ColumnSchema> {
107 column_schemas([
108 &CATALOG_NAME_COLUMN,
109 &SCHEMA_NAME_COLUMN,
110 &TABLE_NAME_COLUMN,
111 &TABLE_ID_COLUMN,
112 ])
113 }
114}
115
116impl Event for RepartitionEvent {
117 fn event_type(&self) -> &str {
118 REPARTITION_EVENT_TYPE
119 }
120
121 fn json_payload(&self) -> Result<serde_json::Value> {
122 self.payload
123 .as_ref()
124 .map(serde_json::to_value)
125 .transpose()
126 .context(SerializeEventSnafu)
127 .map(|payload| payload.unwrap_or(serde_json::Value::Null))
128 }
129
130 fn extra_schema(&self) -> Vec<ColumnSchema> {
131 Self::schema()
132 }
133
134 fn extra_rows(&self) -> Result<Vec<Row>> {
135 Ok(vec![Row {
136 values: vec![
137 nullable_string(self.catalog_name.as_deref()),
138 nullable_string(self.schema_name.as_deref()),
139 nullable_string(self.table_name.as_deref()),
140 nullable_value(self.table_id.map(ValueData::U32Value)),
141 ],
142 }])
143 }
144
145 fn as_any(&self) -> &dyn Any {
146 self
147 }
148}
149
150#[derive(Debug, Serialize)]
151struct RepartitionGroupSubmittedPayload {
152 version: u8,
153 sync_region: bool,
154 allocated_region_ids: Vec<u64>,
155 pending_deallocate_region_ids: Vec<u64>,
156 #[serde(with = "humantime_serde")]
157 timeout: Duration,
158}
159
160#[derive(Debug)]
161struct RepartitionTopology {
162 sources: Vec<RepartitionTopologySource>,
163 target_partition_exprs: HashMap<RegionId, String>,
164 region_mapping: HashMap<RegionId, Vec<RegionId>>,
165}
166
167#[derive(Debug)]
168struct RepartitionTopologySource {
169 region_id: RegionId,
170 partition_expr: Option<String>,
171}
172
173#[derive(Debug)]
174struct RepartitionTopologyRow<'a> {
175 source_region_id: RegionId,
176 source_partition_expr: Option<&'a str>,
177 target_region_id: RegionId,
178 target_partition_expr: Option<&'a str>,
179}
180
181impl RepartitionTopology {
182 fn new(
183 sources: &[SourceRegionDescriptor],
184 targets: &[TargetRegionDescriptor],
185 region_mapping: &HashMap<RegionId, Vec<RegionId>>,
186 ) -> Self {
187 Self {
188 sources: sources
189 .iter()
190 .map(|source| RepartitionTopologySource {
191 region_id: source.region_id(),
192 partition_expr: source.partition_expr().map(|expr| expr.to_string()),
193 })
194 .collect(),
195 target_partition_exprs: targets
196 .iter()
197 .map(|target| (target.region_id, target.partition_expr.to_string()))
198 .collect(),
199 region_mapping: region_mapping.clone(),
200 }
201 }
202
203 fn rows(&self) -> impl Iterator<Item = RepartitionTopologyRow<'_>> {
204 self.sources.iter().flat_map(|source| {
205 self.region_mapping
206 .get(&source.region_id)
207 .into_iter()
208 .flatten()
209 .map(|target_region_id| RepartitionTopologyRow {
210 source_region_id: source.region_id,
211 source_partition_expr: source.partition_expr.as_deref(),
212 target_region_id: *target_region_id,
213 target_partition_expr: self
214 .target_partition_exprs
215 .get(target_region_id)
216 .map(String::as_str),
217 })
218 })
219 }
220}
221
222#[derive(Debug)]
223pub(crate) struct RepartitionGroupEvent {
224 catalog_name: Option<String>,
225 schema_name: Option<String>,
226 table_name: Option<String>,
227 table_id: Option<TableId>,
228 parent_procedure_id: Option<String>,
229 group_id: Option<String>,
230 topology: Option<RepartitionTopology>,
231 payload: Option<RepartitionGroupSubmittedPayload>,
232}
233
234impl RepartitionGroupEvent {
235 pub(crate) fn submitted(persistent_ctx: &GroupPersistentContext) -> Self {
236 Self {
237 catalog_name: Some(persistent_ctx.catalog_name.clone()),
238 schema_name: Some(persistent_ctx.schema_name.clone()),
239 table_name: persistent_ctx.table_name.clone(),
240 table_id: Some(persistent_ctx.table_id),
241 parent_procedure_id: persistent_ctx.parent_procedure_id.map(|id| id.to_string()),
242 group_id: Some(persistent_ctx.group_id.to_string()),
243 topology: Some(RepartitionTopology::new(
244 &persistent_ctx.sources,
245 &persistent_ctx.targets,
246 &persistent_ctx.region_mapping,
247 )),
248 payload: Some(RepartitionGroupSubmittedPayload {
249 version: PAYLOAD_VERSION,
250 sync_region: persistent_ctx.sync_region,
251 allocated_region_ids: persistent_ctx
252 .allocated_region_ids
253 .iter()
254 .map(|region_id| region_id.as_u64())
255 .collect(),
256 pending_deallocate_region_ids: persistent_ctx
257 .pending_deallocate_region_ids
258 .iter()
259 .map(|region_id| region_id.as_u64())
260 .collect(),
261 timeout: persistent_ctx.timeout,
262 }),
263 }
264 }
265
266 pub(crate) fn lifecycle(persistent_ctx: &GroupPersistentContext) -> Self {
267 Self {
268 catalog_name: Some(persistent_ctx.catalog_name.clone()),
269 schema_name: Some(persistent_ctx.schema_name.clone()),
270 table_name: persistent_ctx.table_name.clone(),
271 table_id: Some(persistent_ctx.table_id),
272 parent_procedure_id: persistent_ctx.parent_procedure_id.map(|id| id.to_string()),
273 group_id: Some(persistent_ctx.group_id.to_string()),
274 topology: None,
275 payload: None,
276 }
277 }
278
279 fn extra_row(&self, topology: Option<RepartitionTopologyRow<'_>>) -> Row {
280 let (source_region_id, source_partition_expr, target_region_id, target_partition_expr) =
281 match topology {
282 Some(topology) => (
283 Some(topology.source_region_id),
284 topology.source_partition_expr,
285 Some(topology.target_region_id),
286 topology.target_partition_expr,
287 ),
288 None => (None, None, None, None),
289 };
290
291 Row {
292 values: vec![
293 nullable_string(self.catalog_name.as_deref()),
294 nullable_string(self.schema_name.as_deref()),
295 nullable_string(self.table_name.as_deref()),
296 nullable_value(self.table_id.map(ValueData::U32Value)),
297 nullable_string(self.parent_procedure_id.as_deref()),
298 nullable_string(self.group_id.as_deref()),
299 nullable_value(source_region_id.map(|id| ValueData::U64Value(id.as_u64()))),
300 nullable_value(source_region_id.map(|id| ValueData::U32Value(id.region_number()))),
301 nullable_string(source_partition_expr),
302 nullable_value(target_region_id.map(|id| ValueData::U64Value(id.as_u64()))),
303 nullable_value(target_region_id.map(|id| ValueData::U32Value(id.region_number()))),
304 nullable_string(target_partition_expr),
305 ],
306 }
307 }
308
309 fn schema() -> Vec<ColumnSchema> {
310 let mut schema = RepartitionEvent::schema();
311 schema.extend(column_schemas([
312 &PARENT_PROCEDURE_ID_COLUMN,
313 &REPARTITION_GROUP_ID_COLUMN,
314 &SOURCE_REGION_ID_COLUMN,
315 &SOURCE_REGION_NUMBER_COLUMN,
316 &SOURCE_PARTITION_EXPR_COLUMN,
317 &TARGET_REGION_ID_COLUMN,
318 &TARGET_REGION_NUMBER_COLUMN,
319 &TARGET_PARTITION_EXPR_COLUMN,
320 ]));
321 schema
322 }
323}
324
325impl Event for RepartitionGroupEvent {
326 fn event_type(&self) -> &str {
327 REPARTITION_GROUP_EVENT_TYPE
328 }
329
330 fn json_payload(&self) -> Result<serde_json::Value> {
331 self.payload
332 .as_ref()
333 .map(serde_json::to_value)
334 .transpose()
335 .context(SerializeEventSnafu)
336 .map(|payload| payload.unwrap_or(serde_json::Value::Null))
337 }
338
339 fn extra_schema(&self) -> Vec<ColumnSchema> {
340 Self::schema()
341 }
342
343 fn extra_rows(&self) -> Result<Vec<Row>> {
344 match &self.topology {
345 Some(topology) => Ok(topology
346 .rows()
347 .map(|row| self.extra_row(Some(row)))
348 .collect()),
349 None => Ok(vec![self.extra_row(None)]),
350 }
351 }
352
353 fn as_any(&self) -> &dyn Any {
354 self
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use std::collections::HashMap;
361 use std::time::Duration;
362
363 use api::v1::value::ValueData;
364 use api::v1::{ColumnSchema, Row, Value};
365 use common_event_recorder::Event;
366 use common_event_recorder::event_table::{
367 ACTOR_COLUMN, EVENT_CONTEXT_COLUMN, PROCEDURE_ERROR_COLUMN, PROCEDURE_ID_COLUMN,
368 PROCEDURE_STATE_COLUMN, PROCEDURE_TRIGGER_COLUMN, jsonb_value,
369 };
370 use common_event_recorder::testing::assert_event_contract;
371 use common_procedure::{EventTrigger, ProcedureEvent, ProcedureId, ProcedureState};
372 use table::table_name::TableName;
373 use uuid::Uuid;
374
375 use super::*;
376 use crate::procedure::repartition::repartition_start::RepartitionFrom;
377 use crate::procedure::repartition::test_util::{new_persistent_context, range_expr};
378
379 fn expr(start: i64, end: i64) -> partition::expr::PartitionExpr {
380 range_expr("host", start, end)
381 }
382
383 fn parent_persistent_ctx() -> RepartitionPersistentContext {
384 RepartitionPersistentContext::new(
385 TableName::new("greptime", "public", "repartition_events"),
386 1024,
387 Some(Duration::from_secs(30)),
388 )
389 }
390
391 #[test]
392 fn test_parent_submitted_payload_preserves_repartition_semantics() {
393 let unpartitioned = RepartitionEvent::submitted(
394 &parent_persistent_ctx(),
395 &RepartitionStart::new(
396 RepartitionFrom::Unpartitioned {
397 partition_columns: vec!["host".to_string()],
398 },
399 vec![expr(0, 100)],
400 ),
401 );
402 let partitioned = RepartitionEvent::submitted(
403 &parent_persistent_ctx(),
404 &RepartitionStart::new(
405 RepartitionFrom::Partitioned {
406 exprs: vec![expr(0, 100)],
407 target_partition_columns: Some(vec!["host".to_string()]),
408 },
409 vec![expr(0, 50), expr(50, 100)],
410 ),
411 );
412
413 assert_event_contract(
414 &unpartitioned,
415 REPARTITION_EVENT_TYPE,
416 &parent_schema(),
417 &[Row {
418 values: vec![
419 ValueData::StringValue("greptime".to_string()).into(),
420 ValueData::StringValue("public".to_string()).into(),
421 ValueData::StringValue("repartition_events".to_string()).into(),
422 ValueData::U32Value(1024).into(),
423 ],
424 }],
425 );
426
427 let unpartitioned_payload = unpartitioned.json_payload().unwrap();
428 let partitioned_payload = partitioned.json_payload().unwrap();
429 assert_eq!(unpartitioned_payload["version"], PAYLOAD_VERSION);
430 assert_eq!(unpartitioned_payload["source_type"], "unpartitioned");
431 assert!(
432 unpartitioned_payload
433 .get("source_partition_exprs")
434 .is_none()
435 );
436 assert_eq!(
437 unpartitioned_payload["target_partition_columns"],
438 serde_json::json!(["host"])
439 );
440 assert_eq!(partitioned_payload["source_type"], "partitioned");
441 assert_eq!(
442 partitioned_payload["source_partition_exprs"],
443 serde_json::json!([expr(0, 100).to_string()])
444 );
445 assert_eq!(
446 partitioned_payload["target_partition_columns"],
447 serde_json::json!(["host"])
448 );
449
450 let merge = RepartitionEvent::submitted(
451 &parent_persistent_ctx(),
452 &RepartitionStart::new(
453 RepartitionFrom::Partitioned {
454 exprs: vec![expr(0, 50), expr(50, 100)],
455 target_partition_columns: None,
456 },
457 vec![expr(0, 100)],
458 ),
459 );
460 assert!(
461 merge
462 .json_payload()
463 .unwrap()
464 .get("target_partition_columns")
465 .is_none()
466 );
467 }
468
469 #[test]
470 fn test_topology_rows_expand_one_source_to_multiple_targets() {
471 let table_id = 1024;
472 let source = RegionId::new(table_id, 1);
473 let left = RegionId::new(table_id, 2);
474 let right = RegionId::new(table_id, 3);
475 let source_expr = expr(0, 100);
476 let targets = vec![
477 TargetRegionDescriptor {
478 region_id: left,
479 partition_expr: expr(0, 50),
480 },
481 TargetRegionDescriptor {
482 region_id: right,
483 partition_expr: expr(50, 100),
484 },
485 ];
486 let topology = RepartitionTopology::new(
487 &[SourceRegionDescriptor::partitioned(
488 source,
489 source_expr.clone(),
490 )],
491 &targets,
492 &HashMap::from([(source, vec![left, right])]),
493 );
494 let rows = topology.rows().collect::<Vec<_>>();
495 let source_expr = source_expr.to_string();
496
497 assert_eq!(rows.len(), 2);
498 assert!(rows.iter().all(|row| row.source_region_id == source));
499 assert_eq!(rows[0].source_partition_expr, Some(source_expr.as_str()));
500 assert_eq!(rows[0].target_region_id, left);
501 assert_eq!(rows[1].target_region_id, right);
502 }
503
504 #[test]
505 fn test_group_submitted_event_contract() {
506 let table_id = 1024;
507 let source = RegionId::new(table_id, 1);
508 let left = RegionId::new(table_id, 2);
509 let right = RegionId::new(table_id, 3);
510 let source_expr = expr(0, 100);
511 let left_expr = expr(0, 50);
512 let right_expr = expr(50, 100);
513 let parent_procedure_id =
514 ProcedureId::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
515 let group_id = Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap();
516 let mut persistent_ctx = new_persistent_context(
517 table_id,
518 vec![SourceRegionDescriptor::partitioned(
519 source,
520 source_expr.clone(),
521 )],
522 vec![
523 TargetRegionDescriptor {
524 region_id: left,
525 partition_expr: left_expr.clone(),
526 },
527 TargetRegionDescriptor {
528 region_id: right,
529 partition_expr: right_expr.clone(),
530 },
531 ],
532 );
533 persistent_ctx.parent_procedure_id = Some(parent_procedure_id);
534 persistent_ctx.group_id = group_id;
535 persistent_ctx.region_mapping = HashMap::from([(source, vec![left, right])]);
536
537 let event = RepartitionGroupEvent::submitted(&persistent_ctx);
538
539 assert_event_contract(
540 &event,
541 REPARTITION_GROUP_EVENT_TYPE,
542 &group_schema(),
543 &[
544 group_row(
545 parent_procedure_id,
546 group_id,
547 source,
548 &source_expr.to_string(),
549 left,
550 &left_expr.to_string(),
551 ),
552 group_row(
553 parent_procedure_id,
554 group_id,
555 source,
556 &source_expr.to_string(),
557 right,
558 &right_expr.to_string(),
559 ),
560 ],
561 );
562 }
563
564 #[test]
565 fn test_topology_rows_expand_multiple_sources_to_one_target() {
566 let table_id = 1024;
567 let left = RegionId::new(table_id, 1);
568 let right = RegionId::new(table_id, 2);
569 let merged = RegionId::new(table_id, 3);
570 let topology = RepartitionTopology::new(
571 &[
572 SourceRegionDescriptor::partitioned(left, expr(0, 50)),
573 SourceRegionDescriptor::partitioned(right, expr(50, 100)),
574 ],
575 &[TargetRegionDescriptor {
576 region_id: merged,
577 partition_expr: expr(0, 100),
578 }],
579 &HashMap::from([(left, vec![merged]), (right, vec![merged])]),
580 );
581 let rows = topology.rows().collect::<Vec<_>>();
582
583 assert_eq!(rows.len(), 2);
584 assert_eq!(rows[0].source_region_id, left);
585 assert_eq!(rows[1].source_region_id, right);
586 assert!(rows.iter().all(|row| row.target_region_id == merged));
587 }
588
589 #[test]
590 fn test_topology_rows_keep_default_source_expr_null() {
591 let table_id = 1024;
592 let source = RegionId::new(table_id, 0);
593 let target = RegionId::new(table_id, 1);
594 let topology = RepartitionTopology::new(
595 &[SourceRegionDescriptor::Default { region_id: source }],
596 &[TargetRegionDescriptor {
597 region_id: target,
598 partition_expr: expr(0, 100),
599 }],
600 &HashMap::from([(source, vec![target])]),
601 );
602 let rows = topology.rows().collect::<Vec<_>>();
603 let target_partition_expr = expr(0, 100).to_string();
604
605 assert_eq!(rows.len(), 1);
606 assert_eq!(rows[0].source_partition_expr, None);
607 assert_eq!(rows[0].source_region_id.region_number(), 0);
608 assert_eq!(rows[0].target_region_id.region_number(), 1);
609 assert_eq!(
610 rows[0].target_partition_expr,
611 Some(target_partition_expr.as_str())
612 );
613 }
614
615 #[test]
616 fn test_topology_rows_skip_empty_mapping() {
617 let topology = RepartitionTopology::new(&[], &[], &HashMap::new());
618
619 assert!(topology.rows().next().is_none());
620 }
621
622 #[test]
623 fn test_lifecycle_events_preserve_locators_and_null_payloads() {
624 let parent_ctx = parent_persistent_ctx();
625 let parent = RepartitionEvent::lifecycle(&parent_ctx);
626 assert_event_contract(
627 &parent,
628 REPARTITION_EVENT_TYPE,
629 &parent_schema(),
630 &[Row {
631 values: vec![
632 ValueData::StringValue("greptime".to_string()).into(),
633 ValueData::StringValue("public".to_string()).into(),
634 ValueData::StringValue("repartition_events".to_string()).into(),
635 ValueData::U32Value(1024).into(),
636 ],
637 }],
638 );
639 assert_eq!(parent.json_payload().unwrap(), serde_json::Value::Null);
640
641 let group_ctx = new_persistent_context(1024, vec![], vec![]);
642 let group = RepartitionGroupEvent::lifecycle(&group_ctx);
643 assert_event_contract(
644 &group,
645 REPARTITION_GROUP_EVENT_TYPE,
646 &group_schema(),
647 &[group.extra_row(None)],
648 );
649 assert_eq!(group.json_payload().unwrap(), serde_json::Value::Null);
650 }
651
652 #[test]
653 fn test_repartition_event_preserves_procedure_envelope_contract() {
654 let procedure_id = ProcedureId::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
655 let event = ProcedureEvent::new(
656 procedure_id,
657 Box::new(RepartitionEvent::submitted(
658 &parent_persistent_ctx(),
659 &RepartitionStart::new(
660 RepartitionFrom::Unpartitioned {
661 partition_columns: vec!["host".to_string()],
662 },
663 vec![expr(0, 100)],
664 ),
665 )),
666 ProcedureState::Running,
667 EventTrigger::Submitted,
668 );
669 let mut schema = procedure_schema();
670 schema.extend(parent_schema());
671 schema.push(ACTOR_COLUMN.column_schema());
672 schema.push(EVENT_CONTEXT_COLUMN.column_schema());
673
674 assert_event_contract(
675 &event,
676 REPARTITION_EVENT_TYPE,
677 &schema,
678 &[Row {
679 values: vec![
680 ValueData::StringValue(procedure_id.to_string()).into(),
681 ValueData::StringValue("Running".to_string()).into(),
682 ValueData::StringValue(String::new()).into(),
683 jsonb_value(&serde_json::json!({"type": "Submitted"})),
684 ValueData::StringValue("greptime".to_string()).into(),
685 ValueData::StringValue("public".to_string()).into(),
686 ValueData::StringValue("repartition_events".to_string()).into(),
687 ValueData::U32Value(1024).into(),
688 Value { value_data: None },
689 Value { value_data: None },
690 ],
691 }],
692 );
693 }
694
695 fn parent_schema() -> Vec<ColumnSchema> {
696 column_schemas([
697 &CATALOG_NAME_COLUMN,
698 &SCHEMA_NAME_COLUMN,
699 &TABLE_NAME_COLUMN,
700 &TABLE_ID_COLUMN,
701 ])
702 }
703
704 fn group_schema() -> Vec<ColumnSchema> {
705 let mut schema = parent_schema();
706 schema.extend(column_schemas([
707 &PARENT_PROCEDURE_ID_COLUMN,
708 &REPARTITION_GROUP_ID_COLUMN,
709 &SOURCE_REGION_ID_COLUMN,
710 &SOURCE_REGION_NUMBER_COLUMN,
711 &SOURCE_PARTITION_EXPR_COLUMN,
712 &TARGET_REGION_ID_COLUMN,
713 &TARGET_REGION_NUMBER_COLUMN,
714 &TARGET_PARTITION_EXPR_COLUMN,
715 ]));
716 schema
717 }
718
719 fn procedure_schema() -> Vec<ColumnSchema> {
720 column_schemas([
721 &PROCEDURE_ID_COLUMN,
722 &PROCEDURE_STATE_COLUMN,
723 &PROCEDURE_ERROR_COLUMN,
724 &PROCEDURE_TRIGGER_COLUMN,
725 ])
726 }
727
728 fn group_row(
729 parent_procedure_id: ProcedureId,
730 group_id: Uuid,
731 source_region_id: RegionId,
732 source_partition_expr: &str,
733 target_region_id: RegionId,
734 target_partition_expr: &str,
735 ) -> Row {
736 Row {
737 values: vec![
738 ValueData::StringValue("test_catalog".to_string()).into(),
739 ValueData::StringValue("test_schema".to_string()).into(),
740 ValueData::StringValue("test_table".to_string()).into(),
741 ValueData::U32Value(source_region_id.table_id()).into(),
742 ValueData::StringValue(parent_procedure_id.to_string()).into(),
743 ValueData::StringValue(group_id.to_string()).into(),
744 ValueData::U64Value(source_region_id.as_u64()).into(),
745 ValueData::U32Value(source_region_id.region_number()).into(),
746 ValueData::StringValue(source_partition_expr.to_string()).into(),
747 ValueData::U64Value(target_region_id.as_u64()).into(),
748 ValueData::U32Value(target_region_id.region_number()).into(),
749 ValueData::StringValue(target_partition_expr.to_string()).into(),
750 ],
751 }
752 }
753}