1use std::sync::Arc;
16
17use async_trait::async_trait;
18use chrono::Utc;
19use common_catalog::{format_full_flow_name, format_full_table_name};
20use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu};
21use common_procedure::{Context as ProcedureContext, LockKey, Procedure, Status};
22use common_telemetry::tracing::info;
23use datatypes::schema::{COMMENT_KEY as COLUMN_COMMENT_KEY, Schema};
24use serde::{Deserialize, Serialize};
25use snafu::{OptionExt, ResultExt, ensure};
26use store_api::storage::TableId;
27use strum::AsRefStr;
28use table::metadata::TableInfo;
29use table::requests::COMMENT_KEY as TABLE_COMMENT_KEY;
30use table::table_name::TableName;
31
32use crate::cache_invalidator::Context;
33use crate::ddl::DdlContext;
34use crate::ddl::utils::map_to_procedure_error;
35use crate::error::{ColumnNotFoundSnafu, FlowNotFoundSnafu, Result, TableNotFoundSnafu};
36use crate::instruction::CacheIdent;
37use crate::key::flow::flow_info::{FlowInfoKey, FlowInfoValue};
38use crate::key::table_info::{TableInfoKey, TableInfoValue};
39use crate::key::table_name::TableNameKey;
40use crate::key::{DeserializedValueWithBytes, FlowId, MetadataKey, MetadataValue};
41use crate::lock_key::{CatalogLock, FlowLock, FlowNameLock, SchemaLock, TableLock, TableNameLock};
42use crate::rpc::ddl::{CommentObjectId, CommentObjectType, CommentOnTask};
43use crate::rpc::store::PutRequest;
44
45pub struct CommentOnProcedure {
46 pub context: DdlContext,
47 pub data: CommentOnData,
48}
49
50impl CommentOnProcedure {
51 pub const TYPE_NAME: &'static str = "metasrv-procedure::CommentOn";
52
53 pub fn new(task: CommentOnTask, context: DdlContext) -> Self {
54 Self {
55 context,
56 data: CommentOnData::new(task),
57 }
58 }
59
60 pub fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
61 let data = serde_json::from_str(json).context(FromJsonSnafu)?;
62
63 Ok(Self { context, data })
64 }
65
66 pub async fn on_prepare(&mut self) -> Result<Status> {
67 match self.data.object_type {
68 CommentObjectType::Table | CommentObjectType::Column => {
69 self.prepare_table_or_column().await?;
70 }
71 CommentObjectType::Flow => {
72 self.prepare_flow().await?;
73 }
74 }
75
76 if self.data.is_unchanged {
78 let object_desc = match self.data.object_type {
79 CommentObjectType::Table => format!(
80 "table {}",
81 format_full_table_name(
82 &self.data.catalog_name,
83 &self.data.schema_name,
84 &self.data.object_name,
85 )
86 ),
87 CommentObjectType::Column => format!(
88 "column {}.{}",
89 format_full_table_name(
90 &self.data.catalog_name,
91 &self.data.schema_name,
92 &self.data.object_name,
93 ),
94 self.data.column_name.as_ref().unwrap()
95 ),
96 CommentObjectType::Flow => {
97 format!("flow {}.{}", self.data.catalog_name, self.data.object_name)
98 }
99 };
100 info!("Comment unchanged for {}, skipping update", object_desc);
101 return Ok(Status::done());
102 }
103
104 self.data.state = CommentOnState::UpdateMetadata;
105 Ok(Status::executing(true))
106 }
107
108 async fn prepare_table_or_column(&mut self) -> Result<()> {
109 let table_id = if let Some(table_id) = self.data.table_id {
110 table_id
111 } else {
112 let table_name_key = TableNameKey::new(
113 &self.data.catalog_name,
114 &self.data.schema_name,
115 &self.data.object_name,
116 );
117
118 self.context
119 .table_metadata_manager
120 .table_name_manager()
121 .get(table_name_key)
122 .await?
123 .with_context(|| TableNotFoundSnafu {
124 table_name: format_full_table_name(
125 &self.data.catalog_name,
126 &self.data.schema_name,
127 &self.data.object_name,
128 ),
129 })?
130 .table_id()
131 };
132
133 let table_info = self
134 .context
135 .table_metadata_manager
136 .table_info_manager()
137 .get(table_id)
138 .await?
139 .with_context(|| TableNotFoundSnafu {
140 table_name: format_full_table_name(
141 &self.data.catalog_name,
142 &self.data.schema_name,
143 &self.data.object_name,
144 ),
145 })?;
146
147 if self.data.object_type == CommentObjectType::Column {
149 let column_name = self.data.column_name.as_ref().unwrap();
150 let column_exists = table_info
151 .table_info
152 .meta
153 .schema
154 .column_schemas()
155 .iter()
156 .any(|col| &col.name == column_name);
157
158 ensure!(
159 column_exists,
160 ColumnNotFoundSnafu {
161 column_name,
162 column_id: 0u32, }
164 );
165 }
166
167 self.data.table_id = Some(table_id);
168
169 match self.data.object_type {
171 CommentObjectType::Table => {
172 let current_comment = &table_info.table_info.desc;
173 if &self.data.comment == current_comment {
174 self.data.is_unchanged = true;
175 }
176 }
177 CommentObjectType::Column => {
178 let column_name = self.data.column_name.as_ref().unwrap();
179 let column_schema = table_info
180 .table_info
181 .meta
182 .schema
183 .column_schemas()
184 .iter()
185 .find(|col| &col.name == column_name)
186 .unwrap(); let current_comment = column_schema.metadata().get(COLUMN_COMMENT_KEY);
189 if self.data.comment.as_deref() == current_comment.map(String::as_str) {
190 self.data.is_unchanged = true;
191 }
192 }
193 CommentObjectType::Flow => {
194 }
196 }
197
198 self.data.table_info = Some(table_info);
199
200 Ok(())
201 }
202
203 async fn prepare_flow(&mut self) -> Result<()> {
204 let flow_id = if let Some(flow_id) = self.data.flow_id {
205 flow_id
206 } else {
207 self.context
208 .flow_metadata_manager
209 .flow_name_manager()
210 .get(&self.data.catalog_name, &self.data.object_name)
211 .await?
212 .with_context(|| FlowNotFoundSnafu {
213 flow_name: format_full_flow_name(
214 &self.data.catalog_name,
215 &self.data.object_name,
216 ),
217 })?
218 .flow_id()
219 };
220 let flow_info = self
221 .context
222 .flow_metadata_manager
223 .flow_info_manager()
224 .get_raw(flow_id)
225 .await?
226 .with_context(|| FlowNotFoundSnafu {
227 flow_name: format_full_flow_name(&self.data.catalog_name, &self.data.object_name),
228 })?;
229
230 self.data.flow_id = Some(flow_id);
231
232 let current_comment = &flow_info.get_inner_ref().comment;
234 let new_comment = self.data.comment.as_deref().unwrap_or("");
235 if new_comment == current_comment.as_str() {
236 self.data.is_unchanged = true;
237 }
238
239 self.data.flow_info = Some(flow_info);
240
241 Ok(())
242 }
243
244 pub async fn on_update_metadata(&mut self) -> Result<Status> {
245 match self.data.object_type {
246 CommentObjectType::Table => {
247 self.update_table_comment().await?;
248 }
249 CommentObjectType::Column => {
250 self.update_column_comment().await?;
251 }
252 CommentObjectType::Flow => {
253 self.update_flow_comment().await?;
254 }
255 }
256
257 self.data.state = CommentOnState::InvalidateCache;
258 Ok(Status::executing(true))
259 }
260
261 async fn update_table_comment(&mut self) -> Result<()> {
262 let table_info_value = self.data.table_info.as_ref().unwrap();
263 let mut new_table_info = table_info_value.table_info.clone();
264
265 new_table_info.desc = self.data.comment.clone();
266
267 sync_table_comment_option(
269 &mut new_table_info.meta.options,
270 new_table_info.desc.as_deref(),
271 );
272
273 self.update_table_info(table_info_value, new_table_info)
274 .await?;
275
276 info!(
277 "Updated comment for table {}.{}.{}",
278 self.data.catalog_name, self.data.schema_name, self.data.object_name
279 );
280
281 Ok(())
282 }
283
284 async fn update_column_comment(&mut self) -> Result<()> {
285 let table_info_value = self.data.table_info.as_ref().unwrap();
286 let mut new_table_info = table_info_value.table_info.clone();
287
288 let column_name = self.data.column_name.as_ref().unwrap();
289 let mut column_schemas = new_table_info.meta.schema.column_schemas().to_vec();
290 let column_schema = column_schemas
291 .iter_mut()
292 .find(|col| &col.name == column_name)
293 .unwrap(); update_column_comment_metadata(column_schema, self.data.comment.clone());
296
297 new_table_info.meta.schema = Arc::new(Schema::new_with_version(
298 column_schemas,
299 new_table_info.meta.schema.version(),
300 ));
301 self.update_table_info(table_info_value, new_table_info)
302 .await?;
303
304 info!(
305 "Updated comment for column {}.{}.{}.{}",
306 self.data.catalog_name, self.data.schema_name, self.data.object_name, column_name
307 );
308
309 Ok(())
310 }
311
312 async fn update_flow_comment(&mut self) -> Result<()> {
313 let flow_id = self.data.flow_id.unwrap();
314 let flow_info_value = self.data.flow_info.as_ref().unwrap();
315
316 let mut new_flow_info = flow_info_value.get_inner_ref().clone();
317 new_flow_info.comment = self.data.comment.clone().unwrap_or_default();
318 new_flow_info.updated_time = Utc::now();
319
320 let raw_value = new_flow_info.try_as_raw_value()?;
321
322 self.context
323 .table_metadata_manager
324 .kv_backend()
325 .put(
326 PutRequest::new()
327 .with_key(FlowInfoKey::new(flow_id).to_bytes())
328 .with_value(raw_value),
329 )
330 .await?;
331
332 info!(
333 "Updated comment for flow {}.{}",
334 self.data.catalog_name, self.data.object_name
335 );
336
337 Ok(())
338 }
339
340 async fn update_table_info(
341 &self,
342 current_table_info: &DeserializedValueWithBytes<TableInfoValue>,
343 new_table_info: TableInfo,
344 ) -> Result<()> {
345 let table_id = current_table_info.table_info.ident.table_id;
346 let new_table_info_value = current_table_info.update(new_table_info);
347 let raw_value = new_table_info_value.try_as_raw_value()?;
348
349 self.context
350 .table_metadata_manager
351 .kv_backend()
352 .put(
353 PutRequest::new()
354 .with_key(TableInfoKey::new(table_id).to_bytes())
355 .with_value(raw_value),
356 )
357 .await?;
358
359 Ok(())
360 }
361
362 pub async fn on_invalidate_cache(&mut self) -> Result<Status> {
363 let cache_invalidator = &self.context.cache_invalidator;
364
365 match self.data.object_type {
366 CommentObjectType::Table | CommentObjectType::Column => {
367 let table_id = self.data.table_id.unwrap();
368 let table_name = TableName::new(
369 self.data.catalog_name.clone(),
370 self.data.schema_name.clone(),
371 self.data.object_name.clone(),
372 );
373
374 let cache_ident = vec![
375 CacheIdent::TableId(table_id),
376 CacheIdent::TableName(table_name),
377 ];
378
379 cache_invalidator
380 .invalidate(&Context::default(), &cache_ident)
381 .await?;
382 }
383 CommentObjectType::Flow => {
384 let flow_id = self.data.flow_id.unwrap();
385 let cache_ident = vec![CacheIdent::FlowId(flow_id)];
386
387 cache_invalidator
388 .invalidate(&Context::default(), &cache_ident)
389 .await?;
390 }
391 }
392
393 Ok(Status::done())
394 }
395}
396
397#[async_trait]
398impl Procedure for CommentOnProcedure {
399 fn type_name(&self) -> &str {
400 Self::TYPE_NAME
401 }
402
403 async fn execute(&mut self, _ctx: &ProcedureContext) -> ProcedureResult<Status> {
404 match self.data.state {
405 CommentOnState::Prepare => self.on_prepare().await,
406 CommentOnState::UpdateMetadata => self.on_update_metadata().await,
407 CommentOnState::InvalidateCache => self.on_invalidate_cache().await,
408 }
409 .map_err(map_to_procedure_error)
410 }
411
412 fn dump(&self) -> ProcedureResult<String> {
413 serde_json::to_string(&self.data).context(ToJsonSnafu)
414 }
415
416 fn lock_key(&self) -> LockKey {
417 let catalog = &self.data.catalog_name;
418 let schema = &self.data.schema_name;
419
420 let lock_key = match self.data.object_type {
421 CommentObjectType::Table | CommentObjectType::Column => {
422 let mut lock_key = vec![
423 CatalogLock::Read(catalog).into(),
424 SchemaLock::read(catalog, schema).into(),
425 ];
426 if let Some(table_id) = self.data.table_id {
427 lock_key.push(TableLock::Write(table_id).into());
428 }
429 lock_key.push(TableNameLock::new(catalog, schema, &self.data.object_name).into());
430 lock_key
431 }
432 CommentObjectType::Flow => {
433 let mut lock_key = vec![CatalogLock::Read(catalog).into()];
434 if let Some(flow_id) = self.data.flow_id {
435 lock_key.push(FlowLock::Write(flow_id).into());
436 }
437 lock_key.push(FlowNameLock::new(catalog, &self.data.object_name).into());
438 lock_key
439 }
440 };
441
442 LockKey::new(lock_key)
443 }
444}
445
446#[derive(Debug, Serialize, Deserialize, AsRefStr)]
447enum CommentOnState {
448 Prepare,
449 UpdateMetadata,
450 InvalidateCache,
451}
452
453#[derive(Debug, Serialize, Deserialize)]
455pub struct CommentOnData {
456 state: CommentOnState,
457 catalog_name: String,
458 schema_name: String,
459 object_type: CommentObjectType,
460 object_name: String,
461 column_name: Option<String>,
463 comment: Option<String>,
464 #[serde(skip_serializing_if = "Option::is_none")]
466 table_id: Option<TableId>,
467 #[serde(skip)]
469 table_info: Option<DeserializedValueWithBytes<TableInfoValue>>,
470 #[serde(skip_serializing_if = "Option::is_none")]
472 flow_id: Option<FlowId>,
473 #[serde(skip)]
475 flow_info: Option<DeserializedValueWithBytes<FlowInfoValue>>,
476 #[serde(skip)]
478 is_unchanged: bool,
479}
480
481impl CommentOnData {
482 pub fn new(task: CommentOnTask) -> Self {
483 let (table_id, flow_id) = match task.object_id {
484 Some(CommentObjectId::Table(table_id)) => (Some(table_id), None),
485 Some(CommentObjectId::Flow(flow_id)) => (None, Some(flow_id)),
486 None => (None, None),
487 };
488
489 Self {
490 state: CommentOnState::Prepare,
491 catalog_name: task.catalog_name,
492 schema_name: task.schema_name,
493 object_type: task.object_type,
494 object_name: task.object_name,
495 column_name: task.column_name,
496 comment: task.comment,
497 table_id,
498 table_info: None,
499 flow_id,
500 flow_info: None,
501 is_unchanged: false,
502 }
503 }
504}
505
506fn update_column_comment_metadata(
507 column_schema: &mut datatypes::schema::ColumnSchema,
508 comment: Option<String>,
509) {
510 match comment {
511 Some(value) => {
512 column_schema
513 .mut_metadata()
514 .insert(COLUMN_COMMENT_KEY.to_string(), value);
515 }
516 None => {
517 column_schema.mut_metadata().remove(COLUMN_COMMENT_KEY);
518 }
519 }
520}
521
522fn sync_table_comment_option(options: &mut table::requests::TableOptions, comment: Option<&str>) {
523 match comment {
524 Some(value) => {
525 options
526 .extra_options
527 .insert(TABLE_COMMENT_KEY.to_string(), value.to_string());
528 }
529 None => {
530 options.extra_options.remove(TABLE_COMMENT_KEY);
531 }
532 }
533}