1pub 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 pub data: CreateTableData,
61 pub opening_regions: Vec<OperatingRegionGuard>,
63 pub executor: CreateTableExecutor,
65 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 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 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 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 storage
236 .remap_region_routes(&mut table_route.region_routes)
237 .await?;
238 }
239 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 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 let region_wal_options = self.region_wal_options()?.clone();
277 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 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 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 Prepare,
444 DatanodeCreateRegions,
446 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 pub(crate) table_route: Option<PhysicalTableRouteValue>,
460 #[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}