1mod check;
16mod metadata;
17mod region_request;
18mod update_metadata;
19
20use api::region::RegionResponse;
21use api::v1::CreateTableExpr;
22use async_trait::async_trait;
23use common_catalog::consts::METRIC_ENGINE;
24use common_event_recorder::Event;
25use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu};
26use common_procedure::{
27 Context as ProcedureContext, EventContext, EventTrigger, LockKey, Procedure, ProcedureState,
28 Status,
29};
30use common_telemetry::{debug, error, warn};
31use futures::future;
32pub use region_request::create_region_request_builder;
33use serde::{Deserialize, Serialize};
34use snafu::ResultExt;
35use store_api::metadata::ColumnMetadata;
36use store_api::metric_engine_consts::ALTER_PHYSICAL_EXTENSION_KEY;
37use store_api::storage::RegionNumber;
38use strum::AsRefStr;
39use table::metadata::{TableId, TableInfo};
40
41use crate::ddl::DdlContext;
42use crate::ddl::event::table::{TableDdlEvent, TableDdlEventType, TableDdlLocator};
43use crate::ddl::utils::{
44 add_peer_context_if_needed, extract_column_metadatas, map_to_procedure_error,
45 sync_follower_regions,
46};
47use crate::error::Result;
48use crate::key::table_route::TableRouteValue;
49use crate::lock_key::{CatalogLock, SchemaLock, TableLock, TableNameLock};
50use crate::metrics;
51use crate::rpc::ddl::CreateTableTask;
52use crate::rpc::router::{RegionRoute, find_leaders};
53
54pub struct CreateLogicalTablesProcedure {
55 pub context: DdlContext,
56 pub data: CreateTablesData,
57}
58
59impl CreateLogicalTablesProcedure {
60 pub const TYPE_NAME: &'static str = "metasrv-procedure::CreateLogicalTables";
61
62 pub fn new(
63 tasks: Vec<CreateTableTask>,
64 physical_table_id: TableId,
65 context: DdlContext,
66 ) -> Self {
67 Self {
68 context,
69 data: CreateTablesData {
70 state: CreateTablesState::Prepare,
71 tasks,
72 table_ids_already_exists: vec![],
73 physical_table_id,
74 physical_region_numbers: vec![],
75 physical_columns: vec![],
76 physical_partition_columns: vec![],
77 },
78 }
79 }
80
81 pub fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
82 let data = serde_json::from_str(json).context(FromJsonSnafu)?;
83 Ok(Self { context, data })
84 }
85
86 pub(crate) async fn on_prepare(&mut self) -> Result<Status> {
97 self.check_input_tasks()?;
98 self.fill_physical_table_info().await?;
100 self.merge_partition_columns_into_logical_tables()?;
102 self.check_tables_already_exist().await?;
104
105 if self
107 .data
108 .table_ids_already_exists
109 .iter()
110 .all(Option::is_some)
111 {
112 return Ok(Status::done_with_output(
113 self.data
114 .table_ids_already_exists
115 .drain(..)
116 .flatten()
117 .collect::<Vec<_>>(),
118 ));
119 }
120
121 self.allocate_table_ids().await?;
123
124 self.data.state = CreateTablesState::DatanodeCreateRegions;
125 Ok(Status::executing(true))
126 }
127
128 pub async fn on_datanode_create_regions(&mut self) -> Result<Status> {
129 let (_, physical_table_route) = self
130 .context
131 .table_metadata_manager
132 .table_route_manager()
133 .get_physical_table_route(self.data.physical_table_id)
134 .await?;
135
136 self.create_regions(&physical_table_route.region_routes)
137 .await
138 }
139
140 pub async fn on_create_metadata(&mut self) -> Result<Status> {
146 self.update_physical_table_metadata().await?;
147 let table_ids = self.create_logical_tables_metadata().await?;
148
149 Ok(Status::done_with_output(table_ids))
150 }
151
152 async fn create_regions(&mut self, region_routes: &[RegionRoute]) -> Result<Status> {
153 let leaders = find_leaders(region_routes);
154 let mut create_region_tasks = Vec::with_capacity(leaders.len());
155
156 for peer in leaders {
157 let requester = self.context.node_manager.datanode(&peer).await;
158 let Some(request) = self.make_request(&peer, region_routes)? else {
159 debug!("no region request to send to datanode {}", peer);
160 break;
163 };
164
165 create_region_tasks.push(async move {
166 requester
167 .handle(request)
168 .await
169 .map_err(add_peer_context_if_needed(peer))
170 });
171 }
172
173 let mut results = future::join_all(create_region_tasks)
174 .await
175 .into_iter()
176 .collect::<Result<Vec<_>>>()?;
177
178 if let Some(column_metadatas) =
179 extract_column_metadatas(&mut results, ALTER_PHYSICAL_EXTENSION_KEY)?
180 {
181 self.data.physical_columns = column_metadatas;
182 } else {
183 warn!(
184 "creating logical table result doesn't contains extension key `{ALTER_PHYSICAL_EXTENSION_KEY}`,leaving the physical table's schema unchanged"
185 );
186 }
187
188 self.submit_sync_region_requests(&results, region_routes)
189 .await;
190 self.data.state = CreateTablesState::CreateMetadata;
191 Ok(Status::executing(true))
192 }
193
194 async fn submit_sync_region_requests(
195 &self,
196 results: &[RegionResponse],
197 region_routes: &[RegionRoute],
198 ) {
199 if let Err(err) = sync_follower_regions(
200 &self.context,
201 self.data.physical_table_id,
202 results,
203 region_routes,
204 METRIC_ENGINE,
205 )
206 .await
207 {
208 error!(err; "Failed to sync regions for physical table_id: {}",self.data.physical_table_id);
209 }
210 }
211}
212
213impl CreateLogicalTablesProcedure {
214 fn event_locators(&self) -> impl Iterator<Item = TableDdlLocator> + '_ {
215 self.data.tasks.iter().map(|task| {
216 TableDdlLocator::new(
217 &task.create_table.catalog_name,
218 &task.create_table.schema_name,
219 &task.create_table.table_name,
220 )
221 .with_physical_table_id(self.data.physical_table_id)
222 })
223 }
224}
225
226#[async_trait]
227impl Procedure for CreateLogicalTablesProcedure {
228 fn type_name(&self) -> &str {
229 Self::TYPE_NAME
230 }
231
232 async fn execute(&mut self, _ctx: &ProcedureContext) -> ProcedureResult<Status> {
233 let state = &self.data.state;
234
235 let _timer = metrics::METRIC_META_PROCEDURE_CREATE_TABLES
236 .with_label_values(&[state.as_ref()])
237 .start_timer();
238
239 match state {
240 CreateTablesState::Prepare => self.on_prepare().await,
241 CreateTablesState::DatanodeCreateRegions => self.on_datanode_create_regions().await,
242 CreateTablesState::CreateMetadata => self.on_create_metadata().await,
243 }
244 .map_err(map_to_procedure_error)
245 }
246
247 fn dump(&self) -> ProcedureResult<String> {
248 serde_json::to_string(&self.data).context(ToJsonSnafu)
249 }
250
251 fn lock_key(&self) -> LockKey {
252 let mut lock_key = Vec::with_capacity(2 + 1 + self.data.tasks.len());
256 let table_ref = self.data.tasks[0].table_ref();
257 lock_key.push(CatalogLock::Read(table_ref.catalog).into());
258 lock_key.push(SchemaLock::read(table_ref.catalog, table_ref.schema).into());
259 lock_key.push(TableLock::Write(self.data.physical_table_id).into());
260
261 for task in &self.data.tasks {
262 lock_key.push(
263 TableNameLock::new(
264 &task.create_table.catalog_name,
265 &task.create_table.schema_name,
266 &task.create_table.table_name,
267 )
268 .into(),
269 );
270 }
271 LockKey::new(lock_key)
272 }
273
274 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
275 if !ctx
276 .event_type_filter
277 .allows(TableDdlEventType::CreateLogicalTables.as_str())
278 {
279 return None;
280 }
281 let event = match &ctx.trigger {
282 EventTrigger::Submitted => TableDdlEvent::create_logical_tables_submitted(
283 self.event_locators(),
284 self.data.tasks.len(),
285 ),
286 EventTrigger::Succeeded => match ctx.lifecycle_state {
287 ProcedureState::Done {
288 output: Some(output),
289 } => output
290 .downcast_ref::<Vec<TableId>>()
291 .map(|table_ids| {
292 debug_assert_eq!(self.data.tasks.len(), table_ids.len());
293 let locators = self
294 .event_locators()
295 .zip(table_ids)
296 .map(|(locator, table_id)| locator.with_table_id(*table_id));
297 TableDdlEvent::create_logical_tables_succeeded(locators)
298 })
299 .unwrap_or_else(|| {
300 TableDdlEvent::lifecycle(
301 TableDdlEventType::CreateLogicalTables,
302 self.event_locators(),
303 )
304 }),
305 _ => TableDdlEvent::lifecycle(
306 TableDdlEventType::CreateLogicalTables,
307 self.event_locators(),
308 ),
309 },
310 _ => TableDdlEvent::lifecycle(
311 TableDdlEventType::CreateLogicalTables,
312 self.event_locators(),
313 ),
314 };
315
316 Some(Box::new(event))
317 }
318}
319
320#[derive(Debug, Serialize, Deserialize)]
321pub struct CreateTablesData {
322 state: CreateTablesState,
323 tasks: Vec<CreateTableTask>,
324 table_ids_already_exists: Vec<Option<TableId>>,
325 physical_table_id: TableId,
326 physical_region_numbers: Vec<RegionNumber>,
327 physical_columns: Vec<ColumnMetadata>,
328 physical_partition_columns: Vec<String>,
329}
330
331impl CreateTablesData {
332 pub fn state(&self) -> &CreateTablesState {
333 &self.state
334 }
335
336 fn all_create_table_exprs(&self) -> Vec<&CreateTableExpr> {
337 self.tasks
338 .iter()
339 .map(|task| &task.create_table)
340 .collect::<Vec<_>>()
341 }
342
343 fn remaining_tasks(&self) -> Vec<(TableInfo, TableRouteValue)> {
346 self.tasks
347 .iter()
348 .zip(self.table_ids_already_exists.iter())
349 .flat_map(|(task, table_id)| {
350 if table_id.is_none() {
351 let table_info = task.table_info.clone();
352 let table_route = TableRouteValue::logical(self.physical_table_id);
353 Some((table_info, table_route))
354 } else {
355 None
356 }
357 })
358 .collect::<Vec<_>>()
359 }
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize, AsRefStr)]
363pub enum CreateTablesState {
364 Prepare,
366 DatanodeCreateRegions,
368 CreateMetadata,
370}