risingwave_frontend/binder/
delete.rs

1// Copyright 2025 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 risingwave_common::acl::AclMode;
16use risingwave_common::catalog::{Schema, TableVersionId};
17use risingwave_pb::user::grant_privilege::PbObject;
18use risingwave_sqlparser::ast::{Expr, ObjectName, SelectItem};
19
20use super::statement::RewriteExprsRecursive;
21use super::{Binder, BoundBaseTable};
22use crate::catalog::TableId;
23use crate::error::{ErrorCode, Result, RwError};
24use crate::expr::ExprImpl;
25use crate::handler::privilege::ObjectCheckItem;
26use crate::user::UserId;
27
28#[derive(Debug, Clone)]
29pub struct BoundDelete {
30    /// Id of the table to perform deleting.
31    pub table_id: TableId,
32
33    /// Version id of the table.
34    pub table_version_id: TableVersionId,
35
36    /// Name of the table to perform deleting.
37    pub table_name: String,
38
39    /// Owner of the table to perform deleting.
40    pub owner: UserId,
41
42    /// Used for scanning the records to delete with the `selection`.
43    pub table: BoundBaseTable,
44
45    pub selection: Option<ExprImpl>,
46
47    /// used for the 'RETURNING" keyword to indicate the returning items and schema
48    /// if the list is empty and the schema is None, the output schema will be a INT64 as the
49    /// affected row cnt
50    pub returning_list: Vec<ExprImpl>,
51
52    pub returning_schema: Option<Schema>,
53}
54
55impl RewriteExprsRecursive for BoundDelete {
56    fn rewrite_exprs_recursive(&mut self, rewriter: &mut impl crate::expr::ExprRewriter) {
57        self.selection =
58            std::mem::take(&mut self.selection).map(|expr| rewriter.rewrite_expr(expr));
59
60        let new_returning_list = std::mem::take(&mut self.returning_list)
61            .into_iter()
62            .map(|expr| rewriter.rewrite_expr(expr))
63            .collect::<Vec<_>>();
64        self.returning_list = new_returning_list;
65    }
66}
67
68impl Binder {
69    pub(super) fn bind_delete(
70        &mut self,
71        name: ObjectName,
72        selection: Option<Expr>,
73        returning_items: Vec<SelectItem>,
74    ) -> Result<BoundDelete> {
75        let (schema_name, table_name) = Self::resolve_schema_qualified_name(&self.db_name, name)?;
76        let schema_name = schema_name.as_deref();
77        let table = self.bind_table(schema_name, &table_name)?;
78
79        let table_catalog = &table.table_catalog;
80        Self::check_for_dml(table_catalog, false)?;
81        self.check_privilege(
82            ObjectCheckItem::new(
83                table_catalog.owner,
84                AclMode::Delete,
85                table_name.clone(),
86                PbObject::TableId(table_catalog.id.table_id),
87            ),
88            table_catalog.database_id,
89        )?;
90
91        if !returning_items.is_empty() && table_catalog.has_generated_column() {
92            return Err(RwError::from(ErrorCode::BindError(
93                "`RETURNING` clause is not supported for tables with generated columns".to_owned(),
94            )));
95        }
96        let table_id = table_catalog.id;
97        let owner = table_catalog.owner;
98        let table_version_id = table_catalog.version_id().expect("table must be versioned");
99
100        let (returning_list, fields) = self.bind_returning_list(returning_items)?;
101        let returning = !returning_list.is_empty();
102        let delete = BoundDelete {
103            table_id,
104            table_version_id,
105            table_name,
106            owner,
107            table,
108            selection: selection
109                .map(|expr| self.bind_expr(expr)?.enforce_bool_clause("WHERE"))
110                .transpose()?,
111            returning_list,
112            returning_schema: if returning {
113                Some(Schema { fields })
114            } else {
115                None
116            },
117        };
118        Ok(delete)
119    }
120}