Skip to main content

common_meta/ddl/
create_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
15pub mod executor;
16pub mod template;
17
18use api::v1::CreateTableExpr;
19use async_trait::async_trait;
20use common_error::ext::BoxedError;
21use common_procedure::error::{
22    ExternalSnafu, FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu,
23};
24use common_procedure::local::DynamicKeyLockGuard;
25use common_procedure::{
26    Context as ProcedureContext, EventContext, EventTrigger, LockKey, Procedure, ProcedureId,
27    ProcedureState, Status,
28};
29use common_telemetry::info;
30use serde::{Deserialize, Serialize};
31use snafu::{OptionExt, ResultExt};
32use store_api::metadata::ColumnMetadata;
33use strum::AsRefStr;
34use table::metadata::{TableId, TableInfo};
35use table::table_name::TableName;
36use table::table_reference::TableReference;
37pub(crate) use template::{CreateRequestBuilder, build_template_from_raw_table_info};
38
39use crate::ddl::create_table::executor::CreateTableExecutor;
40use crate::ddl::create_table::template::build_template;
41use crate::ddl::event::table::{TableDdlEvent, TableDdlEventType, TableDdlLocator};
42use crate::ddl::utils::map_to_procedure_error;
43use crate::ddl::{DdlContext, TableMetadata};
44use crate::error::{self, Result};
45use crate::key::table_route::PhysicalTableRouteValue;
46use crate::lock_key::{CatalogLock, SchemaLock, TableNameLock};
47use crate::metrics;
48use crate::peer::PeerAllocContext;
49use crate::region_keeper::OperatingRegionGuard;
50use crate::rpc::ddl::{CreateTableTask, QueryContext};
51use crate::rpc::router::{RegionRoute, operating_leader_region_roles};
52use crate::wal_provider::{
53    RegionWalOptions, acquire_remote_wal_read_locks, optional_region_wal_options_serde,
54    refresh_initial_pruned_entry_ids,
55};
56
57pub struct CreateTableProcedure {
58    pub context: DdlContext,
59    /// The serializable data.
60    pub data: CreateTableData,
61    /// The guards of opening.
62    pub opening_regions: Vec<OperatingRegionGuard>,
63    /// The executor of the procedure.
64    pub executor: CreateTableExecutor,
65    /// The guards of remote WAL topic locks.
66    remote_wal_lock_guards: Vec<DynamicKeyLockGuard>,
67}
68
69fn build_executor_from_create_table_data(
70    create_table_expr: &CreateTableExpr,
71) -> Result<CreateTableExecutor> {
72    let template = build_template(create_table_expr)?;
73    let builder = CreateRequestBuilder::new(template, None);
74    let table_name = TableName::new(
75        create_table_expr.catalog_name.clone(),
76        create_table_expr.schema_name.clone(),
77        create_table_expr.table_name.clone(),
78    );
79    let executor =
80        CreateTableExecutor::new(table_name, create_table_expr.create_if_not_exists, builder);
81    Ok(executor)
82}
83
84impl CreateTableProcedure {
85    pub const TYPE_NAME: &'static str = "metasrv-procedure::CreateTable";
86
87    pub fn new(task: CreateTableTask, context: DdlContext) -> Result<Self> {
88        Self::new_with_query_context(task, QueryContext::default(), context)
89    }
90
91    pub fn new_with_query_context(
92        task: CreateTableTask,
93        query_context: QueryContext,
94        context: DdlContext,
95    ) -> Result<Self> {
96        let executor = build_executor_from_create_table_data(&task.create_table)?;
97
98        Ok(Self {
99            context,
100            data: CreateTableData::new(task, query_context),
101            opening_regions: vec![],
102            executor,
103            remote_wal_lock_guards: vec![],
104        })
105    }
106
107    pub fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
108        let data: CreateTableData = serde_json::from_str(json).context(FromJsonSnafu)?;
109        let create_table_expr = &data.task.create_table;
110        let executor = build_executor_from_create_table_data(create_table_expr)
111            .map_err(BoxedError::new)
112            .context(ExternalSnafu {
113                clean_poisons: false,
114            })?;
115
116        Ok(CreateTableProcedure {
117            context,
118            data,
119            opening_regions: vec![],
120            executor,
121            remote_wal_lock_guards: vec![],
122        })
123    }
124
125    fn table_info(&self) -> &TableInfo {
126        &self.data.task.table_info
127    }
128
129    pub(crate) fn table_id(&self) -> TableId {
130        self.table_info().ident.table_id
131    }
132
133    fn region_wal_options(&self) -> Result<&RegionWalOptions> {
134        self.data
135            .region_wal_options
136            .as_ref()
137            .context(error::UnexpectedSnafu {
138                err_msg: "region_wal_options is not allocated",
139            })
140    }
141
142    fn table_route(&self) -> Result<&PhysicalTableRouteValue> {
143        self.data
144            .table_route
145            .as_ref()
146            .context(error::UnexpectedSnafu {
147                err_msg: "table_route is not allocated",
148            })
149    }
150
151    /// On the prepare step, it performs:
152    /// - Checks whether the table exists.
153    /// - Allocates the table id.
154    ///
155    /// Abort(non-retry):
156    /// - TableName exists and `create_if_not_exists` is false.
157    /// - Failed to allocate [TableMetadata].
158    pub(crate) async fn on_prepare(&mut self) -> Result<Status> {
159        let table_id = self
160            .executor
161            .on_prepare(&self.context.table_metadata_manager)
162            .await?;
163        // Return the table id if the table already exists.
164        if let Some(table_id) = table_id {
165            return Ok(Status::done_with_output(table_id));
166        }
167
168        let alloc_context = match &self.context.peer_alloc_context_provider {
169            Some(provider) => provider.build(&self.data.query_context).await?,
170            None => PeerAllocContext::default(),
171        };
172        let TableMetadata {
173            table_id,
174            table_route,
175            region_wal_options,
176        } = self
177            .context
178            .table_metadata_allocator
179            .create_with_context(&self.data.task, &alloc_context)
180            .await?;
181        self.set_allocated_metadata(table_id, table_route, region_wal_options);
182        self.data.state = CreateTableState::DatanodeCreateRegions;
183
184        Ok(Status::executing(true))
185    }
186
187    async fn ensure_remote_wal_read_locks(&mut self, ctx: &ProcedureContext) -> Result<()> {
188        if !self.remote_wal_lock_guards.is_empty() {
189            return Ok(());
190        }
191
192        self.remote_wal_lock_guards =
193            acquire_remote_wal_read_locks(ctx, self.region_wal_options()?).await;
194
195        Ok(())
196    }
197
198    async fn refresh_initial_pruned_entry_ids(&mut self) -> Result<()> {
199        let region_wal_options =
200            self.data
201                .region_wal_options
202                .as_mut()
203                .context(error::UnexpectedSnafu {
204                    err_msg: "region_wal_options is not allocated",
205                })?;
206        refresh_initial_pruned_entry_ids(&self.context.table_metadata_manager, region_wal_options)
207            .await
208    }
209
210    /// Creates regions on datanodes
211    ///
212    /// Abort(non-retry):
213    /// - Failed to create [CreateRequestBuilder].
214    /// - Failed to get the table route of physical table (for logical table).
215    ///
216    /// Retry:
217    /// - If the underlying servers returns one of the following [Code](tonic::status::Code):
218    ///   - [Code::Cancelled](tonic::status::Code::Cancelled)
219    ///   - [Code::DeadlineExceeded](tonic::status::Code::DeadlineExceeded)
220    ///   - [Code::Unavailable](tonic::status::Code::Unavailable)
221    pub async fn on_datanode_create_regions(&mut self, retrying: bool) -> Result<Status> {
222        let mut table_route = self.table_route()?.clone();
223        if retrying {
224            info!(
225                "Remapping region routes addresses for retrying create regions for table: {}",
226                self.data.table_ref()
227            );
228            let storage = self
229                .context
230                .table_metadata_manager
231                .table_route_manager()
232                .table_route_storage();
233            // The peer addresses may change during retries,
234            // so we always remap the region routes.
235            storage
236                .remap_region_routes(&mut table_route.region_routes)
237                .await?;
238        }
239        // Registers opening regions
240        let guards = self.register_opening_regions(&self.context, &table_route.region_routes)?;
241        if !guards.is_empty() {
242            self.opening_regions = guards;
243        }
244        self.create_regions(&table_route.region_routes).await
245    }
246
247    async fn create_regions(&mut self, region_routes: &[RegionRoute]) -> Result<Status> {
248        let table_id = self.table_id();
249        let region_wal_options = self.region_wal_options()?;
250        let column_metadatas = self
251            .executor
252            .on_create_regions(
253                &self.context.node_manager,
254                table_id,
255                region_routes,
256                region_wal_options,
257            )
258            .await?;
259
260        self.data.column_metadatas = column_metadatas;
261        self.data.state = CreateTableState::CreateMetadata;
262        Ok(Status::executing(true))
263    }
264
265    /// Creates table metadata
266    ///
267    /// Abort(not-retry):
268    /// - Failed to create table metadata.
269    async fn on_create_metadata(&mut self, pid: ProcedureId) -> Result<Status> {
270        let table_id = self.table_id();
271        let table_ref = self.data.table_ref();
272        let manager = &self.context.table_metadata_manager;
273
274        let raw_table_info = self.table_info().clone();
275        // Safety: the region_wal_options must be allocated.
276        let region_wal_options = self.region_wal_options()?.clone();
277        // Safety: the table_route must be allocated.
278        let physical_table_route = self.table_route()?.clone();
279        self.executor
280            .on_create_metadata(
281                manager,
282                &self.context.region_failure_detector_controller,
283                raw_table_info,
284                &self.data.column_metadatas,
285                physical_table_route,
286                region_wal_options,
287            )
288            .await?;
289
290        info!(
291            "Successfully created table: {}, table_id: {}, procedure_id: {}",
292            table_ref, table_id, pid
293        );
294
295        self.opening_regions.clear();
296        self.remote_wal_lock_guards.clear();
297        Ok(Status::done_with_output(table_id))
298    }
299
300    /// Registers and returns the guards of the opening region if they don't exist.
301    fn register_opening_regions(
302        &self,
303        context: &DdlContext,
304        region_routes: &[RegionRoute],
305    ) -> Result<Vec<OperatingRegionGuard>> {
306        let opening_regions = operating_leader_region_roles(region_routes);
307        if self.opening_regions.len() == opening_regions.len() {
308            return Ok(vec![]);
309        }
310
311        let mut opening_region_guards = Vec::with_capacity(opening_regions.len());
312
313        for (region_id, datanode_id, role) in opening_regions {
314            let guard = context
315                .memory_region_keeper
316                .register_with_role(datanode_id, region_id, role)
317                .context(error::RegionOperatingRaceSnafu {
318                    region_id,
319                    peer_id: datanode_id,
320                })?;
321            opening_region_guards.push(guard);
322        }
323        Ok(opening_region_guards)
324    }
325
326    pub fn set_allocated_metadata(
327        &mut self,
328        table_id: TableId,
329        table_route: PhysicalTableRouteValue,
330        region_wal_options: RegionWalOptions,
331    ) {
332        self.data.task.table_info.ident.table_id = table_id;
333        self.data.table_route = Some(table_route);
334        self.data.region_wal_options = Some(region_wal_options);
335    }
336}
337
338#[async_trait]
339impl Procedure for CreateTableProcedure {
340    fn type_name(&self) -> &str {
341        Self::TYPE_NAME
342    }
343
344    fn recover(&mut self) -> ProcedureResult<()> {
345        // Only registers regions if the table route is allocated.
346        if let Some(x) = &self.data.table_route {
347            self.opening_regions = self
348                .register_opening_regions(&self.context, &x.region_routes)
349                .map_err(BoxedError::new)
350                .context(ExternalSnafu {
351                    clean_poisons: false,
352                })?;
353        }
354
355        Ok(())
356    }
357
358    async fn execute(&mut self, ctx: &ProcedureContext) -> ProcedureResult<Status> {
359        let state = &self.data.state;
360
361        let _timer = metrics::METRIC_META_PROCEDURE_CREATE_TABLE
362            .with_label_values(&[state.as_ref()])
363            .start_timer();
364
365        match state {
366            CreateTableState::Prepare => self.on_prepare().await,
367            CreateTableState::DatanodeCreateRegions => {
368                async {
369                    self.ensure_remote_wal_read_locks(ctx).await?;
370                    self.refresh_initial_pruned_entry_ids().await?;
371                    let retrying = ctx.is_retrying().await.unwrap_or(false);
372                    self.on_datanode_create_regions(retrying).await
373                }
374                .await
375            }
376            CreateTableState::CreateMetadata => {
377                async {
378                    self.ensure_remote_wal_read_locks(ctx).await?;
379                    self.on_create_metadata(ctx.procedure_id).await
380                }
381                .await
382            }
383        }
384        .map_err(map_to_procedure_error)
385    }
386
387    fn dump(&self) -> ProcedureResult<String> {
388        serde_json::to_string(&self.data).context(ToJsonSnafu)
389    }
390
391    fn lock_key(&self) -> LockKey {
392        let table_ref = &self.data.table_ref();
393
394        LockKey::new(vec![
395            CatalogLock::Read(table_ref.catalog).into(),
396            SchemaLock::read(table_ref.catalog, table_ref.schema).into(),
397            TableNameLock::new(table_ref.catalog, table_ref.schema, table_ref.table).into(),
398        ])
399    }
400
401    fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn common_event_recorder::Event>> {
402        if !ctx
403            .event_type_filter
404            .allows(TableDdlEventType::CreateTable.as_str())
405        {
406            return None;
407        }
408        let table_ref = self.data.table_ref();
409        let locator = TableDdlLocator::new(table_ref.catalog, table_ref.schema, table_ref.table);
410        let event = match &ctx.trigger {
411            EventTrigger::Submitted => {
412                let create_table = &self.data.task.create_table;
413                TableDdlEvent::create_table_submitted(
414                    locator,
415                    create_table.create_if_not_exists,
416                    &create_table.engine,
417                )
418            }
419            EventTrigger::Succeeded => match ctx.lifecycle_state {
420                ProcedureState::Done {
421                    output: Some(output),
422                } => output
423                    .downcast_ref::<TableId>()
424                    .copied()
425                    .map(|table_id| {
426                        TableDdlEvent::create_table_succeeded(locator.clone(), table_id)
427                    })
428                    .unwrap_or_else(|| {
429                        TableDdlEvent::lifecycle(TableDdlEventType::CreateTable, [locator.clone()])
430                    }),
431                _ => TableDdlEvent::lifecycle(TableDdlEventType::CreateTable, [locator.clone()]),
432            },
433            _ => TableDdlEvent::lifecycle(TableDdlEventType::CreateTable, [locator]),
434        };
435
436        Some(Box::new(event))
437    }
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, PartialEq)]
441pub enum CreateTableState {
442    /// Prepares to create the table
443    Prepare,
444    /// Creates regions on the Datanode
445    DatanodeCreateRegions,
446    /// Creates metadata
447    CreateMetadata,
448}
449
450#[derive(Debug, Serialize, Deserialize)]
451pub struct CreateTableData {
452    pub state: CreateTableState,
453    pub task: CreateTableTask,
454    #[serde(default)]
455    pub query_context: QueryContext,
456    #[serde(default)]
457    pub column_metadatas: Vec<ColumnMetadata>,
458    /// None stands for not allocated yet.
459    pub(crate) table_route: Option<PhysicalTableRouteValue>,
460    /// None stands for not allocated yet.
461    #[serde(default)]
462    #[serde(with = "optional_region_wal_options_serde")]
463    pub region_wal_options: Option<RegionWalOptions>,
464}
465
466impl CreateTableData {
467    pub fn new(task: CreateTableTask, query_context: QueryContext) -> Self {
468        CreateTableData {
469            state: CreateTableState::Prepare,
470            column_metadatas: vec![],
471            task,
472            query_context,
473            table_route: None,
474            region_wal_options: None,
475        }
476    }
477
478    fn table_ref(&self) -> TableReference<'_> {
479        self.task.table_ref()
480    }
481}