risingwave_frontend/handler/
alter_table_drop_connector.rs1use std::collections::HashSet;
16use std::sync::{Arc, LazyLock};
17
18use risingwave_common::bail_not_implemented;
19use risingwave_connector::parser::additional_columns::gen_default_addition_col_name;
20use risingwave_connector::sink::decouple_checkpoint_log_sink::COMMIT_CHECKPOINT_INTERVAL;
21use risingwave_pb::ddl_service::TableJobType;
22use risingwave_sqlparser::ast::{ColumnDef, Ident};
23
24use crate::catalog::root_catalog::SchemaPath;
25use crate::catalog::source_catalog::SourceCatalog;
26use crate::error::{ErrorCode, Result};
27use crate::handler::create_source::SqlColumnStrategy;
28use crate::handler::{
29 HandlerArgs, ObjectName, PgResponse, RwPgResponse, Statement, StatementType,
30 get_replace_table_plan,
31};
32use crate::session::SessionImpl;
33use crate::utils::data_type::DataTypeToAst;
34use crate::utils::options::RETENTION_SECONDS;
35use crate::{Binder, TableCatalog, bind_data_type};
36
37static TABLE_PROPS: LazyLock<HashSet<&str>> =
39 LazyLock::new(|| HashSet::from([COMMIT_CHECKPOINT_INTERVAL, RETENTION_SECONDS]));
40
41fn fetch_schema_info(
42 session: &Arc<SessionImpl>,
43 table_name: ObjectName,
44) -> Result<(Arc<TableCatalog>, Arc<SourceCatalog>)> {
45 let db_name = session.database();
46 let (schema_name, real_table_name) =
47 Binder::resolve_schema_qualified_name(db_name.as_str(), &table_name)?;
48 let search_path = session.config().search_path();
49 let user_name = &session.auth_context().user_name;
50
51 let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
52 let reader = session.env().catalog_reader().read_guard();
53
54 let (table_def, schema_name) =
55 reader.get_any_table_by_name(db_name.as_str(), schema_path, &real_table_name)?;
56 session.check_privilege_for_drop_alter(schema_name, &**table_def)?;
57
58 let Some(source_id) = table_def.associated_source_id else {
59 return Err(ErrorCode::ProtocolError(format!(
60 "Table {} is not associated with a connector",
61 real_table_name
62 ))
63 .into());
64 };
65 let (source_def, _) = reader.get_source_by_id(db_name.as_str(), schema_path, source_id)?;
66 Ok((table_def.clone(), source_def.clone()))
67}
68
69fn rewrite_table_definition(
70 original_table_def: &Arc<TableCatalog>,
71 original_source_def: &Arc<SourceCatalog>,
72 original_statement: Statement,
73) -> Result<Statement> {
74 let Statement::CreateTable {
75 mut columns,
76 include_column_options,
77 or_replace,
78 temporary,
79 if_not_exists,
80 name,
81 wildcard_idx,
82 constraints,
83 mut with_options,
84 source_watermarks,
85 append_only,
86 on_conflict,
87 with_version_columns,
88 query,
89 engine,
90 ..
91 } = original_statement
92 else {
93 panic!("unexpected statement: {:?}", original_statement);
94 };
95
96 for item in include_column_options.iter().rev() {
98 let col_name = if let Some(col_alias) = &item.column_alias {
99 col_alias.real_value()
100 } else {
101 let data_type = if let Some(dt) = &item.header_inner_expect_type {
102 Some(bind_data_type(dt)?)
103 } else {
104 None
105 };
106 gen_default_addition_col_name(
107 original_source_def.connector_name().as_str(),
108 item.column_type.real_value().as_str(),
109 item.inner_field.as_deref(),
110 data_type.as_ref(),
111 )
112 };
113 if let Some(col_def) = original_table_def
115 .columns
116 .iter()
117 .find(|col_def| col_def.name() == col_name)
118 {
119 columns.push(ColumnDef {
120 name: Ident::from(col_name.as_str()),
121 data_type: Some(col_def.data_type().to_ast()),
122 collation: None,
123 options: vec![],
124 });
125 }
126 }
127
128 let new_statement = Statement::CreateTable {
129 or_replace,
130 temporary,
131 if_not_exists,
132 name,
133 columns: columns.clone(),
134 wildcard_idx,
135 constraints,
136 with_options: {
137 with_options.retain(|item| {
138 TABLE_PROPS.contains(item.name.real_value().to_lowercase().as_str())
139 });
140 with_options
141 },
142 format_encode: None,
143 source_watermarks,
146 append_only,
147 on_conflict,
148 with_version_columns,
149 query,
150 cdc_table_info: None,
151 include_column_options: vec![],
152 webhook_info: None,
153 engine,
154 };
155 Ok(new_statement)
156}
157
158pub async fn handle_alter_table_drop_connector(
159 handler_args: HandlerArgs,
160 table_name: ObjectName,
161) -> Result<RwPgResponse> {
162 let session = handler_args.session;
163 let (table_def, source_def) = fetch_schema_info(&session, table_name.clone())?;
164 if table_def.refreshable {
165 bail_not_implemented!("DROP CONNECTOR on a refreshable table");
166 }
167 let original_definition = table_def.create_sql_ast_purified()?;
168
169 let new_statement = rewrite_table_definition(&table_def, &source_def, original_definition)?;
170 let (_, table, graph, _) = Box::pin(get_replace_table_plan(
171 &session,
172 table_name,
173 new_statement,
174 &table_def,
175 SqlColumnStrategy::FollowUnchecked,
176 ))
177 .await?;
178
179 let catalog_writer = session.catalog_writer()?;
180 catalog_writer
181 .replace_table(None, table.to_prost(), graph, TableJobType::General as _)
182 .await?;
183
184 Ok(PgResponse::empty_result(StatementType::ALTER_TABLE))
185}