feat: table/column/flow COMMENT (#7060)

* initial impl

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* simplify impl

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* sqlness test

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* avoid unimplemented panic

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* validate flow

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* update sqlness result

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* fix table column comment

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* table level comment

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* simplify table info serde

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* don't txn

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* remove empty trait

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* wip: procedure

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* update proto

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* grpc support

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* Apply suggestions from code review

Co-authored-by: dennis zhuang <killme2008@gmail.com>
Co-authored-by: LFC <990479+MichaelScofield@users.noreply.github.com>
Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* try from pb struct

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* doc comment

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* check unchanged fast case

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* tune errors

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* fix merge error

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* use try_as_raw_value

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

---------

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>
Co-authored-by: dennis zhuang <killme2008@gmail.com>
Co-authored-by: LFC <990479+MichaelScofield@users.noreply.github.com>
This commit is contained in:
Ruihang Xia
2025-12-10 23:08:47 +08:00
committed by GitHub
parent f1abe5d215
commit 564cc0c750
33 changed files with 1840 additions and 316 deletions

View File

@@ -13,6 +13,7 @@
// limitations under the License.
mod admin;
mod comment;
mod copy_database;
mod copy_query_to;
mod copy_table_from;
@@ -428,6 +429,7 @@ impl StatementExecutor {
Statement::ShowCreateTrigger(show) => self.show_create_trigger(show, query_ctx).await,
Statement::SetVariables(set_var) => self.set_variables(set_var, query_ctx),
Statement::ShowVariables(show_variable) => self.show_variable(show_variable, query_ctx),
Statement::Comment(stmt) => self.comment(stmt, query_ctx).await,
Statement::ShowColumns(show_columns) => {
self.show_columns(show_columns, query_ctx).await
}

View File

@@ -0,0 +1,176 @@
// Copyright 2023 Greptime Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use api::v1::CommentOnExpr;
use common_error::ext::BoxedError;
use common_meta::procedure_executor::ExecutorContext;
use common_meta::rpc::ddl::{CommentObjectType, CommentOnTask, DdlTask, SubmitDdlTaskRequest};
use common_query::Output;
use session::context::QueryContextRef;
use session::table_name::table_idents_to_full_name;
use snafu::ResultExt;
use sql::ast::ObjectNamePartExt;
use sql::statements::comment::{Comment, CommentObject};
use crate::error::{ExecuteDdlSnafu, ExternalSnafu, InvalidSqlSnafu, Result};
use crate::statement::StatementExecutor;
impl StatementExecutor {
/// Adds a comment to a database object (table, column, or flow).
///
/// # Arguments
///
/// * `stmt`: A `Comment` struct containing the object to comment on and the comment text.
/// * `query_ctx`: A `QueryContextRef` providing contextual information for the query.
///
/// # Returns
///
/// A `Result` containing the `Output` of the operation, or an error if the operation fails.
pub async fn comment(&self, stmt: Comment, query_ctx: QueryContextRef) -> Result<Output> {
let comment_on_task = self.create_comment_on_task_from_stmt(stmt, &query_ctx)?;
let request = SubmitDdlTaskRequest {
task: DdlTask::new_comment_on(comment_on_task),
query_context: query_ctx,
};
self.procedure_executor
.submit_ddl_task(&ExecutorContext::default(), request)
.await
.context(ExecuteDdlSnafu)
.map(|_| Output::new_with_affected_rows(0))
}
pub async fn comment_by_expr(
&self,
expr: CommentOnExpr,
query_ctx: QueryContextRef,
) -> Result<Output> {
let comment_on_task = self.create_comment_on_task_from_expr(expr)?;
let request = SubmitDdlTaskRequest {
task: DdlTask::new_comment_on(comment_on_task),
query_context: query_ctx,
};
self.procedure_executor
.submit_ddl_task(&ExecutorContext::default(), request)
.await
.context(ExecuteDdlSnafu)
.map(|_| Output::new_with_affected_rows(0))
}
fn create_comment_on_task_from_expr(&self, expr: CommentOnExpr) -> Result<CommentOnTask> {
let object_type = match expr.object_type {
0 => CommentObjectType::Table,
1 => CommentObjectType::Column,
2 => CommentObjectType::Flow,
_ => {
return InvalidSqlSnafu {
err_msg: format!(
"Invalid CommentObjectType value: {}. Valid values are: 0 (Table), 1 (Column), 2 (Flow)",
expr.object_type
),
}
.fail();
}
};
Ok(CommentOnTask {
catalog_name: expr.catalog_name,
schema_name: expr.schema_name,
object_type,
object_name: expr.object_name,
column_name: if expr.column_name.is_empty() {
None
} else {
Some(expr.column_name)
},
object_id: None,
comment: if expr.comment.is_empty() {
None
} else {
Some(expr.comment)
},
})
}
fn create_comment_on_task_from_stmt(
&self,
stmt: Comment,
query_ctx: &QueryContextRef,
) -> Result<CommentOnTask> {
match stmt.object {
CommentObject::Table(table) => {
let (catalog_name, schema_name, table_name) =
table_idents_to_full_name(&table, query_ctx)
.map_err(BoxedError::new)
.context(ExternalSnafu)?;
Ok(CommentOnTask {
catalog_name,
schema_name,
object_type: CommentObjectType::Table,
object_name: table_name,
column_name: None,
object_id: None,
comment: stmt.comment,
})
}
CommentObject::Column { table, column } => {
let (catalog_name, schema_name, table_name) =
table_idents_to_full_name(&table, query_ctx)
.map_err(BoxedError::new)
.context(ExternalSnafu)?;
Ok(CommentOnTask {
catalog_name,
schema_name,
object_type: CommentObjectType::Column,
object_name: table_name,
column_name: Some(column.value),
object_id: None,
comment: stmt.comment,
})
}
CommentObject::Flow(flow_name) => {
let (catalog_name, flow_name_str) = match &flow_name.0[..] {
[flow] => (
query_ctx.current_catalog().to_string(),
flow.to_string_unquoted(),
),
[catalog, flow] => (catalog.to_string_unquoted(), flow.to_string_unquoted()),
_ => {
return InvalidSqlSnafu {
err_msg: format!(
"expect flow name to be <catalog>.<flow_name> or <flow_name>, actual: {flow_name}"
),
}
.fail();
}
};
Ok(CommentOnTask {
catalog_name,
schema_name: String::new(), // Flow doesn't use schema
object_type: CommentObjectType::Flow,
object_name: flow_name_str,
column_name: None,
object_id: None,
comment: stmt.comment,
})
}
}
}
}

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use common_error::ext::BoxedError;
use common_meta::key::schema_name::SchemaNameKey;
use common_query::Output;
@@ -120,7 +122,30 @@ impl StatementExecutor {
table: TableRef,
query_ctx: QueryContextRef,
) -> Result<Output> {
let table_info = table.table_info();
let mut table_info = table.table_info();
let partition_column_names: Vec<_> =
table_info.meta.partition_column_names().cloned().collect();
if let Some(latest) = self
.table_metadata_manager
.table_info_manager()
.get(table_info.table_id())
.await
.context(TableMetadataManagerSnafu)?
{
let mut latest_info = TableInfo::try_from(latest.into_inner().table_info)
.context(error::CreateTableInfoSnafu)?;
if !partition_column_names.is_empty() {
latest_info.meta.partition_key_indices = partition_column_names
.iter()
.filter_map(|name| latest_info.meta.schema.column_index_by_name(name.as_str()))
.collect();
}
table_info = Arc::new(latest_info);
}
if table_info.table_type != TableType::Base {
return error::ShowCreateTableBaseOnlySnafu {
table_name: table_name.to_string(),
@@ -150,7 +175,7 @@ impl StatementExecutor {
let partitions = create_partitions_stmt(&table_info, partitions)?;
query::sql::show_create_table(table, schema_options, partitions, query_ctx)
query::sql::show_create_table(table_info, schema_options, partitions, query_ctx)
.context(ExecuteStatementSnafu)
}