Skip to main content

common_meta/ddl/
alter_table.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod executor;
16mod metadata;
17mod partition_compat;
18mod region_request;
19
20pub use partition_compat::validate_region_partition_bounds;
21
22use std::collections::HashSet;
23use std::vec;
24
25use api::region::RegionResponse;
26use api::v1::alter_table_expr::Kind;
27use api::v1::{RenameTable, SetTableOptions};
28use async_trait::async_trait;
29use common_catalog::consts::{METRIC_ENGINE, MITO_ENGINE};
30use common_error::ext::BoxedError;
31use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu};
32use common_procedure::{
33    Context as ProcedureContext, ContextProvider, Error as ProcedureError, EventContext,
34    EventTrigger, LockKey, PoisonKey, PoisonKeys, Procedure, ProcedureId, Status, StringKey,
35};
36use common_telemetry::{error, info, warn};
37use common_wal::options::WalOptions;
38use serde::{Deserialize, Serialize};
39use snafu::{ResultExt, ensure};
40use store_api::metadata::ColumnMetadata;
41use store_api::metric_engine_consts::TABLE_COLUMN_METADATA_EXTENSION_KEY;
42use store_api::storage::RegionId;
43use strum::AsRefStr;
44use table::metadata::{TableId, TableInfo};
45use table::requests::SKIP_WAL_KEY;
46use table::table_reference::TableReference;
47
48use crate::ddl::DdlContext;
49use crate::ddl::alter_table::executor::AlterTableExecutor;
50use crate::ddl::event::table::{
51    TableDdlEvent, TableDdlEventType, TableDdlLocator, alter_table_kind_name,
52};
53use crate::ddl::utils::{
54    MultipleResults, extract_column_metadatas, extract_region_wal_options, handle_multiple_results,
55    map_to_procedure_error, sync_follower_regions,
56};
57use crate::error::{
58    AbortProcedureSnafu, ConvertAlterTableRequestSnafu, NoLeaderSnafu, PutPoisonSnafu, Result,
59    RetryLaterSnafu, UnexpectedSnafu, UnsupportedSnafu,
60};
61use crate::key::table_info::TableInfoValue;
62use crate::key::{DeserializedValueWithBytes, RegionDistribution};
63use crate::lock_key::{CatalogLock, RegionLock, SchemaLock, TableLock, TableNameLock};
64use crate::metrics;
65use crate::poison_key::table_poison_key;
66use crate::rpc::ddl::AlterTableTask;
67use crate::rpc::router::{RegionRoute, find_leaders, region_distribution};
68
69/// The alter table procedure
70pub struct AlterTableProcedure {
71    /// The runtime context.
72    context: DdlContext,
73    /// The serialized data.
74    data: AlterTableData,
75    /// Cached new table metadata in the prepare step.
76    /// If we recover the procedure from json, then the table info value is not cached.
77    /// But we already validated it in the prepare step.
78    new_table_info: Option<TableInfo>,
79    /// The alter table executor.
80    executor: AlterTableExecutor,
81}
82
83#[derive(Debug)]
84pub(crate) struct RegionRouteChanged;
85
86/// Builds the executor from the [`AlterTableData`].
87///
88/// # Panics
89/// - If the alter kind is not set.
90fn build_executor_from_alter_expr(alter_data: &AlterTableData) -> AlterTableExecutor {
91    let table_name = alter_data.table_ref().into();
92    let table_id = alter_data.table_id;
93    let alter_kind = alter_data.task.alter_table.kind.as_ref().unwrap();
94    let new_table_name = if let Kind::RenameTable(RenameTable { new_table_name }) = alter_kind {
95        Some(new_table_name.clone())
96    } else {
97        None
98    };
99    AlterTableExecutor::new(table_name, table_id, new_table_name)
100}
101
102impl AlterTableProcedure {
103    pub const TYPE_NAME: &'static str = "metasrv-procedure::AlterTable";
104
105    pub fn new(table_id: TableId, task: AlterTableTask, context: DdlContext) -> Result<Self> {
106        Self::new_with_region_locks(table_id, task, vec![], context)
107    }
108
109    pub(crate) fn new_with_region_locks(
110        table_id: TableId,
111        task: AlterTableTask,
112        region_locks: Vec<RegionId>,
113        context: DdlContext,
114    ) -> Result<Self> {
115        task.validate()?;
116        let data = AlterTableData::new(task, table_id, region_locks);
117        let executor = build_executor_from_alter_expr(&data);
118        Ok(Self {
119            context,
120            data,
121            new_table_info: None,
122            executor,
123        })
124    }
125
126    pub fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
127        let data: AlterTableData = serde_json::from_str(json).context(FromJsonSnafu)?;
128        let executor = build_executor_from_alter_expr(&data);
129
130        Ok(AlterTableProcedure {
131            context,
132            data,
133            new_table_info: None,
134            executor,
135        })
136    }
137
138    // Checks whether the table exists.
139    pub(crate) async fn on_prepare(&mut self) -> Result<Status> {
140        self.executor
141            .on_prepare(&self.context.table_metadata_manager)
142            .await?;
143        self.fill_table_info().await?;
144
145        // Safety: filled in `fill_table_info`.
146        let table_info_value = self.data.table_info_value.as_ref().unwrap();
147        let new_table_info = AlterTableExecutor::validate_alter_table_expr(
148            &table_info_value.table_info,
149            self.data.task.alter_table.clone(),
150        )?;
151        self.new_table_info = Some(new_table_info);
152
153        // Reject schema changes that the persisted partition expressions of some
154        // region cannot survive, before any region is altered.
155        self.validate_region_partition_exprs().await?;
156
157        // Safety: Checked in `AlterTableProcedure::new`.
158        let alter_kind = self.data.task.alter_table.kind.as_ref().unwrap();
159        if sets_skip_wal(alter_kind) {
160            ensure!(
161                only_sets_skip_wal(alter_kind),
162                UnsupportedSnafu {
163                    operation: "combining skip_wal with other table options".to_string()
164                }
165            );
166            let engine = table_info_value.table_info.meta.engine.as_str();
167            ensure!(
168                engine == MITO_ENGINE || engine == METRIC_ENGINE,
169                UnsupportedSnafu {
170                    operation: format!("setting skip_wal on {engine} engine tables")
171                }
172            );
173            // Keep skip-WAL changes serialized with region migration and use the same
174            // route snapshot for validation and RPC delivery.
175            let (physical_table_id, physical_table_route) = self
176                .context
177                .table_metadata_manager
178                .table_route_manager()
179                .get_physical_table_route(self.data.table_id())
180                .await?;
181            ensure!(
182                physical_table_id == self.data.table_id(),
183                UnsupportedSnafu {
184                    operation: "setting skip_wal on logical tables".to_string()
185                }
186            );
187            let current_region_ids = physical_table_route
188                .region_routes
189                .iter()
190                .map(|route| route.region.id)
191                .collect::<HashSet<_>>();
192            let locked_region_ids = self
193                .data
194                .region_locks
195                .iter()
196                .copied()
197                .collect::<HashSet<_>>();
198            if physical_table_route.region_routes.len() != self.data.region_locks.len()
199                || current_region_ids != locked_region_ids
200            {
201                // Procedure lock keys are fixed at submission. Finish this attempt so the
202                // DDL manager can submit another procedure with locks for the current route.
203                return Ok(Status::done_with_output(RegionRouteChanged));
204            }
205            ensure!(
206                !find_leaders(&physical_table_route.region_routes).is_empty(),
207                NoLeaderSnafu {
208                    table_id: physical_table_id
209                }
210            );
211            // validate_alter_table_expr() has already parsed this single skip-WAL option.
212            let Some(skip_wal) = skip_wal_value(alter_kind) else {
213                return UnexpectedSnafu {
214                    err_msg: "missing or invalid skip_wal value after validation".to_string(),
215                }
216                .fail();
217            };
218            if !skip_wal {
219                let datanode_table_values = self
220                    .context
221                    .table_metadata_manager
222                    .datanode_table_manager()
223                    .regions(physical_table_id, &physical_table_route)
224                    .await?;
225                let region_wal_options = extract_region_wal_options(&datanode_table_values)?;
226                ensure!(
227                    current_region_ids.iter().all(|region_id| {
228                        matches!(
229                            region_wal_options
230                                .get(&region_id.region_number())
231                                .cloned()
232                                .unwrap_or_default(),
233                            WalOptions::RaftEngine
234                                | WalOptions::Kafka(_)
235                                | WalOptions::ObjectStore(_)
236                        )
237                    }),
238                    UnsupportedSnafu {
239                        operation: "setting skip_wal = 'false' without an existing WAL provider"
240                            .to_string()
241                    }
242                );
243            }
244            self.data.region_distribution =
245                Some(region_distribution(&physical_table_route.region_routes));
246        }
247        self.data.state = self.data.flow()?.after_prepare();
248        Ok(Status::executing(true))
249    }
250
251    /// Validates the persisted partition expressions of every region against the
252    /// schema proposed by this ALTER TABLE, so an ALTER that would break a region's
253    /// partition predicate is rejected before any region is modified.
254    async fn validate_region_partition_exprs(&self) -> Result<()> {
255        // Safety: filled in `fill_table_info`.
256        let old_schema = &self
257            .data
258            .table_info_value
259            .as_ref()
260            .unwrap()
261            .table_info
262            .meta
263            .schema;
264        // Safety: set right after the new table info is built in `on_prepare`.
265        let new_schema = &self.new_table_info.as_ref().unwrap().meta.schema;
266        let changed_columns = partition_compat::changed_column_types(old_schema, new_schema);
267        if changed_columns.is_empty() {
268            return Ok(());
269        }
270
271        let (physical_table_id, physical_table_route) = self
272            .context
273            .table_metadata_manager
274            .table_route_manager()
275            .get_physical_table_route(self.data.table_id())
276            .await?;
277        if physical_table_id != self.data.table_id() {
278            // Partition expressions are persisted on the regions of the physical
279            // table; the altered schema here belongs to a logical table.
280            return Ok(());
281        }
282
283        partition_compat::validate_region_partition_bounds(
284            &changed_columns,
285            &physical_table_route.region_routes,
286        )
287    }
288
289    fn table_poison_key(&self) -> PoisonKey {
290        table_poison_key(self.data.table_id())
291    }
292
293    async fn put_poison(
294        &self,
295        ctx_provider: &dyn ContextProvider,
296        procedure_id: ProcedureId,
297    ) -> Result<()> {
298        let poison_key = self.table_poison_key();
299        ctx_provider
300            .try_put_poison(&poison_key, procedure_id)
301            .await
302            .context(PutPoisonSnafu)
303    }
304
305    pub async fn submit_alter_region_requests(
306        &mut self,
307        procedure_id: ProcedureId,
308        ctx_provider: &dyn ContextProvider,
309    ) -> Result<Status> {
310        let table_id = self.data.table_id();
311        let (_, physical_table_route) = self
312            .context
313            .table_metadata_manager
314            .table_route_manager()
315            .get_physical_table_route(table_id)
316            .await?;
317
318        self.data.region_distribution =
319            Some(region_distribution(&physical_table_route.region_routes));
320        let leaders = find_leaders(&physical_table_route.region_routes);
321        let alter_kind = self.make_region_alter_kind()?;
322
323        info!(
324            "Submitting alter region requests for table {}, table_id: {}, alter_kind: {:?}",
325            self.data.table_ref(),
326            table_id,
327            alter_kind,
328        );
329
330        ensure!(!leaders.is_empty(), NoLeaderSnafu { table_id });
331        // Puts the poison before submitting alter region requests to datanodes.
332        self.put_poison(ctx_provider, procedure_id).await?;
333        let flow = self.data.flow()?;
334        if flow == AlterTableFlow::MetadataFirst {
335            let results = self
336                .executor
337                .on_alter_skip_wal_regions(
338                    &self.context.node_manager,
339                    &physical_table_route.region_routes,
340                    alter_kind,
341                )
342                .await;
343
344            return match handle_multiple_results(results) {
345                MultipleResults::PartialRetryable(error) => Err(error),
346                MultipleResults::PartialNonRetryable(error)
347                | MultipleResults::AllNonRetryable(error) => {
348                    // The metadata already contains the requested skip-WAL value. Retry the
349                    // idempotent request so later attempts can update the remaining replicas.
350                    Err(BoxedError::new(error)).context(RetryLaterSnafu {
351                        clean_poisons: true,
352                    })
353                }
354                MultipleResults::AllRetryable(error) => {
355                    Err(BoxedError::new(error)).context(RetryLaterSnafu {
356                        clean_poisons: true,
357                    })
358                }
359                MultipleResults::Ok(results) => {
360                    self.handle_alter_region_response(results)?;
361                    Ok(Status::executing_with_clean_poisons(true))
362                }
363            };
364        }
365
366        let results = self
367            .executor
368            .on_alter_regions(
369                &self.context.node_manager,
370                &physical_table_route.region_routes,
371                alter_kind,
372            )
373            .await;
374
375        match handle_multiple_results(results) {
376            MultipleResults::PartialRetryable(error) => {
377                // Just returns the error, and wait for the next try.
378                Err(error)
379            }
380            MultipleResults::PartialNonRetryable(error) => {
381                error!(error; "Partial non-retryable errors occurred during alter table, table {}, table_id: {}", self.data.table_ref(), self.data.table_id());
382                // No retry will be done.
383                Ok(Status::poisoned(
384                    Some(self.table_poison_key()),
385                    ProcedureError::external(error),
386                ))
387            }
388            MultipleResults::AllRetryable(error) => {
389                // Just returns the error, and wait for the next try.
390                let err = BoxedError::new(error);
391                Err(err).context(RetryLaterSnafu {
392                    clean_poisons: true,
393                })
394            }
395            MultipleResults::Ok(results) => {
396                self.submit_sync_region_requests(&results, &physical_table_route.region_routes)
397                    .await;
398                self.handle_alter_region_response(results)?;
399                Ok(Status::executing_with_clean_poisons(true))
400            }
401            MultipleResults::AllNonRetryable(error) => {
402                error!(error; "All alter requests returned non-retryable errors for table {}, table_id: {}", self.data.table_ref(), self.data.table_id());
403                // It assumes the metadata on datanode is not changed.
404                // Case: The alter region request is sent but not applied. (e.g., InvalidArgument)
405
406                let err = BoxedError::new(error);
407                Err(err).context(AbortProcedureSnafu {
408                    clean_poisons: true,
409                })
410            }
411        }
412    }
413
414    fn handle_alter_region_response(&mut self, mut results: Vec<RegionResponse>) -> Result<()> {
415        if let Some(column_metadatas) =
416            extract_column_metadatas(&mut results, TABLE_COLUMN_METADATA_EXTENSION_KEY)?
417        {
418            self.data.column_metadatas = column_metadatas;
419        } else {
420            warn!(
421                "altering table result doesn't contains extension key `{TABLE_COLUMN_METADATA_EXTENSION_KEY}`,leaving the table's column metadata unchanged"
422            );
423        }
424        self.data.state = self.data.flow()?.after_regions();
425        Ok(())
426    }
427
428    async fn submit_sync_region_requests(
429        &mut self,
430        results: &[RegionResponse],
431        region_routes: &[RegionRoute],
432    ) {
433        // Safety: filled in `prepare` step.
434        let table_info = self.data.table_info().unwrap();
435        if let Err(err) = sync_follower_regions(
436            &self.context,
437            self.data.table_id(),
438            results,
439            region_routes,
440            table_info.meta.engine.as_str(),
441        )
442        .await
443        {
444            error!(err; "Failed to sync regions for table {}, table_id: {}", self.data.table_ref(), self.data.table_id());
445        }
446    }
447
448    /// Update table metadata.
449    pub(crate) async fn on_update_metadata(&mut self) -> Result<Status> {
450        let table_id = self.data.table_id();
451        let table_ref = self.data.table_ref();
452        // Safety: filled in `fill_table_info`.
453        let table_info_value = self.data.table_info_value.as_ref().unwrap();
454        // Safety: Checked in `AlterTableProcedure::new`.
455        let alter_kind = self.data.task.alter_table.kind.as_ref().unwrap();
456        let flow = self.data.flow()?;
457        let metadata_only_alter = flow == AlterTableFlow::MetadataOnly;
458
459        // Gets the table info from the cache or builds it.
460        let  new_info = match &self.new_table_info {
461            Some(cached) => cached.clone(),
462            None => AlterTableExecutor::validate_alter_table_expr(
463                &table_info_value.table_info,
464                self.data.task.alter_table.clone(),
465               )
466                .inspect_err(|e| {
467                    // We already check the table info in the prepare step so this should not happen.
468                    error!(e; "Unable to build info for table {} in update metadata step, table_id: {}", table_ref, table_id);
469                })?,
470        };
471
472        // Safety: region distribution is set in `submit_alter_region_requests`.
473        self.executor
474            .on_alter_metadata(
475                &self.context.table_metadata_manager,
476                table_info_value,
477                self.data.region_distribution.as_ref(),
478                new_info,
479                &self.data.column_metadatas,
480                metadata_only_alter,
481            )
482            .await?;
483
484        info!(
485            "Updated table metadata for table {table_ref}, table_id: {table_id}, kind: {alter_kind:?}"
486        );
487        self.data.state = flow.after_metadata();
488        Ok(Status::executing(true))
489    }
490
491    /// Broadcasts the invalidating table cache instructions.
492    async fn on_broadcast(&mut self) -> Result<Status> {
493        self.executor
494            .invalidate_table_cache(&self.context.cache_invalidator)
495            .await?;
496        Ok(Status::done())
497    }
498
499    fn lock_key_inner(&self) -> Vec<StringKey> {
500        let mut lock_key = vec![];
501        let table_ref = self.data.table_ref();
502        let table_id = self.data.table_id();
503        lock_key.push(CatalogLock::Read(table_ref.catalog).into());
504        lock_key.push(SchemaLock::read(table_ref.catalog, table_ref.schema).into());
505        lock_key.push(TableLock::Write(table_id).into());
506
507        for region_id in &self.data.region_locks {
508            lock_key.push(RegionLock::Write(*region_id).into());
509        }
510
511        // Safety: Checked in `AlterTableProcedure::new`.
512        let alter_kind = self.data.task.alter_table.kind.as_ref().unwrap();
513        if let Kind::RenameTable(RenameTable { new_table_name }) = alter_kind {
514            lock_key.push(
515                TableNameLock::new(table_ref.catalog, table_ref.schema, new_table_name).into(),
516            )
517        }
518
519        lock_key
520    }
521
522    #[cfg(test)]
523    pub(crate) fn data(&self) -> &AlterTableData {
524        &self.data
525    }
526
527    #[cfg(test)]
528    pub(crate) fn mut_data(&mut self) -> &mut AlterTableData {
529        &mut self.data
530    }
531}
532
533fn sets_skip_wal(alter_kind: &Kind) -> bool {
534    let Kind::SetTableOptions(SetTableOptions { table_options }) = alter_kind else {
535        return false;
536    };
537
538    table_options
539        .iter()
540        .any(|option| option.key == SKIP_WAL_KEY)
541}
542
543pub(crate) fn only_sets_skip_wal(alter_kind: &Kind) -> bool {
544    let Kind::SetTableOptions(SetTableOptions { table_options }) = alter_kind else {
545        return false;
546    };
547
548    table_options.len() == 1 && table_options[0].key == SKIP_WAL_KEY
549}
550
551fn skip_wal_value(alter_kind: &Kind) -> Option<bool> {
552    let Kind::SetTableOptions(SetTableOptions { table_options }) = alter_kind else {
553        return None;
554    };
555    let [option] = table_options.as_slice() else {
556        return None;
557    };
558    if option.key != SKIP_WAL_KEY {
559        return None;
560    }
561    option.value.parse().ok()
562}
563
564fn is_metadata_only_alter(alter_kind: &Kind) -> Result<bool> {
565    // A mixed annotation batch is an error, never "not metadata-only": falling
566    // through to the region-first flow would dispatch it to regions.
567    let family = common_grpc_expr::annotation_alter_family(alter_kind)
568        .context(ConvertAlterTableRequestSnafu)?;
569    Ok(family.is_some() || matches!(alter_kind, Kind::RenameTable { .. }))
570}
571
572#[derive(Debug, Clone, Copy, PartialEq, Eq)]
573enum AlterTableFlow {
574    RegionFirst,
575    MetadataFirst,
576    MetadataOnly,
577}
578
579impl AlterTableFlow {
580    fn from_kind(kind: &Kind) -> Result<Self> {
581        Ok(if only_sets_skip_wal(kind) {
582            Self::MetadataFirst
583        } else if is_metadata_only_alter(kind)? {
584            Self::MetadataOnly
585        } else {
586            Self::RegionFirst
587        })
588    }
589
590    fn after_prepare(self) -> AlterTableState {
591        match self {
592            Self::RegionFirst => AlterTableState::SubmitAlterRegionRequests,
593            Self::MetadataFirst | Self::MetadataOnly => AlterTableState::UpdateMetadata,
594        }
595    }
596
597    fn after_regions(self) -> AlterTableState {
598        match self {
599            Self::RegionFirst => AlterTableState::UpdateMetadata,
600            Self::MetadataFirst | Self::MetadataOnly => AlterTableState::InvalidateTableCache,
601        }
602    }
603
604    fn after_metadata(self) -> AlterTableState {
605        match self {
606            Self::MetadataFirst => AlterTableState::SubmitAlterRegionRequests,
607            Self::RegionFirst | Self::MetadataOnly => AlterTableState::InvalidateTableCache,
608        }
609    }
610}
611
612#[async_trait]
613impl Procedure for AlterTableProcedure {
614    fn type_name(&self) -> &str {
615        Self::TYPE_NAME
616    }
617
618    async fn execute(&mut self, ctx: &ProcedureContext) -> ProcedureResult<Status> {
619        let state = &self.data.state;
620
621        let step = state.as_ref();
622
623        let _timer = metrics::METRIC_META_PROCEDURE_ALTER_TABLE
624            .with_label_values(&[step])
625            .start_timer();
626
627        match state {
628            AlterTableState::Prepare => self.on_prepare().await,
629            AlterTableState::SubmitAlterRegionRequests => {
630                self.submit_alter_region_requests(ctx.procedure_id, ctx.provider.as_ref())
631                    .await
632            }
633            AlterTableState::UpdateMetadata => self.on_update_metadata().await,
634            AlterTableState::InvalidateTableCache => self.on_broadcast().await,
635        }
636        .map_err(map_to_procedure_error)
637    }
638
639    fn dump(&self) -> ProcedureResult<String> {
640        serde_json::to_string(&self.data).context(ToJsonSnafu)
641    }
642
643    fn lock_key(&self) -> LockKey {
644        let key = self.lock_key_inner();
645
646        LockKey::new(key)
647    }
648
649    fn poison_keys(&self) -> PoisonKeys {
650        PoisonKeys::new(vec![self.table_poison_key()])
651    }
652
653    fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn common_event_recorder::Event>> {
654        if !ctx
655            .event_type_filter
656            .allows(TableDdlEventType::AlterTable.as_str())
657        {
658            return None;
659        }
660        let table_ref = self.data.table_ref();
661        let locator = TableDdlLocator::new(table_ref.catalog, table_ref.schema, table_ref.table)
662            .with_table_id(self.data.table_id());
663        let event = match &ctx.trigger {
664            EventTrigger::Submitted => {
665                let kind = self
666                    .data
667                    .task
668                    .alter_table
669                    .kind
670                    .as_ref()
671                    .and_then(alter_table_kind_name);
672                TableDdlEvent::alter_table_submitted(locator, kind)
673            }
674            _ => TableDdlEvent::lifecycle(TableDdlEventType::AlterTable, [locator]),
675        };
676
677        Some(Box::new(event))
678    }
679}
680
681#[derive(Debug, Serialize, Deserialize, AsRefStr)]
682enum AlterTableState {
683    /// Prepares to alter the table.
684    Prepare,
685    /// Sends alter region requests to Datanode.
686    SubmitAlterRegionRequests,
687    /// Updates table metadata.
688    UpdateMetadata,
689    /// Broadcasts the invalidating table cache instruction.
690    InvalidateTableCache,
691}
692
693// The serialized data of alter table.
694#[derive(Debug, Serialize, Deserialize)]
695pub struct AlterTableData {
696    state: AlterTableState,
697    task: AlterTableTask,
698    table_id: TableId,
699    #[serde(default)]
700    column_metadatas: Vec<ColumnMetadata>,
701    /// Table info value before alteration.
702    table_info_value: Option<DeserializedValueWithBytes<TableInfoValue>>,
703    /// Region distribution for table in case we need to update region options.
704    region_distribution: Option<RegionDistribution>,
705    /// Region locks held by skip-WAL alters.
706    #[serde(default)]
707    region_locks: Vec<RegionId>,
708}
709
710impl AlterTableData {
711    pub fn new(task: AlterTableTask, table_id: TableId, region_locks: Vec<RegionId>) -> Self {
712        Self {
713            state: AlterTableState::Prepare,
714            task,
715            table_id,
716            column_metadatas: vec![],
717            table_info_value: None,
718            region_distribution: None,
719            region_locks,
720        }
721    }
722
723    fn table_ref(&self) -> TableReference<'_> {
724        self.task.table_ref()
725    }
726
727    fn table_id(&self) -> TableId {
728        self.table_id
729    }
730
731    fn table_info(&self) -> Option<&TableInfo> {
732        self.table_info_value
733            .as_ref()
734            .map(|value| &value.table_info)
735    }
736
737    fn flow(&self) -> Result<AlterTableFlow> {
738        // Safety: Checked in `AlterTableProcedure::new`.
739        AlterTableFlow::from_kind(self.task.alter_table.kind.as_ref().unwrap())
740    }
741
742    #[cfg(test)]
743    pub(crate) fn column_metadatas(&self) -> &[ColumnMetadata] {
744        &self.column_metadatas
745    }
746
747    #[cfg(test)]
748    pub(crate) fn set_column_metadatas(&mut self, column_metadatas: Vec<ColumnMetadata>) {
749        self.column_metadatas = column_metadatas;
750    }
751}