diff --git a/src/frontend/src/instance.rs b/src/frontend/src/instance.rs index 07b6d414f9..87d069ef1d 100644 --- a/src/frontend/src/instance.rs +++ b/src/frontend/src/instance.rs @@ -645,6 +645,9 @@ pub fn check_permission( Statement::Copy(sql::statements::copy::Copy::CopyDatabase(stmt)) => { validate_param(&stmt.database_name, query_ctx)? } + Statement::TruncateTable(stmt) => { + validate_param(stmt.table_name(), query_ctx)?; + } } Ok(()) } diff --git a/src/frontend/src/statement.rs b/src/frontend/src/statement.rs index ba8246b706..6eefad4b21 100644 --- a/src/frontend/src/statement.rs +++ b/src/frontend/src/statement.rs @@ -130,6 +130,7 @@ impl StatementExecutor { | Statement::CreateExternalTable(_) | Statement::Alter(_) | Statement::DropTable(_) + | Statement::TruncateTable(_) | Statement::ShowCreateTable(_) => self .sql_stmt_executor .execute_sql(stmt, query_ctx) diff --git a/src/sql/src/parser.rs b/src/sql/src/parser.rs index 4da3328f8b..f403877185 100644 --- a/src/sql/src/parser.rs +++ b/src/sql/src/parser.rs @@ -127,6 +127,8 @@ impl<'a> ParserContext<'a> { Keyword::COPY => self.parse_copy(), + Keyword::TRUNCATE => self.parse_truncate(), + Keyword::NoKeyword if w.value.to_uppercase() == tql_parser::TQL && w.quote_style.is_none() => { diff --git a/src/sql/src/parsers.rs b/src/sql/src/parsers.rs index d6f128bd62..960d5fe8c7 100644 --- a/src/sql/src/parsers.rs +++ b/src/sql/src/parsers.rs @@ -19,3 +19,4 @@ pub(crate) mod delete_parser; pub(crate) mod insert_parser; pub(crate) mod query_parser; pub(crate) mod tql_parser; +pub(crate) mod truncate_parser; diff --git a/src/sql/src/parsers/truncate_parser.rs b/src/sql/src/parsers/truncate_parser.rs new file mode 100644 index 0000000000..2f6fcd7af4 --- /dev/null +++ b/src/sql/src/parsers/truncate_parser.rs @@ -0,0 +1,139 @@ +// 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 snafu::{ensure, ResultExt}; +use sqlparser::keywords::Keyword; + +use crate::error::{self, InvalidTableNameSnafu, Result}; +use crate::parser::ParserContext; +use crate::statements::statement::Statement; +use crate::statements::truncate::TruncateTable; + +/// TRUNCATE [TABLE] table_name; +impl<'a> ParserContext<'a> { + pub(crate) fn parse_truncate(&mut self) -> Result { + let _ = self.parser.next_token(); + let _ = self.parser.parse_keyword(Keyword::TABLE); + + let table_ident = + self.parser + .parse_object_name() + .with_context(|_| error::UnexpectedSnafu { + sql: self.sql, + expected: "a table name", + actual: self.peek_token_as_string(), + })?; + + ensure!( + !table_ident.0.is_empty(), + InvalidTableNameSnafu { + name: table_ident.to_string() + } + ); + + Ok(Statement::TruncateTable(TruncateTable::new(table_ident))) + } +} + +#[cfg(test)] +mod tests { + use sqlparser::ast::{Ident, ObjectName}; + + use super::*; + use crate::dialect::GreptimeDbDialect; + + #[test] + pub fn test_parse_truncate() { + let sql = "TRUNCATE foo"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![Ident::new("foo")]))) + ); + + let sql = "TRUNCATE TABLE foo"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![Ident::new("foo")]))) + ); + + let sql = "TRUNCATE TABLE my_schema.foo"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![ + Ident::new("my_schema"), + Ident::new("foo") + ]))) + ); + + let sql = "TRUNCATE my_schema.foo"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![ + Ident::new("my_schema"), + Ident::new("foo") + ]))) + ); + + let sql = "TRUNCATE TABLE my_catalog.my_schema.foo"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![ + Ident::new("my_catalog"), + Ident::new("my_schema"), + Ident::new("foo") + ]))) + ); + + let sql = "TRUNCATE drop"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![Ident::new("drop")]))) + ); + + let sql = "TRUNCATE `drop`"; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![Ident::with_quote( + '`', "drop" + ),]))) + ); + + let sql = "TRUNCATE \"drop\""; + let mut stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}).unwrap(); + assert_eq!( + stmts.pop().unwrap(), + Statement::TruncateTable(TruncateTable::new(ObjectName(vec![Ident::with_quote( + '"', "drop" + ),]))) + ); + } + + #[test] + pub fn test_parse_invalid_truncate() { + let sql = "TRUNCATE SCHEMA foo"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + assert!(result.is_err(), "result is: {result:?}"); + + let sql = "TRUNCATE"; + let result = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}); + assert!(result.is_err(), "result is: {result:?}"); + } +} diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index bd88c268ab..5e8e8f60d6 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -24,6 +24,7 @@ pub mod query; pub mod show; pub mod statement; pub mod tql; +pub mod truncate; use std::str::FromStr; diff --git a/src/sql/src/statements/statement.rs b/src/sql/src/statements/statement.rs index 20a608ae5d..4b8d279952 100644 --- a/src/sql/src/statements/statement.rs +++ b/src/sql/src/statements/statement.rs @@ -26,6 +26,7 @@ use crate::statements::insert::Insert; use crate::statements::query::Query; use crate::statements::show::{ShowCreateTable, ShowDatabases, ShowTables}; use crate::statements::tql::Tql; +use crate::statements::truncate::TruncateTable; /// Tokens parsed by `DFParser` are converted into these values. #[allow(clippy::large_enum_variant)] @@ -61,6 +62,8 @@ pub enum Statement { // COPY Copy(crate::statements::copy::Copy), Tql(Tql), + // TRUNCATE TABLE + TruncateTable(TruncateTable), } /// Comment hints from SQL. diff --git a/src/sql/src/statements/truncate.rs b/src/sql/src/statements/truncate.rs new file mode 100644 index 0000000000..e7eae4e185 --- /dev/null +++ b/src/sql/src/statements/truncate.rs @@ -0,0 +1,32 @@ +// 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 sqlparser::ast::ObjectName; + +/// TRUNCATE TABLE statement. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TruncateTable { + table_name: ObjectName, +} + +impl TruncateTable { + /// Creates a statement for `TRUNCATE TABLE` + pub fn new(table_name: ObjectName) -> Self { + Self { table_name } + } + + pub fn table_name(&self) -> &ObjectName { + &self.table_name + } +}