1pub(crate) mod reconcile_regions;
16pub(crate) mod reconciliation_end;
17pub(crate) mod reconciliation_start;
18pub(crate) mod resolve_column_metadata;
19pub(crate) mod update_table_info;
20
21use std::fmt::Debug;
22
23use common_error::ext::ErrorExt;
24use common_procedure::error::{FromJsonSnafu, ToJsonSnafu};
25use common_procedure::{
26 Context as ProcedureContext, Error as ProcedureError, EventContext, EventTrigger, LockKey,
27 Procedure, Result as ProcedureResult, Status,
28};
29use serde::{Deserialize, Serialize};
30use snafu::ResultExt;
31use store_api::metadata::ColumnMetadata;
32use store_api::storage::TableId;
33use table::metadata::TableMeta;
34use table::table_name::TableName;
35use tonic::async_trait;
36
37use crate::cache_invalidator::CacheInvalidatorRef;
38use crate::error::Result;
39use crate::key::table_info::TableInfoValue;
40use crate::key::table_route::PhysicalTableRouteValue;
41use crate::key::{DeserializedValueWithBytes, TableMetadataManagerRef};
42use crate::lock_key::{CatalogLock, SchemaLock, TableNameLock};
43use crate::metrics;
44use crate::node_manager::NodeManagerRef;
45use crate::reconciliation::event::{
46 RECONCILE_TABLE_EVENT_TYPE, ReconcileTableEvent, ReconciliationLocator,
47};
48use crate::reconciliation::reconcile_table::reconciliation_start::ReconciliationStart;
49use crate::reconciliation::reconcile_table::resolve_column_metadata::ResolveStrategy;
50use crate::reconciliation::utils::{
51 Context, ReconcileTableMetrics, build_table_meta_from_column_metadatas,
52};
53
54pub struct ReconcileTableContext {
55 pub node_manager: NodeManagerRef,
56 pub table_metadata_manager: TableMetadataManagerRef,
57 pub cache_invalidator: CacheInvalidatorRef,
58 pub persistent_ctx: PersistentContext,
59 pub volatile_ctx: VolatileContext,
60}
61
62#[derive(Debug, Clone, Copy)]
63enum TablePhase {
64 Start,
65 ResolveColumnMetadata,
66 ReconcileRegions,
67 UpdateTableInfo,
68}
69
70impl TablePhase {
71 const fn as_event_value(self) -> &'static str {
72 match self {
73 Self::Start => "start",
74 Self::ResolveColumnMetadata => "resolve_column_metadata",
75 Self::ReconcileRegions => "reconcile_regions",
76 Self::UpdateTableInfo => "update_table_info",
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy)]
82enum TableMetadataState {
83 Consistent,
84 Inconsistent,
85}
86
87impl TableMetadataState {
88 const fn as_event_value(self) -> &'static str {
89 match self {
90 Self::Consistent => "consistent",
91 Self::Inconsistent => "inconsistent",
92 }
93 }
94}
95
96#[derive(Debug, Default)]
97struct ReconcileTableResultSummary {
98 metadata_state: Option<TableMetadataState>,
99 resolution_strategy_applied: Option<ResolveStrategy>,
100 resolved_column_count: Option<usize>,
101 scanned_region_count: usize,
102 updated_region_count: usize,
103 table_info_updated: bool,
104 last_completed_phase: Option<TablePhase>,
105}
106
107impl ReconcileTableResultSummary {
108 fn record_scanned_regions(&mut self, scanned_region_count: usize) {
109 self.scanned_region_count = scanned_region_count;
110 }
111
112 fn mark_start_completed(&mut self) {
113 self.last_completed_phase = Some(TablePhase::Start);
114 }
115
116 fn record_metadata_state(&mut self, metadata_state: TableMetadataState) {
117 self.metadata_state = Some(metadata_state);
118 }
119
120 fn record_resolution_strategy(&mut self, resolve_strategy: ResolveStrategy) {
121 self.resolution_strategy_applied = Some(resolve_strategy);
122 }
123
124 fn record_resolved_columns(
125 &mut self,
126 metadata_state: TableMetadataState,
127 resolution_strategy_applied: Option<ResolveStrategy>,
128 resolved_column_count: Option<usize>,
129 ) {
130 self.metadata_state = Some(metadata_state);
131 self.resolution_strategy_applied = resolution_strategy_applied;
132 self.resolved_column_count = resolved_column_count;
133 self.last_completed_phase = Some(TablePhase::ResolveColumnMetadata);
134 }
135
136 fn record_updated_regions(&mut self, updated_region_count: usize) {
137 self.updated_region_count = updated_region_count;
138 }
139
140 fn mark_region_phase_completed(&mut self) {
141 self.last_completed_phase = Some(TablePhase::ReconcileRegions);
142 }
143
144 fn mark_table_info_updated(&mut self) {
145 self.table_info_updated = true;
146 }
147
148 fn mark_table_info_phase_completed(&mut self) {
149 self.last_completed_phase = Some(TablePhase::UpdateTableInfo);
150 }
151
152 fn metadata_state(&self) -> Option<&'static str> {
153 self.metadata_state.map(TableMetadataState::as_event_value)
154 }
155
156 fn last_completed_phase(&self) -> Option<&'static str> {
157 self.last_completed_phase.map(TablePhase::as_event_value)
158 }
159}
160
161impl ReconcileTableContext {
162 pub fn new(ctx: Context, persistent_ctx: PersistentContext) -> Self {
164 Self {
165 node_manager: ctx.node_manager,
166 table_metadata_manager: ctx.table_metadata_manager,
167 cache_invalidator: ctx.cache_invalidator,
168 persistent_ctx,
169 volatile_ctx: VolatileContext::default(),
170 }
171 }
172
173 pub(crate) fn table_name(&self) -> &TableName {
175 &self.persistent_ctx.table_name
176 }
177
178 pub(crate) fn table_id(&self) -> TableId {
180 self.persistent_ctx.table_id
181 }
182
183 pub(crate) fn build_table_meta(
185 &self,
186 column_metadatas: &[ColumnMetadata],
187 ) -> Result<TableMeta> {
188 let table_info_value = self.persistent_ctx.table_info_value.as_ref().unwrap();
190 let table_id = self.table_id();
191 let table_ref = self.table_name().table_ref();
192 let name_to_ids = table_info_value.table_info.name_to_ids();
193 let table_meta = build_table_meta_from_column_metadatas(
194 table_id,
195 table_ref,
196 &table_info_value.table_info.meta,
197 name_to_ids,
198 column_metadatas,
199 )?;
200
201 Ok(table_meta)
202 }
203
204 pub(crate) fn mut_metrics(&mut self) -> &mut ReconcileTableMetrics {
206 &mut self.volatile_ctx.metrics
207 }
208
209 pub(crate) fn metrics(&self) -> &ReconcileTableMetrics {
211 &self.volatile_ctx.metrics
212 }
213}
214
215#[derive(Debug, Serialize, Deserialize)]
216pub(crate) struct PersistentContext {
217 pub(crate) table_id: TableId,
218 pub(crate) table_name: TableName,
219 pub(crate) resolve_strategy: ResolveStrategy,
220 pub(crate) table_info_value: Option<DeserializedValueWithBytes<TableInfoValue>>,
223 pub(crate) physical_table_route: Option<PhysicalTableRouteValue>,
226 pub(crate) is_subprocedure: bool,
228}
229
230impl PersistentContext {
231 pub(crate) fn new(
232 table_id: TableId,
233 table_name: TableName,
234 resolve_strategy: ResolveStrategy,
235 is_subprocedure: bool,
236 ) -> Self {
237 Self {
238 table_id,
239 table_name,
240 resolve_strategy,
241 table_info_value: None,
242 physical_table_route: None,
243 is_subprocedure,
244 }
245 }
246}
247
248#[derive(Default)]
249pub(crate) struct VolatileContext {
250 pub(crate) table_meta: Option<TableMeta>,
251 pub(crate) metrics: ReconcileTableMetrics,
252 result_summary: ReconcileTableResultSummary,
253}
254
255pub struct ReconcileTableProcedure {
256 pub context: ReconcileTableContext,
257 state: Box<dyn State>,
258}
259
260impl ReconcileTableProcedure {
261 pub fn new(
263 ctx: Context,
264 table_id: TableId,
265 table_name: TableName,
266 resolve_strategy: ResolveStrategy,
267 is_subprocedure: bool,
268 ) -> Self {
269 let persistent_ctx =
270 PersistentContext::new(table_id, table_name, resolve_strategy, is_subprocedure);
271 let context = ReconcileTableContext::new(ctx, persistent_ctx);
272 let state = Box::new(ReconciliationStart);
273 Self { context, state }
274 }
275}
276
277impl ReconcileTableProcedure {
278 pub const TYPE_NAME: &'static str = "metasrv-procedure::ReconcileTable";
279
280 pub(crate) fn from_json(ctx: Context, json: &str) -> ProcedureResult<Self> {
281 let ProcedureDataOwned {
282 state,
283 persistent_ctx,
284 } = serde_json::from_str(json).context(FromJsonSnafu)?;
285 let context = ReconcileTableContext::new(ctx, persistent_ctx);
286 Ok(Self { context, state })
287 }
288}
289
290#[derive(Debug, Serialize)]
291struct ProcedureData<'a> {
292 state: &'a dyn State,
293 persistent_ctx: &'a PersistentContext,
294}
295
296#[derive(Debug, Deserialize)]
297struct ProcedureDataOwned {
298 state: Box<dyn State>,
299 persistent_ctx: PersistentContext,
300}
301
302#[async_trait]
303impl Procedure for ReconcileTableProcedure {
304 fn type_name(&self) -> &str {
305 Self::TYPE_NAME
306 }
307
308 async fn execute(&mut self, _ctx: &ProcedureContext) -> ProcedureResult<Status> {
309 let state = &mut self.state;
310
311 let procedure_name = Self::TYPE_NAME;
312 let step = state.name();
313 let _timer = metrics::METRIC_META_RECONCILIATION_PROCEDURE
314 .with_label_values(&[procedure_name, step])
315 .start_timer();
316 match state.next(&mut self.context, _ctx).await {
317 Ok((next, status)) => {
318 *state = next;
319 Ok(status)
320 }
321 Err(e) => {
322 if e.retry_hint().is_retryable() {
323 metrics::METRIC_META_RECONCILIATION_PROCEDURE_ERROR
324 .with_label_values(&[procedure_name, step, metrics::ERROR_TYPE_RETRYABLE])
325 .inc();
326 Err(ProcedureError::retry_later(e))
327 } else {
328 metrics::METRIC_META_RECONCILIATION_PROCEDURE_ERROR
329 .with_label_values(&[procedure_name, step, metrics::ERROR_TYPE_EXTERNAL])
330 .inc();
331 Err(ProcedureError::external(e))
332 }
333 }
334 }
335 }
336
337 fn dump(&self) -> ProcedureResult<String> {
338 let data = ProcedureData {
339 state: self.state.as_ref(),
340 persistent_ctx: &self.context.persistent_ctx,
341 };
342 serde_json::to_string(&data).context(ToJsonSnafu)
343 }
344
345 fn lock_key(&self) -> LockKey {
346 let table_ref = &self.context.table_name().table_ref();
347
348 if self.context.persistent_ctx.is_subprocedure {
349 return LockKey::new(vec![
352 TableNameLock::new(table_ref.catalog, table_ref.schema, table_ref.table).into(),
353 ]);
354 }
355
356 LockKey::new(vec![
357 CatalogLock::Read(table_ref.catalog).into(),
358 SchemaLock::read(table_ref.catalog, table_ref.schema).into(),
359 TableNameLock::new(table_ref.catalog, table_ref.schema, table_ref.table).into(),
360 ])
361 }
362
363 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn common_event_recorder::Event>> {
364 if !ctx.event_type_filter.allows(RECONCILE_TABLE_EVENT_TYPE) {
365 return None;
366 }
367
368 let persistent_ctx = &self.context.persistent_ctx;
369 let table_name = &persistent_ctx.table_name;
370 let locator = ReconciliationLocator::physical_table(
371 &table_name.catalog_name,
372 &table_name.schema_name,
373 &table_name.table_name,
374 persistent_ctx.table_id,
375 );
376 let event = match ctx.trigger {
377 EventTrigger::Submitted => ReconcileTableEvent::table_submitted(
378 locator,
379 persistent_ctx.resolve_strategy,
380 persistent_ctx.is_subprocedure,
381 ),
382 EventTrigger::Succeeded => {
383 Self::result_event(locator, &self.context.volatile_ctx.result_summary, true)
384 }
385 EventTrigger::Failed | EventTrigger::Poisoned => {
386 Self::result_event(locator, &self.context.volatile_ctx.result_summary, false)
387 }
388 _ => ReconcileTableEvent::table_lifecycle(locator),
389 };
390 Some(Box::new(event))
391 }
392}
393
394impl ReconcileTableProcedure {
395 fn result_event(
396 locator: ReconciliationLocator,
397 summary: &ReconcileTableResultSummary,
398 complete: bool,
399 ) -> ReconcileTableEvent {
400 ReconcileTableEvent::table_result(
401 locator,
402 complete,
403 summary.metadata_state(),
404 summary.resolution_strategy_applied,
405 summary.resolved_column_count,
406 summary.scanned_region_count,
407 summary.updated_region_count,
408 summary.table_info_updated,
409 summary.last_completed_phase(),
410 )
411 }
412}
413
414#[async_trait::async_trait]
415#[typetag::serde(tag = "reconcile_table_state")]
416pub(crate) trait State: Sync + Send + Debug {
417 fn name(&self) -> &'static str {
418 let type_name = std::any::type_name::<Self>();
419 type_name.split("::").last().unwrap_or(type_name)
421 }
422
423 async fn next(
424 &mut self,
425 ctx: &mut ReconcileTableContext,
426 procedure_ctx: &ProcedureContext,
427 ) -> Result<(Box<dyn State>, Status)>;
428}
429
430#[cfg(test)]
431mod tests {
432 use std::sync::Arc;
433
434 use common_event_recorder::{EventTypeFilter, EventTypeFilterRef};
435 use common_procedure::{
436 ChildSubmissionOutcome, EventContext, EventTrigger, Procedure, ProcedureId, ProcedureState,
437 RetryPhase,
438 };
439 use common_procedure_test::MockContextProvider;
440 use serde_json::{Value, json};
441 use store_api::storage::RegionId;
442
443 use super::*;
444 use crate::ddl::test_util::datanode_handler::PartialSuccessDatanodeHandler;
445 use crate::key::DeserializedValueWithBytes;
446 use crate::key::table_info::TableInfoValue;
447 use crate::key::table_route::PhysicalTableRouteValue;
448 use crate::key::test_utils::new_test_table_info_with_name;
449 use crate::peer::Peer;
450 use crate::reconciliation::reconcile_table::reconcile_regions::ReconcileRegions;
451 use crate::reconciliation::utils::build_column_metadata_from_table_info;
452 use crate::rpc::router::{Region, RegionRoute};
453 use crate::test_util::{MockDatanodeManager, new_ddl_context};
454
455 struct TableEventHarness {
456 procedure_id: ProcedureId,
457 lifecycle_state: ProcedureState,
458 event_type_filter: EventTypeFilterRef,
459 }
460
461 impl TableEventHarness {
462 fn all() -> Self {
463 Self {
464 procedure_id: ProcedureId::random(),
465 lifecycle_state: ProcedureState::Running,
466 event_type_filter: Arc::new(EventTypeFilter::All),
467 }
468 }
469
470 fn selected(event_types: impl IntoIterator<Item = &'static str>) -> Self {
471 Self {
472 event_type_filter: Arc::new(EventTypeFilter::Only(
473 event_types.into_iter().map(str::to_string).collect(),
474 )),
475 ..Self::all()
476 }
477 }
478
479 fn event(
480 &self,
481 procedure: &dyn Procedure,
482 trigger: EventTrigger,
483 ) -> Option<Box<dyn common_event_recorder::Event>> {
484 procedure.event(&EventContext {
485 procedure_id: self.procedure_id,
486 lifecycle_state: &self.lifecycle_state,
487 trigger,
488 event_type_filter: self.event_type_filter.clone(),
489 event_context: None,
490 })
491 }
492 }
493
494 #[test]
495 fn table_submitted_events_cover_root_and_child_intent() {
496 let events = TableEventHarness::all();
497 let root = test_procedure(false);
498 let child = test_procedure(true);
499 for (procedure, is_subprocedure) in [(&root, false), (&child, true)] {
500 let submitted = events.event(procedure, EventTrigger::Submitted).unwrap();
501 assert_eq!(submitted.event_type(), RECONCILE_TABLE_EVENT_TYPE);
502 assert_eq!(
503 submitted.json_payload().unwrap(),
504 json!({
505 "version": 1,
506 "resolve_strategy": "use_latest",
507 "is_subprocedure": is_subprocedure,
508 })
509 );
510 }
511 }
512
513 #[test]
514 fn table_non_terminal_lifecycle_events_have_null_payloads() {
515 let events = TableEventHarness::all();
516 let mut procedure = test_procedure(true);
517 procedure.context.volatile_ctx.result_summary = populated_summary();
518
519 for trigger in [
520 EventTrigger::Recovered,
521 EventTrigger::ChildSubmitted {
522 procedure_id: ProcedureId::random(),
523 outcome: ChildSubmissionOutcome::Accepted,
524 },
525 EventTrigger::Retrying {
526 phase: RetryPhase::Execute,
527 attempt: 2,
528 },
529 EventTrigger::RollingBack,
530 ] {
531 assert_eq!(
532 events
533 .event(&procedure, trigger)
534 .unwrap()
535 .json_payload()
536 .unwrap(),
537 Value::Null
538 );
539 }
540 }
541
542 #[test]
543 fn table_terminal_events_report_bounded_results() {
544 let events = TableEventHarness::all();
545 let mut procedure = test_procedure(true);
546 procedure.context.volatile_ctx.result_summary = populated_summary();
547
548 for (trigger, complete) in [
549 (EventTrigger::Succeeded, true),
550 (EventTrigger::Failed, false),
551 (EventTrigger::Poisoned, false),
552 ] {
553 assert_eq!(
554 events
555 .event(&procedure, trigger)
556 .unwrap()
557 .json_payload()
558 .unwrap(),
559 json!({
560 "version": 1,
561 "complete": complete,
562 "metadata_state": "inconsistent",
563 "resolution_strategy_applied": "use_latest",
564 "resolved_column_count": 3,
565 "scanned_region_count": 2,
566 "updated_region_count": 1,
567 "table_info_updated": true,
568 "last_completed_phase": "update_table_info",
569 })
570 );
571 }
572 }
573
574 #[test]
575 fn table_event_filtering_uses_the_reconciliation_event_type() {
576 let procedure = test_procedure(false);
577 assert!(
578 TableEventHarness::selected([RECONCILE_TABLE_EVENT_TYPE])
579 .event(&procedure, EventTrigger::Submitted)
580 .is_some()
581 );
582 assert!(
583 TableEventHarness::selected(["create_table"])
584 .event(&procedure, EventTrigger::Submitted)
585 .is_none()
586 );
587 assert!(
588 TableEventHarness::selected([])
589 .event(&procedure, EventTrigger::Submitted)
590 .is_none()
591 );
592 }
593
594 #[test]
595 fn table_result_summary_is_not_persisted() {
596 let events = TableEventHarness::all();
597 let mut procedure = test_procedure(false);
598 procedure.context.volatile_ctx.result_summary = populated_summary();
599 let value: Value = serde_json::from_str(&procedure.dump().unwrap()).unwrap();
600 assert!(value["persistent_ctx"].get("result_summary").is_none());
601
602 let loaded = ReconcileTableProcedure::from_json(
603 test_context(),
604 &serde_json::to_string(&value).unwrap(),
605 )
606 .unwrap();
607 assert_eq!(
608 events
609 .event(&loaded, EventTrigger::Succeeded)
610 .unwrap()
611 .json_payload()
612 .unwrap(),
613 json!({
614 "version": 1,
615 "complete": true,
616 "metadata_state": null,
617 "resolution_strategy_applied": null,
618 "resolved_column_count": null,
619 "scanned_region_count": 0,
620 "updated_region_count": 0,
621 "table_info_updated": false,
622 "last_completed_phase": null,
623 })
624 );
625 }
626
627 #[test]
628 fn table_partial_summary_preserves_completed_work_without_advancing_phases() {
629 let events = TableEventHarness::all();
630 let mut procedure = test_procedure(false);
631 let summary = &mut procedure.context.volatile_ctx.result_summary;
632 summary.record_scanned_regions(2);
633
634 assert_eq!(
635 events
636 .event(&procedure, EventTrigger::Failed)
637 .unwrap()
638 .json_payload()
639 .unwrap(),
640 json!({
641 "version": 1,
642 "complete": false,
643 "metadata_state": null,
644 "resolution_strategy_applied": null,
645 "resolved_column_count": null,
646 "scanned_region_count": 2,
647 "updated_region_count": 0,
648 "table_info_updated": false,
649 "last_completed_phase": null,
650 })
651 );
652
653 let summary = &mut procedure.context.volatile_ctx.result_summary;
654 summary.mark_start_completed();
655 summary.record_metadata_state(TableMetadataState::Inconsistent);
656 assert_eq!(
657 events
658 .event(&procedure, EventTrigger::Failed)
659 .unwrap()
660 .json_payload()
661 .unwrap(),
662 json!({
663 "version": 1,
664 "complete": false,
665 "metadata_state": "inconsistent",
666 "resolution_strategy_applied": null,
667 "resolved_column_count": null,
668 "scanned_region_count": 2,
669 "updated_region_count": 0,
670 "table_info_updated": false,
671 "last_completed_phase": "start",
672 })
673 );
674
675 let summary = &mut procedure.context.volatile_ctx.result_summary;
676 summary.record_resolved_columns(
677 TableMetadataState::Inconsistent,
678 Some(ResolveStrategy::UseLatest),
679 Some(3),
680 );
681 summary.record_updated_regions(1);
682 assert_eq!(
683 events
684 .event(&procedure, EventTrigger::Failed)
685 .unwrap()
686 .json_payload()
687 .unwrap(),
688 json!({
689 "version": 1,
690 "complete": false,
691 "metadata_state": "inconsistent",
692 "resolution_strategy_applied": "use_latest",
693 "resolved_column_count": 3,
694 "scanned_region_count": 2,
695 "updated_region_count": 1,
696 "table_info_updated": false,
697 "last_completed_phase": "resolve_column_metadata",
698 })
699 );
700 }
701
702 #[tokio::test]
703 async fn table_mixed_region_outcome_preserves_partial_result() {
704 let ddl_context = new_ddl_context(Arc::new(MockDatanodeManager::new(
705 PartialSuccessDatanodeHandler { retryable: false },
706 )));
707 let context = Context {
708 node_manager: ddl_context.node_manager,
709 table_metadata_manager: ddl_context.table_metadata_manager,
710 cache_invalidator: ddl_context.cache_invalidator,
711 };
712 let mut procedure = ReconcileTableProcedure::new(
713 context,
714 42,
715 TableName::new("greptime", "public", "metrics"),
716 ResolveStrategy::UseLatest,
717 false,
718 );
719
720 let mut table_info = new_test_table_info_with_name(42, "metrics");
721 table_info.meta.column_ids = vec![0, 1, 2];
722 let name_to_ids = table_info.name_to_ids().unwrap();
723 let column_metadatas = build_column_metadata_from_table_info(
724 table_info.meta.schema.column_schemas(),
725 &table_info.meta.primary_key_indices,
726 &name_to_ids,
727 )
728 .unwrap();
729 let region_ids = [RegionId::new(42, 1), RegionId::new(42, 2)];
730 procedure.context.persistent_ctx.table_info_value = Some(
731 DeserializedValueWithBytes::from_inner(TableInfoValue::new(table_info)),
732 );
733 procedure.context.persistent_ctx.physical_table_route =
734 Some(PhysicalTableRouteValue::new(vec![
735 RegionRoute {
736 region: Region::new_test(region_ids[0]),
737 leader_peer: Some(Peer::empty(1)),
738 ..Default::default()
739 },
740 RegionRoute {
741 region: Region::new_test(region_ids[1]),
742 leader_peer: Some(Peer::empty(2)),
743 ..Default::default()
744 },
745 ]));
746
747 let summary = &mut procedure.context.volatile_ctx.result_summary;
748 summary.record_scanned_regions(region_ids.len());
749 summary.mark_start_completed();
750 summary.record_resolved_columns(
751 TableMetadataState::Inconsistent,
752 Some(ResolveStrategy::UseLatest),
753 Some(column_metadatas.len()),
754 );
755 procedure.state = Box::new(ReconcileRegions::new(column_metadatas, region_ids.to_vec()));
756
757 let procedure_ctx = ProcedureContext {
758 procedure_id: ProcedureId::random(),
759 provider: Arc::new(MockContextProvider::default()),
760 event_context: None,
761 };
762 assert!(procedure.execute(&procedure_ctx).await.is_err());
763
764 assert_eq!(
765 TableEventHarness::all()
766 .event(&procedure, EventTrigger::Failed)
767 .unwrap()
768 .json_payload()
769 .unwrap(),
770 json!({
771 "version": 1,
772 "complete": false,
773 "metadata_state": "inconsistent",
774 "resolution_strategy_applied": "use_latest",
775 "resolved_column_count": 3,
776 "scanned_region_count": 2,
777 "updated_region_count": 1,
778 "table_info_updated": false,
779 "last_completed_phase": "resolve_column_metadata",
780 })
781 );
782 }
783
784 fn populated_summary() -> ReconcileTableResultSummary {
785 ReconcileTableResultSummary {
786 metadata_state: Some(TableMetadataState::Inconsistent),
787 resolution_strategy_applied: Some(ResolveStrategy::UseLatest),
788 resolved_column_count: Some(3),
789 scanned_region_count: 2,
790 updated_region_count: 1,
791 table_info_updated: true,
792 last_completed_phase: Some(TablePhase::UpdateTableInfo),
793 }
794 }
795
796 fn test_procedure(is_subprocedure: bool) -> ReconcileTableProcedure {
797 ReconcileTableProcedure::new(
798 test_context(),
799 42,
800 TableName::new("greptime", "public", "metrics"),
801 ResolveStrategy::UseLatest,
802 is_subprocedure,
803 )
804 }
805
806 fn test_context() -> Context {
807 let ddl_context = new_ddl_context(Arc::new(MockDatanodeManager::new(())));
808 Context {
809 node_manager: ddl_context.node_manager,
810 table_metadata_manager: ddl_context.table_metadata_manager,
811 cache_invalidator: ddl_context.cache_invalidator,
812 }
813 }
814}