Skip to main content

risingwave_frontend/handler/
alter_rename.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use pgwire::pg_response::{PgResponse, StatementType};
16use risingwave_common::acl::AclMode;
17use risingwave_common::catalog::is_system_schema;
18use risingwave_sqlparser::ast::ObjectName;
19
20use super::{HandlerArgs, RwPgResponse};
21use crate::Binder;
22use crate::catalog::OwnedByUserCatalog;
23use crate::catalog::root_catalog::SchemaPath;
24use crate::catalog::table_catalog::TableType;
25use crate::error::{ErrorCode, Result};
26
27pub async fn handle_rename_table(
28    handler_args: HandlerArgs,
29    table_type: TableType,
30    table_name: ObjectName,
31    new_table_name: ObjectName,
32) -> Result<RwPgResponse> {
33    let session = handler_args.session;
34    let db_name = &session.database();
35    let (schema_name, real_table_name) =
36        Binder::resolve_schema_qualified_name(db_name, &table_name)?;
37    let new_table_name = Binder::resolve_table_name(new_table_name)?;
38    let search_path = session.config().search_path();
39    let user_name = &session.user_name();
40
41    let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
42
43    let table_id = {
44        let reader = session.env().catalog_reader().read_guard();
45        let (table, schema_name) =
46            reader.get_created_table_by_name(db_name, schema_path, &real_table_name)?;
47        if table_type != table.table_type {
48            return Err(ErrorCode::InvalidInputSyntax(format!(
49                "\"{table_name}\" is not a {}",
50                table_type.to_prost().as_str_name()
51            ))
52            .into());
53        }
54
55        session.check_privilege_for_drop_alter(schema_name, &**table)?;
56        table.id
57    };
58
59    let catalog_writer = session.catalog_writer()?;
60    catalog_writer
61        .alter_name(table_id.into(), &new_table_name)
62        .await?;
63
64    let stmt_type = match table_type {
65        TableType::Table => StatementType::ALTER_TABLE,
66        TableType::MaterializedView => StatementType::ALTER_MATERIALIZED_VIEW,
67        _ => unreachable!(),
68    };
69    Ok(PgResponse::empty_result(stmt_type))
70}
71
72pub async fn handle_rename_index(
73    handler_args: HandlerArgs,
74    index_name: ObjectName,
75    new_index_name: ObjectName,
76) -> Result<RwPgResponse> {
77    let session = handler_args.session;
78    let db_name = &session.database();
79    let (schema_name, real_index_name) =
80        Binder::resolve_schema_qualified_name(db_name, &index_name)?;
81    let new_index_name = Binder::resolve_index_name(new_index_name)?;
82    let search_path = session.config().search_path();
83    let user_name = &session.user_name();
84
85    let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
86
87    let index_id = {
88        let reader = session.env().catalog_reader().read_guard();
89        let (index, schema_name) =
90            reader.get_index_by_name(db_name, schema_path, &real_index_name)?;
91        session.check_privilege_for_drop_alter(schema_name, &**index)?;
92        index.id
93    };
94
95    let catalog_writer = session.catalog_writer()?;
96    catalog_writer
97        .alter_name(index_id.into(), &new_index_name)
98        .await?;
99
100    Ok(PgResponse::empty_result(StatementType::ALTER_INDEX))
101}
102
103pub async fn handle_rename_view(
104    handler_args: HandlerArgs,
105    view_name: ObjectName,
106    new_view_name: ObjectName,
107) -> Result<RwPgResponse> {
108    let session = handler_args.session;
109    let db_name = &session.database();
110    let (schema_name, real_view_name) = Binder::resolve_schema_qualified_name(db_name, &view_name)?;
111    let new_view_name = Binder::resolve_view_name(new_view_name)?;
112    let search_path = session.config().search_path();
113    let user_name = &session.user_name();
114
115    let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
116
117    let view_id = {
118        let reader = session.env().catalog_reader().read_guard();
119        let (view, schema_name) = reader.get_view_by_name(db_name, schema_path, &real_view_name)?;
120        session.check_privilege_for_drop_alter(schema_name, &**view)?;
121        view.id
122    };
123
124    let catalog_writer = session.catalog_writer()?;
125    catalog_writer
126        .alter_name(view_id.into(), &new_view_name)
127        .await?;
128
129    Ok(PgResponse::empty_result(StatementType::ALTER_VIEW))
130}
131
132pub async fn handle_rename_sink(
133    handler_args: HandlerArgs,
134    sink_name: ObjectName,
135    new_sink_name: ObjectName,
136) -> Result<RwPgResponse> {
137    let session = handler_args.session;
138    let db_name = &session.database();
139    let (schema_name, real_sink_name) = Binder::resolve_schema_qualified_name(db_name, &sink_name)?;
140    let new_sink_name = Binder::resolve_sink_name(new_sink_name)?;
141    let search_path = session.config().search_path();
142    let user_name = &session.user_name();
143
144    let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
145
146    let sink_id = {
147        let reader = session.env().catalog_reader().read_guard();
148        let (sink, schema_name) =
149            reader.get_created_sink_by_name(db_name, schema_path, &real_sink_name)?;
150        session.check_privilege_for_drop_alter(schema_name, &**sink)?;
151        sink.id
152    };
153
154    let catalog_writer = session.catalog_writer()?;
155    catalog_writer
156        .alter_name(sink_id.into(), &new_sink_name)
157        .await?;
158
159    Ok(PgResponse::empty_result(StatementType::ALTER_SINK))
160}
161
162pub async fn handle_rename_subscription(
163    handler_args: HandlerArgs,
164    subscription_name: ObjectName,
165    new_subscription_name: ObjectName,
166) -> Result<RwPgResponse> {
167    let session = handler_args.session;
168    let db_name = &session.database();
169    let (schema_name, real_subscription_name) =
170        Binder::resolve_schema_qualified_name(db_name, &subscription_name)?;
171    let new_subscription_name = Binder::resolve_subscription_name(new_subscription_name)?;
172    let search_path = session.config().search_path();
173    let user_name = &session.user_name();
174
175    let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
176
177    let subscription_id = {
178        let reader = session.env().catalog_reader().read_guard();
179        let (subscription, schema_name) =
180            reader.get_subscription_by_name(db_name, schema_path, &real_subscription_name)?;
181        session.check_privilege_for_drop_alter(schema_name, &**subscription)?;
182        subscription.id
183    };
184
185    let catalog_writer = session.catalog_writer()?;
186    catalog_writer
187        .alter_name(subscription_id.into(), &new_subscription_name)
188        .await?;
189
190    Ok(PgResponse::empty_result(StatementType::ALTER_SUBSCRIPTION))
191}
192
193pub async fn handle_rename_source(
194    handler_args: HandlerArgs,
195    source_name: ObjectName,
196    new_source_name: ObjectName,
197) -> Result<RwPgResponse> {
198    let session = handler_args.session;
199    let db_name = &session.database();
200    let (schema_name, real_source_name) =
201        Binder::resolve_schema_qualified_name(db_name, &source_name)?;
202    let new_source_name = Binder::resolve_source_name(new_source_name)?;
203    let search_path = session.config().search_path();
204    let user_name = &session.user_name();
205
206    let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name);
207
208    let source_id = {
209        let reader = session.env().catalog_reader().read_guard();
210        let (source, schema_name) =
211            reader.get_source_by_name(db_name, schema_path, &real_source_name)?;
212
213        // For `CREATE TABLE WITH (connector = '...')`, users should call `ALTER TABLE` instead.
214        if source.associated_table_id.is_some() {
215            return Err(ErrorCode::InvalidInputSyntax(
216                "Use `ALTER TABLE` to alter a table with connector.".to_owned(),
217            )
218            .into());
219        }
220
221        session.check_privilege_for_drop_alter(schema_name, &**source)?;
222        source.id
223    };
224
225    let catalog_writer = session.catalog_writer()?;
226    catalog_writer
227        .alter_name(source_id.into(), &new_source_name)
228        .await?;
229
230    Ok(PgResponse::empty_result(StatementType::ALTER_SOURCE))
231}
232
233pub async fn handle_rename_schema(
234    handler_args: HandlerArgs,
235    schema_name: ObjectName,
236    new_schema_name: ObjectName,
237) -> Result<RwPgResponse> {
238    let session = handler_args.session;
239    let db_name = &session.database();
240    let schema_name = Binder::resolve_schema_name(schema_name)?;
241    let new_schema_name = Binder::resolve_schema_name(new_schema_name)?;
242
243    let schema_id = {
244        let catalog_reader = session.env().catalog_reader().read_guard();
245        let user_reader = session.env().user_info_reader().read_guard();
246        let schema = catalog_reader.get_schema_by_name(db_name, &schema_name)?;
247        let db_id = catalog_reader.get_database_by_name(db_name)?.id();
248
249        // The current one should not be system schema.
250        if is_system_schema(&schema.name()) {
251            return Err(ErrorCode::ProtocolError(format!(
252                "permission denied to rename on \"{}\", System catalog modifications are currently disallowed.",
253                schema_name
254            )).into());
255        }
256
257        // The user should be super user or owner to alter the schema.
258        session.check_privilege_for_drop_alter_db_schema(schema)?;
259
260        // To rename a schema you must also have the CREATE privilege for the database.
261        if let Some(user) = user_reader.get_user_by_name(&session.user_name()) {
262            if !user.is_super && !user.has_privilege(db_id, AclMode::Create) {
263                return Err(ErrorCode::PermissionDenied(
264                    "Do not have CREATE privilege on the current database".to_owned(),
265                )
266                .into());
267            }
268        } else {
269            return Err(ErrorCode::PermissionDenied("Session user is invalid".to_owned()).into());
270        }
271
272        schema.id()
273    };
274
275    let catalog_writer = session.catalog_writer()?;
276    catalog_writer
277        .alter_name(schema_id.into(), &new_schema_name)
278        .await?;
279
280    Ok(PgResponse::empty_result(StatementType::ALTER_SCHEMA))
281}
282
283pub async fn handle_rename_database(
284    handler_args: HandlerArgs,
285    database_name: ObjectName,
286    new_database_name: ObjectName,
287) -> Result<RwPgResponse> {
288    let session = handler_args.session;
289    let database_name = Binder::resolve_database_name(database_name)?;
290    let new_database_name = Binder::resolve_database_name(new_database_name)?;
291
292    let database_id = {
293        let catalog_reader = session.env().catalog_reader().read_guard();
294        let user_reader = session.env().user_info_reader().read_guard();
295        let database = catalog_reader.get_database_by_name(&database_name)?;
296        let current_user = user_reader
297            .get_user_by_name(&session.user_name())
298            .ok_or_else(|| ErrorCode::PermissionDenied("Session user is invalid".to_owned()))?;
299
300        // If the database owner is an admin, only admin users can rename it.
301        if let Some(database_owner) = user_reader.get_user_by_id(&database.owner())
302            && database_owner.is_admin
303            && !current_user.is_admin
304        {
305            return Err(ErrorCode::PermissionDenied(
306                "only admin users can rename databases owned by admin users".to_owned(),
307            )
308            .into());
309        }
310
311        // The user should be super user or owner to alter the database.
312        session.check_privilege_for_drop_alter_db_schema(database)?;
313
314        // Non-superuser owners must also have the CREATEDB privilege.
315        if !current_user.is_super && !current_user.can_create_db {
316            return Err(ErrorCode::PermissionDenied(
317                "Non-superuser owners must also have the CREATEDB privilege".to_owned(),
318            )
319            .into());
320        }
321
322        // The current database cannot be renamed.
323        if database_name == session.database() {
324            return Err(ErrorCode::PermissionDenied(
325                "Current database cannot be renamed".to_owned(),
326            )
327            .into());
328        }
329
330        database.id()
331    };
332
333    let catalog_writer = session.catalog_writer()?;
334    catalog_writer
335        .alter_name(database_id.into(), &new_database_name)
336        .await?;
337
338    Ok(PgResponse::empty_result(StatementType::ALTER_DATABASE))
339}
340
341#[cfg(test)]
342mod tests {
343    use risingwave_common::catalog::{
344        DEFAULT_DATABASE_NAME, DEFAULT_SCHEMA_NAME, DEFAULT_SUPER_USER_FOR_ADMIN,
345        DEFAULT_SUPER_USER_FOR_ADMIN_ID,
346    };
347
348    use crate::catalog::root_catalog::SchemaPath;
349    use crate::test_utils::LocalFrontend;
350
351    #[tokio::test]
352    async fn test_alter_table_name_handler() {
353        let frontend = LocalFrontend::new(Default::default()).await;
354        let session = frontend.session_ref();
355        let schema_path = SchemaPath::Name(DEFAULT_SCHEMA_NAME);
356
357        let sql = "create table t (i int, r real);";
358        frontend.run_sql(sql).await.unwrap();
359
360        let table_id = {
361            let catalog_reader = session.env().catalog_reader().read_guard();
362            catalog_reader
363                .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t")
364                .unwrap()
365                .0
366                .id
367        };
368
369        // Alter table name.
370        let sql = "alter table t rename to t1;";
371        frontend.run_sql(sql).await.unwrap();
372
373        let catalog_reader = session.env().catalog_reader().read_guard();
374        let altered_table_name = catalog_reader
375            .get_any_table_by_id(table_id)
376            .unwrap()
377            .name()
378            .to_owned();
379        assert_eq!(altered_table_name, "t1");
380    }
381
382    #[tokio::test]
383    async fn test_rename_admin_owned_database() {
384        let frontend = LocalFrontend::new(Default::default()).await;
385        let session = frontend.session_ref();
386        let catalog_reader = session.env().catalog_reader();
387
388        frontend
389            .run_user_sql(
390                "CREATE DATABASE admin_owned_database",
391                DEFAULT_DATABASE_NAME.to_owned(),
392                DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
393                DEFAULT_SUPER_USER_FOR_ADMIN_ID,
394            )
395            .await
396            .unwrap();
397
398        let err = frontend
399            .run_sql("ALTER DATABASE admin_owned_database RENAME TO renamed_database")
400            .await
401            .unwrap_err();
402        assert!(
403            err.to_string()
404                .contains("only admin users can rename databases owned by admin users")
405        );
406        assert!(
407            catalog_reader
408                .read_guard()
409                .get_database_by_name("admin_owned_database")
410                .is_ok()
411        );
412
413        frontend
414            .run_user_sql(
415                "ALTER DATABASE admin_owned_database RENAME TO renamed_database",
416                DEFAULT_DATABASE_NAME.to_owned(),
417                DEFAULT_SUPER_USER_FOR_ADMIN.to_owned(),
418                DEFAULT_SUPER_USER_FOR_ADMIN_ID,
419            )
420            .await
421            .unwrap();
422        assert!(
423            catalog_reader
424                .read_guard()
425                .get_database_by_name("renamed_database")
426                .is_ok()
427        );
428
429        frontend
430            .run_sql("CREATE DATABASE regular_database")
431            .await
432            .unwrap();
433        frontend
434            .run_sql("ALTER DATABASE regular_database RENAME TO renamed_regular_database")
435            .await
436            .unwrap();
437        assert!(
438            catalog_reader
439                .read_guard()
440                .get_database_by_name("renamed_regular_database")
441                .is_ok()
442        );
443    }
444}