From c4dcf91a03a2446ea2b91ed99c5c3224b88d304f Mon Sep 17 00:00:00 2001 From: blackstar-baba <535650957@qq.com> Date: Wed, 20 Mar 2024 23:01:55 +0800 Subject: [PATCH] feat: add show views & desc view (#14926) * add show views & desc view * replace useless use of `format!` * fix error & add test case * rust fmt * add logic tests * add more logic tests * move desc view test case to package 05_ddl --------- Co-authored-by: TCeason <33082201+TCeason@users.noreply.github.com> --- src/query/ast/src/ast/format/ast_format.rs | 27 +++ src/query/ast/src/ast/statements/statement.rs | 4 + src/query/ast/src/ast/statements/view.rs | 56 +++++++ src/query/ast/src/ast/visitors/visitor.rs | 4 + src/query/ast/src/ast/visitors/visitor_mut.rs | 4 + src/query/ast/src/ast/visitors/walk.rs | 2 + src/query/ast/src/ast/visitors/walk_mut.rs | 2 + src/query/ast/src/parser/statement.rs | 33 ++++ src/query/ast/src/parser/token.rs | 2 + src/query/ast/tests/it/parser.rs | 5 + .../ast/tests/it/testdata/statement-error.txt | 2 +- src/query/ast/tests/it/testdata/statement.txt | 108 ++++++++++++ .../interpreters/access/privilege_access.rs | 3 + .../src/interpreters/interpreter_factory.rs | 5 + .../interpreters/interpreter_view_describe.rs | 132 +++++++++++++++ src/query/service/src/interpreters/mod.rs | 1 + src/query/sql/src/planner/binder/binder.rs | 2 + src/query/sql/src/planner/binder/ddl/view.rs | 126 ++++++++++++++ .../sql/src/planner/format/display_plan.rs | 1 + src/query/sql/src/planner/plans/ddl/view.rs | 15 ++ src/query/sql/src/planner/plans/plan.rs | 3 + .../base/05_ddl/05_0022_ddl_desc_view.test | 156 ++++++++++++++++++ .../base/06_show/06_0023_show_views.test | 107 ++++++++++++ 23 files changed, 799 insertions(+), 1 deletion(-) create mode 100644 src/query/service/src/interpreters/interpreter_view_describe.rs create mode 100644 tests/sqllogictests/suites/base/05_ddl/05_0022_ddl_desc_view.test create mode 100644 tests/sqllogictests/suites/base/06_show/06_0023_show_views.test diff --git a/src/query/ast/src/ast/format/ast_format.rs b/src/query/ast/src/ast/format/ast_format.rs index a142c70d3b358..a6e674dddb236 100644 --- a/src/query/ast/src/ast/format/ast_format.rs +++ b/src/query/ast/src/ast/format/ast_format.rs @@ -1723,6 +1723,33 @@ impl<'ast> Visitor<'ast> for AstFormatVisitor { self.children.push(node); } + fn visit_show_views(&mut self, stmt: &'ast ShowViewsStmt) { + let mut children = Vec::new(); + if let Some(database) = &stmt.database { + let database_name = format!("Database {}", database); + let database_format_ctx = AstFormatContext::new(database_name); + let database_node = FormatTreeNode::new(database_format_ctx); + children.push(database_node); + } + if let Some(limit) = &stmt.limit { + self.visit_show_limit(limit); + children.push(self.children.pop().unwrap()); + } + let name = "ShowViews".to_string(); + let format_ctx = AstFormatContext::with_children(name, children.len()); + let node = FormatTreeNode::with_children(format_ctx, children); + self.children.push(node); + } + + fn visit_describe_view(&mut self, stmt: &'ast DescribeViewStmt) { + self.visit_table_ref(&stmt.catalog, &stmt.database, &stmt.view); + let child = self.children.pop().unwrap(); + let name = "DescribeView".to_string(); + let format_ctx = AstFormatContext::with_children(name, 1); + let node = FormatTreeNode::with_children(format_ctx, vec![child]); + self.children.push(node); + } + fn visit_create_stream(&mut self, stmt: &'ast CreateStreamStmt) { let mut children = Vec::new(); self.visit_table_ref(&stmt.catalog, &stmt.database, &stmt.stream); diff --git a/src/query/ast/src/ast/statements/statement.rs b/src/query/ast/src/ast/statements/statement.rs index f87789e5656ef..b75246753f396 100644 --- a/src/query/ast/src/ast/statements/statement.rs +++ b/src/query/ast/src/ast/statements/statement.rs @@ -154,6 +154,8 @@ pub enum Statement { CreateView(CreateViewStmt), AlterView(AlterViewStmt), DropView(DropViewStmt), + ShowViews(ShowViewsStmt), + DescribeView(DescribeViewStmt), // Streams CreateStream(CreateStreamStmt), @@ -549,6 +551,8 @@ impl Display for Statement { Statement::CreateView(stmt) => write!(f, "{stmt}")?, Statement::AlterView(stmt) => write!(f, "{stmt}")?, Statement::DropView(stmt) => write!(f, "{stmt}")?, + Statement::ShowViews(stmt) => write!(f, "{stmt}")?, + Statement::DescribeView(stmt) => write!(f, "{stmt}")?, Statement::CreateStream(stmt) => write!(f, "{stmt}")?, Statement::DropStream(stmt) => write!(f, "{stmt}")?, Statement::ShowStreams(stmt) => write!(f, "{stmt}")?, diff --git a/src/query/ast/src/ast/statements/view.rs b/src/query/ast/src/ast/statements/view.rs index 4f83b23ab408d..a1ffbcedde977 100644 --- a/src/query/ast/src/ast/statements/view.rs +++ b/src/query/ast/src/ast/statements/view.rs @@ -23,6 +23,7 @@ use crate::ast::write_comma_separated_list; use crate::ast::write_dot_separated_list; use crate::ast::Identifier; use crate::ast::Query; +use crate::ast::ShowLimit; #[derive(Debug, Clone, PartialEq, Drive, DriveMut)] pub struct CreateViewStmt { @@ -113,3 +114,58 @@ impl Display for DropViewStmt { ) } } + +#[derive(Debug, Clone, PartialEq, Drive, DriveMut)] +pub struct ShowViewsStmt { + pub catalog: Option, + pub database: Option, + #[drive(skip)] + pub full: bool, + pub limit: Option, + #[drive(skip)] + pub with_history: bool, +} + +impl Display for ShowViewsStmt { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "SHOW")?; + if self.full { + write!(f, " FULL")?; + } + write!(f, " VIEWS")?; + if self.with_history { + write!(f, " HISTORY")?; + } + if let Some(database) = &self.database { + write!(f, " FROM ")?; + if let Some(catalog) = &self.catalog { + write!(f, "{catalog}.",)?; + } + write!(f, "{database}")?; + } + if let Some(limit) = &self.limit { + write!(f, " {limit}")?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Drive, DriveMut)] +pub struct DescribeViewStmt { + pub catalog: Option, + pub database: Option, + pub view: Identifier, +} + +impl Display for DescribeViewStmt { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "DESCRIBE ")?; + write_dot_separated_list( + f, + self.catalog + .iter() + .chain(self.database.iter().chain(Some(&self.view))), + ) + } +} diff --git a/src/query/ast/src/ast/visitors/visitor.rs b/src/query/ast/src/ast/visitors/visitor.rs index 4f02965dd2ef3..1ba33dda0e644 100644 --- a/src/query/ast/src/ast/visitors/visitor.rs +++ b/src/query/ast/src/ast/visitors/visitor.rs @@ -572,6 +572,10 @@ pub trait Visitor<'ast>: Sized { fn visit_drop_view(&mut self, _stmt: &'ast DropViewStmt) {} + fn visit_show_views(&mut self, _stmt: &'ast ShowViewsStmt) {} + + fn visit_describe_view(&mut self, _stmt: &'ast DescribeViewStmt) {} + fn visit_create_stream(&mut self, _stmt: &'ast CreateStreamStmt) {} fn visit_drop_stream(&mut self, _stmt: &'ast DropStreamStmt) {} diff --git a/src/query/ast/src/ast/visitors/visitor_mut.rs b/src/query/ast/src/ast/visitors/visitor_mut.rs index 37eacf80492c0..f0d2fad07126b 100644 --- a/src/query/ast/src/ast/visitors/visitor_mut.rs +++ b/src/query/ast/src/ast/visitors/visitor_mut.rs @@ -585,6 +585,10 @@ pub trait VisitorMut: Sized { fn visit_drop_view(&mut self, _stmt: &mut DropViewStmt) {} + fn visit_show_views(&mut self, _stmt: &mut ShowViewsStmt) {} + + fn visit_describe_view(&mut self, _stmt: &mut DescribeViewStmt) {} + fn visit_create_stream(&mut self, _stmt: &mut CreateStreamStmt) {} fn visit_drop_stream(&mut self, _stmt: &mut DropStreamStmt) {} diff --git a/src/query/ast/src/ast/visitors/walk.rs b/src/query/ast/src/ast/visitors/walk.rs index 9068cbc6e0de9..e51522db9a1cb 100644 --- a/src/query/ast/src/ast/visitors/walk.rs +++ b/src/query/ast/src/ast/visitors/walk.rs @@ -451,6 +451,8 @@ pub fn walk_statement<'a, V: Visitor<'a>>(visitor: &mut V, statement: &'a Statem Statement::CreateView(stmt) => visitor.visit_create_view(stmt), Statement::AlterView(stmt) => visitor.visit_alter_view(stmt), Statement::DropView(stmt) => visitor.visit_drop_view(stmt), + Statement::ShowViews(stmt) => visitor.visit_show_views(stmt), + Statement::DescribeView(stmt) => visitor.visit_describe_view(stmt), Statement::CreateStream(stmt) => visitor.visit_create_stream(stmt), Statement::DropStream(stmt) => visitor.visit_drop_stream(stmt), Statement::ShowStreams(stmt) => visitor.visit_show_streams(stmt), diff --git a/src/query/ast/src/ast/visitors/walk_mut.rs b/src/query/ast/src/ast/visitors/walk_mut.rs index 8ebce6c566418..f41dddc7902d9 100644 --- a/src/query/ast/src/ast/visitors/walk_mut.rs +++ b/src/query/ast/src/ast/visitors/walk_mut.rs @@ -446,6 +446,8 @@ pub fn walk_statement_mut(visitor: &mut V, statement: &mut Statem Statement::CreateView(stmt) => visitor.visit_create_view(stmt), Statement::AlterView(stmt) => visitor.visit_alter_view(stmt), Statement::DropView(stmt) => visitor.visit_drop_view(stmt), + Statement::ShowViews(stmt) => visitor.visit_show_views(stmt), + Statement::DescribeView(stmt) => visitor.visit_describe_view(stmt), Statement::CreateStream(stmt) => visitor.visit_create_stream(stmt), Statement::DropStream(stmt) => visitor.visit_drop_stream(stmt), Statement::ShowStreams(stmt) => visitor.visit_show_streams(stmt), diff --git a/src/query/ast/src/parser/statement.rs b/src/query/ast/src/parser/statement.rs index d7c38aa94382f..0d85b1187bf09 100644 --- a/src/query/ast/src/parser/statement.rs +++ b/src/query/ast/src/parser/statement.rs @@ -918,6 +918,37 @@ pub fn statement_body(i: Input) -> IResult { }) }, ); + let show_views = map( + rule! { + SHOW ~ FULL? ~ VIEWS ~ HISTORY? ~ ( ( FROM | IN ) ~ #dot_separated_idents_1_to_2 )? ~ #show_limit? + }, + |(_, opt_full, _, opt_history, ctl_db, limit)| { + let (catalog, database) = match ctl_db { + Some((_, (Some(c), d))) => (Some(c), Some(d)), + Some((_, (None, d))) => (None, Some(d)), + _ => (None, None), + }; + Statement::ShowViews(ShowViewsStmt { + catalog, + database, + full: opt_full.is_some(), + limit, + with_history: opt_history.is_some(), + }) + }, + ); + let describe_view = map( + rule! { + ( DESC | DESCRIBE ) ~ VIEW ~ #dot_separated_idents_1_to_3 + }, + |(_, _, (catalog, database, view))| { + Statement::DescribeView(DescribeViewStmt { + catalog, + database, + view, + }) + }, + ); let create_index = map_res( rule! { @@ -2048,6 +2079,7 @@ pub fn statement_body(i: Input) -> IResult { #show_tables : "`SHOW [FULL] TABLES [FROM ] []`" | #show_columns : "`SHOW [FULL] COLUMNS FROM [FROM|IN .] []`" | #show_create_table : "`SHOW CREATE TABLE [.]
`" + | #describe_view : "`DESCRIBE VIEW [.]`" | #describe_table : "`DESCRIBE [.]
`" | #show_fields : "`SHOW FIELDS FROM [.]
`" | #show_tables_status : "`SHOW TABLES STATUS [FROM ] []`" @@ -2071,6 +2103,7 @@ pub fn statement_body(i: Input) -> IResult { #create_view : "`CREATE [OR REPLACE] VIEW [IF NOT EXISTS] [.] [(, ...)] AS SELECT ...`" | #drop_view : "`DROP VIEW [IF EXISTS] [.]`" | #alter_view : "`ALTER VIEW [.] [(, ...)] AS SELECT ...`" + | #show_views : "`SHOW [FULL] VIEWS [FROM ] []`" | #stream_table | #create_index: "`CREATE [OR REPLACE] AGGREGATING INDEX [IF NOT EXISTS] AS SELECT ...`" | #drop_index: "`DROP INDEX [IF EXISTS] `" diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs index 8d91e9f1994bc..9d9974d7937cc 100644 --- a/src/query/ast/src/parser/token.rs +++ b/src/query/ast/src/parser/token.rs @@ -1109,6 +1109,8 @@ pub enum TokenKind { VERBOSE, #[token("VIEW", ignore(ascii_case))] VIEW, + #[token("VIEWS", ignore(ascii_case))] + VIEWS, #[token("VIRTUAL", ignore(ascii_case))] VIRTUAL, #[token("WHEN", ignore(ascii_case))] diff --git a/src/query/ast/tests/it/parser.rs b/src/query/ast/tests/it/parser.rs index 98350451d2e69..c2ecc50b1c7f6 100644 --- a/src/query/ast/tests/it/parser.rs +++ b/src/query/ast/tests/it/parser.rs @@ -142,6 +142,11 @@ fn test_statement() { r#"create view v1(c1) as select number % 3 as a from numbers(1000);"#, r#"create or replace view v1(c1) as select number % 3 as a from numbers(1000);"#, r#"alter view v1(c2) as select number % 3 as a from numbers(1000);"#, + r#"show views"#, + r#"show views format TabSeparatedWithNamesAndTypes;"#, + r#"show full views"#, + r#"show full views from db"#, + r#"show full views from ctl.db"#, r#"create stream test2.s1 on table test.t append_only = false;"#, r#"create stream if not exists test2.s2 on table test.t at (stream => test1.s1) comment = 'this is a stream';"#, r#"create or replace stream test2.s1 on table test.t append_only = false;"#, diff --git a/src/query/ast/tests/it/testdata/statement-error.txt b/src/query/ast/tests/it/testdata/statement-error.txt index e110780e1ba6f..0d9f3849533ab 100644 --- a/src/query/ast/tests/it/testdata/statement-error.txt +++ b/src/query/ast/tests/it/testdata/statement-error.txt @@ -238,7 +238,7 @@ error: --> SQL:1:6 | 1 | SHOW GRANT FOR ROLE 'role1'; - | ^^^^^ unexpected `GRANT`, expecting `GRANTS`, `CREATE`, `NETWORK`, `VIRTUAL`, `STREAMS`, `CATALOGS`, `FUNCTIONS`, `DATABASES`, `CONNECTIONS`, `TABLE_FUNCTIONS`, `DROP`, `TABLE`, `ROLES`, `SHARE`, `TASKS`, `INDEXES`, `COLUMNS`, `PASSWORD`, `PROCESSLIST`, `STAGES`, `TABLES`, `SHARES`, `ENGINES`, `METRICS`, `SETTINGS`, `LOCKS`, `SCHEMAS`, `FIELDS`, `USERS`, `USER`, `FILE`, or `FULL` + | ^^^^^ unexpected `GRANT`, expecting `GRANTS`, `CREATE`, `NETWORK`, `VIRTUAL`, `STREAMS`, `CATALOGS`, `FUNCTIONS`, `DATABASES`, `CONNECTIONS`, `TABLE_FUNCTIONS`, `DROP`, `TABLE`, `ROLES`, `SHARE`, `TASKS`, `INDEXES`, `COLUMNS`, `PASSWORD`, `PROCESSLIST`, `STAGES`, `TABLES`, `SHARES`, `ENGINES`, `METRICS`, `SETTINGS`, `LOCKS`, `SCHEMAS`, `FIELDS`, `VIEWS`, `USERS`, `USER`, `FILE`, or `FULL` ---------- Input ---------- diff --git a/src/query/ast/tests/it/testdata/statement.txt b/src/query/ast/tests/it/testdata/statement.txt index cbef1562b2fe0..ef0b2fe0c93d4 100644 --- a/src/query/ast/tests/it/testdata/statement.txt +++ b/src/query/ast/tests/it/testdata/statement.txt @@ -3240,6 +3240,114 @@ AlterView( ) +---------- Input ---------- +show views +---------- Output --------- +SHOW VIEWS +---------- AST ------------ +ShowViews( + ShowViewsStmt { + catalog: None, + database: None, + full: false, + limit: None, + with_history: false, + }, +) + + +---------- Input ---------- +show views format TabSeparatedWithNamesAndTypes; +---------- Output --------- +SHOW VIEWS +---------- AST ------------ +ShowViews( + ShowViewsStmt { + catalog: None, + database: None, + full: false, + limit: None, + with_history: false, + }, +) + + +---------- FORMAT ------------ +Some( + "TabSeparatedWithNamesAndTypes", +) +---------- Input ---------- +show full views +---------- Output --------- +SHOW FULL VIEWS +---------- AST ------------ +ShowViews( + ShowViewsStmt { + catalog: None, + database: None, + full: true, + limit: None, + with_history: false, + }, +) + + +---------- Input ---------- +show full views from db +---------- Output --------- +SHOW FULL VIEWS FROM db +---------- AST ------------ +ShowViews( + ShowViewsStmt { + catalog: None, + database: Some( + Identifier { + span: Some( + 21..23, + ), + name: "db", + quote: None, + }, + ), + full: true, + limit: None, + with_history: false, + }, +) + + +---------- Input ---------- +show full views from ctl.db +---------- Output --------- +SHOW FULL VIEWS FROM ctl.db +---------- AST ------------ +ShowViews( + ShowViewsStmt { + catalog: Some( + Identifier { + span: Some( + 21..24, + ), + name: "ctl", + quote: None, + }, + ), + database: Some( + Identifier { + span: Some( + 25..27, + ), + name: "db", + quote: None, + }, + ), + full: true, + limit: None, + with_history: false, + }, +) + + ---------- Input ---------- create stream test2.s1 on table test.t append_only = false; ---------- Output --------- diff --git a/src/query/service/src/interpreters/access/privilege_access.rs b/src/query/service/src/interpreters/access/privilege_access.rs index ec02634907534..213870841d588 100644 --- a/src/query/service/src/interpreters/access/privilege_access.rs +++ b/src/query/service/src/interpreters/access/privilege_access.rs @@ -875,6 +875,9 @@ impl AccessChecker for PrivilegeAccess { Plan::DropView(plan) => { self.validate_db_access(&plan.catalog, &plan.database, vec![UserPrivilegeType::Drop], plan.if_exists).await? } + Plan::DescribeView(plan) => { + self.validate_table_access(&plan.catalog, &plan.database, &plan.view_name, vec![UserPrivilegeType::Select], false).await? + } Plan::CreateStream(plan) => { self.validate_db_access(&plan.catalog, &plan.database, vec![UserPrivilegeType::Create], false).await? } diff --git a/src/query/service/src/interpreters/interpreter_factory.rs b/src/query/service/src/interpreters/interpreter_factory.rs index cb4b4a8c7baa1..6428526ff0993 100644 --- a/src/query/service/src/interpreters/interpreter_factory.rs +++ b/src/query/service/src/interpreters/interpreter_factory.rs @@ -60,6 +60,7 @@ use crate::interpreters::interpreter_tasks_show::ShowTasksInterpreter; use crate::interpreters::interpreter_txn_abort::AbortInterpreter; use crate::interpreters::interpreter_txn_begin::BeginInterpreter; use crate::interpreters::interpreter_txn_commit::CommitInterpreter; +use crate::interpreters::interpreter_view_describe::DescribeViewInterpreter; use crate::interpreters::AlterUserInterpreter; use crate::interpreters::CreateShareEndpointInterpreter; use crate::interpreters::CreateShareInterpreter; @@ -262,6 +263,10 @@ impl InterpreterFactory { ctx, *drop_view.clone(), )?)), + Plan::DescribeView(describe_view) => Ok(Arc::new(DescribeViewInterpreter::try_create( + ctx, + *describe_view.clone(), + )?)), // Streams Plan::CreateStream(create_stream) => Ok(Arc::new(CreateStreamInterpreter::try_create( diff --git a/src/query/service/src/interpreters/interpreter_view_describe.rs b/src/query/service/src/interpreters/interpreter_view_describe.rs new file mode 100644 index 0000000000000..20cd067c8f1cf --- /dev/null +++ b/src/query/service/src/interpreters/interpreter_view_describe.rs @@ -0,0 +1,132 @@ +// Copyright 2021 Datafuse Labs +// +// 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 std::sync::Arc; + +use databend_common_exception::ErrorCode; +use databend_common_exception::Result; +use databend_common_expression::infer_table_schema; +use databend_common_expression::types::StringType; +use databend_common_expression::ComputedExpr; +use databend_common_expression::DataBlock; +use databend_common_expression::FromData; +use databend_common_expression::Scalar; +use databend_common_sql::plans::DescribeViewPlan; +use databend_common_storages_stream::stream_table::STREAM_ENGINE; +use databend_common_storages_view::view_table::QUERY; +use databend_common_storages_view::view_table::VIEW_ENGINE; + +use crate::interpreters::Interpreter; +use crate::pipelines::PipelineBuildResult; +use crate::sessions::QueryContext; +use crate::sessions::TableContext; +use crate::sql::Planner; + +pub struct DescribeViewInterpreter { + ctx: Arc, + plan: DescribeViewPlan, +} + +impl DescribeViewInterpreter { + pub fn try_create(ctx: Arc, plan: DescribeViewPlan) -> Result { + Ok(DescribeViewInterpreter { ctx, plan }) + } +} + +#[async_trait::async_trait] +impl Interpreter for DescribeViewInterpreter { + fn name(&self) -> &str { + "DescribeViewInterpreter" + } + + fn is_ddl(&self) -> bool { + true + } + + #[async_backtrace::framed] + async fn execute2(&self) -> Result { + let catalog = self.plan.catalog.as_str(); + let database = self.plan.database.as_str(); + let view = self.plan.view_name.as_str(); + let table = self.ctx.get_table(catalog, database, view).await?; + let tbl_info = table.get_table_info(); + let engine = table.get_table_info().engine(); + let schema = if engine == VIEW_ENGINE { + if let Some(query) = tbl_info.options().get(QUERY) { + let mut planner = Planner::new(self.ctx.clone()); + let (plan, _) = planner.plan_sql(query).await?; + infer_table_schema(&plan.schema()) + } else { + return Err(ErrorCode::Internal( + "Logical error, View Table must have a SelectQuery inside.", + )); + } + } else { + return Err(ErrorCode::TableEngineNotSupported(format!( + "{}.{} is not VIEW, please use `DESC {} {}.{}`", + &self.plan.database, + &self.plan.view_name, + if engine == STREAM_ENGINE { + "STREAM" + } else { + "TABLE" + }, + &self.plan.database, + &self.plan.view_name + ))); + }?; + + let mut names: Vec = vec![]; + let mut types: Vec = vec![]; + let mut nulls: Vec = vec![]; + let mut default_exprs: Vec = vec![]; + let mut extras: Vec = vec![]; + + for field in schema.fields().iter() { + names.push(field.name().to_string()); + + let non_null_type = field.data_type().remove_recursive_nullable(); + types.push(non_null_type.sql_name()); + nulls.push(if field.is_nullable() { + "YES".to_string() + } else { + "NO".to_string() + }); + match field.default_expr() { + Some(expr) => { + default_exprs.push(expr.clone()); + } + + None => { + let value = Scalar::default_value(&field.data_type().into()); + default_exprs.push(value.to_string()); + } + } + let extra = match field.computed_expr() { + Some(ComputedExpr::Virtual(expr)) => format!("VIRTUAL COMPUTED COLUMN `{}`", expr), + Some(ComputedExpr::Stored(expr)) => format!("STORED COMPUTED COLUMN `{}`", expr), + _ => "".to_string(), + }; + extras.push(extra); + } + + PipelineBuildResult::from_blocks(vec![DataBlock::new_from_columns(vec![ + StringType::from_data(names), + StringType::from_data(types), + StringType::from_data(nulls), + StringType::from_data(default_exprs), + StringType::from_data(extras), + ])]) + } +} diff --git a/src/query/service/src/interpreters/mod.rs b/src/query/service/src/interpreters/mod.rs index 7ce40bd5cf700..2b916e943fdcc 100644 --- a/src/query/service/src/interpreters/mod.rs +++ b/src/query/service/src/interpreters/mod.rs @@ -136,6 +136,7 @@ mod interpreter_vacuum_drop_tables; mod interpreter_vacuum_temporary_files; mod interpreter_view_alter; mod interpreter_view_create; +mod interpreter_view_describe; mod interpreter_view_drop; mod interpreter_virtual_column_alter; mod interpreter_virtual_column_create; diff --git a/src/query/sql/src/planner/binder/binder.rs b/src/query/sql/src/planner/binder/binder.rs index f680f7eea5754..fc539647b63dd 100644 --- a/src/query/sql/src/planner/binder/binder.rs +++ b/src/query/sql/src/planner/binder/binder.rs @@ -315,6 +315,8 @@ impl<'a> Binder { Statement::CreateView(stmt) => self.bind_create_view(stmt).await?, Statement::AlterView(stmt) => self.bind_alter_view(stmt).await?, Statement::DropView(stmt) => self.bind_drop_view(stmt).await?, + Statement::ShowViews(stmt) => self.bind_show_views(bind_context, stmt).await?, + Statement::DescribeView(stmt) => self.bind_describe_view(stmt).await?, // Indexes Statement::CreateIndex(stmt) => self.bind_create_index(bind_context, stmt).await?, diff --git a/src/query/sql/src/planner/binder/ddl/view.rs b/src/query/sql/src/planner/binder/ddl/view.rs index 45e43a4b62e90..cc0be8d8a06ca 100644 --- a/src/query/sql/src/planner/binder/ddl/view.rs +++ b/src/query/sql/src/planner/binder/ddl/view.rs @@ -14,16 +14,27 @@ use databend_common_ast::ast::AlterViewStmt; use databend_common_ast::ast::CreateViewStmt; +use databend_common_ast::ast::DescribeViewStmt; use databend_common_ast::ast::DropViewStmt; +use databend_common_ast::ast::ShowLimit; +use databend_common_ast::ast::ShowViewsStmt; use databend_common_exception::Result; +use databend_common_expression::types::DataType; +use databend_common_expression::DataField; +use databend_common_expression::DataSchemaRefExt; use derive_visitor::DriveMut; +use log::debug; use crate::binder::Binder; use crate::planner::semantic::normalize_identifier; use crate::plans::AlterViewPlan; use crate::plans::CreateViewPlan; +use crate::plans::DescribeViewPlan; use crate::plans::DropViewPlan; use crate::plans::Plan; +use crate::plans::RewriteKind; +use crate::BindContext; +use crate::SelectBuilder; use crate::ViewRewriter; impl Binder { @@ -128,4 +139,119 @@ impl Binder { }; Ok(Plan::DropView(plan.into())) } + + #[async_backtrace::framed] + pub(in crate::planner::binder) async fn bind_show_views( + &mut self, + bind_context: &mut BindContext, + stmt: &ShowViewsStmt, + ) -> Result { + let ShowViewsStmt { + catalog, + database, + full, + limit, + with_history, + } = stmt; + + let database = self.check_database_exist(catalog, database).await?; + + let mut select_builder = if stmt.with_history { + SelectBuilder::from("system.tables_with_history") + } else { + SelectBuilder::from("system.tables") + }; + + if *full { + select_builder + .with_column("name AS Tables") + .with_column("'BASE TABLE' AS Table_type") + .with_column("database AS Database") + .with_column("catalog AS Catalog") + .with_column("owner") + .with_column("engine") + .with_column("cluster_by AS Cluster_by") + .with_column("created_on AS create_time"); + if *with_history { + select_builder.with_column("dropped_on AS drop_time"); + } + + select_builder + .with_column("num_rows") + .with_column("data_size") + .with_column("data_compressed_size") + .with_column("index_size"); + } else { + select_builder.with_column(format!("name AS `Views_in_{database}`")); + if *with_history { + select_builder.with_column("dropped_on AS drop_time"); + }; + } + + select_builder + .with_order_by("catalog") + .with_order_by("database") + .with_order_by("name"); + + select_builder.with_filter(format!("database = '{database}'")); + select_builder.with_filter(" engine = 'VIEW'".to_string()); + + let catalog_name = match catalog { + None => self.ctx.get_current_catalog(), + Some(ident) => { + let catalog = normalize_identifier(ident, &self.name_resolution_ctx).name; + self.ctx.get_catalog(&catalog).await?; + catalog + } + }; + + select_builder.with_filter(format!("catalog = '{catalog_name}'")); + let query = match limit { + None => select_builder.build(), + Some(ShowLimit::Like { pattern }) => { + select_builder.with_filter(format!("name LIKE '{pattern}'")); + select_builder.build() + } + Some(ShowLimit::Where { selection }) => { + select_builder.with_filter(format!("({selection})")); + select_builder.build() + } + }; + debug!("show views rewrite to: {:?}", query); + self.bind_rewrite_to_query( + bind_context, + query.as_str(), + RewriteKind::ShowTables(catalog_name, database), + ) + .await + } + + #[async_backtrace::framed] + pub(in crate::planner::binder) async fn bind_describe_view( + &mut self, + stmt: &DescribeViewStmt, + ) -> Result { + let DescribeViewStmt { + catalog, + database, + view, + } = stmt; + + let (catalog, database, view_name) = + self.normalize_object_identifier_triple(catalog, database, view); + let schema = DataSchemaRefExt::create(vec![ + DataField::new("Field", DataType::String), + DataField::new("Type", DataType::String), + DataField::new("Null", DataType::String), + DataField::new("Default", DataType::String), + DataField::new("Extra", DataType::String), + ]); + + Ok(Plan::DescribeView(Box::new(DescribeViewPlan { + catalog, + database, + view_name, + schema, + }))) + } } diff --git a/src/query/sql/src/planner/format/display_plan.rs b/src/query/sql/src/planner/format/display_plan.rs index cf37d082f71a7..e5d7d4801753d 100644 --- a/src/query/sql/src/planner/format/display_plan.rs +++ b/src/query/sql/src/planner/format/display_plan.rs @@ -98,6 +98,7 @@ impl Plan { Plan::CreateView(_) => Ok("CreateView".to_string()), Plan::AlterView(_) => Ok("AlterView".to_string()), Plan::DropView(_) => Ok("DropView".to_string()), + Plan::DescribeView(_) => Ok("DescribeView".to_string()), // Streams Plan::CreateStream(_) => Ok("CreateStream".to_string()), diff --git a/src/query/sql/src/planner/plans/ddl/view.rs b/src/query/sql/src/planner/plans/ddl/view.rs index 4541c357d639a..a555d9ddae409 100644 --- a/src/query/sql/src/planner/plans/ddl/view.rs +++ b/src/query/sql/src/planner/plans/ddl/view.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use databend_common_expression::DataSchemaRef; use databend_common_meta_app::schema::CreateOption; #[derive(Clone, Debug, PartialEq, Eq)] @@ -43,3 +44,17 @@ pub struct DropViewPlan { pub database: String, pub view_name: String, } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DescribeViewPlan { + pub catalog: String, + pub database: String, + pub view_name: String, + pub schema: DataSchemaRef, +} + +impl DescribeViewPlan { + pub fn schema(&self) -> DataSchemaRef { + self.schema.clone() + } +} diff --git a/src/query/sql/src/planner/plans/plan.rs b/src/query/sql/src/planner/plans/plan.rs index c6d6dbe3aaac1..403d43b8077aa 100644 --- a/src/query/sql/src/planner/plans/plan.rs +++ b/src/query/sql/src/planner/plans/plan.rs @@ -72,6 +72,7 @@ use crate::plans::DescPasswordPolicyPlan; use crate::plans::DescSharePlan; use crate::plans::DescribeTablePlan; use crate::plans::DescribeTaskPlan; +use crate::plans::DescribeViewPlan; use crate::plans::DropCatalogPlan; use crate::plans::DropConnectionPlan; use crate::plans::DropDatabasePlan; @@ -228,6 +229,7 @@ pub enum Plan { CreateView(Box), AlterView(Box), DropView(Box), + DescribeView(Box), // Streams CreateStream(Box), @@ -430,6 +432,7 @@ impl Plan { Plan::VacuumDropTable(plan) => plan.schema(), Plan::VacuumTemporaryFiles(plan) => plan.schema(), Plan::ExistsTable(plan) => plan.schema(), + Plan::DescribeView(plan) => plan.schema(), Plan::ShowRoles(plan) => plan.schema(), Plan::ShowGrants(plan) => plan.schema(), Plan::ShowFileFormats(plan) => plan.schema(), diff --git a/tests/sqllogictests/suites/base/05_ddl/05_0022_ddl_desc_view.test b/tests/sqllogictests/suites/base/05_ddl/05_0022_ddl_desc_view.test new file mode 100644 index 0000000000000..77541d70204ea --- /dev/null +++ b/tests/sqllogictests/suites/base/05_ddl/05_0022_ddl_desc_view.test @@ -0,0 +1,156 @@ +statement ok +use default + +statement ok +DROP VIEW IF EXISTS v + +statement ok +DROP TABLE IF EXISTS t + +statement ok +CREATE TABLE t(a bigint null, b int null, c varchar(255) null, d smallint not null, e Date not null ) ENGINE = Null + +statement ok +CREATE VIEW v AS SELECT * FROM t + +onlyif http +query TTTTT +DESCRIBE VIEW v +---- +a BIGINT YES NULL (empty) +b INT YES NULL (empty) +c VARCHAR YES NULL (empty) +d SMALLINT NO 0 (empty) +e DATE NO '1970-01-01' (empty) + +onlyif http +query TTTTT +DESC VIEW v +---- +a BIGINT YES NULL (empty) +b INT YES NULL (empty) +c VARCHAR YES NULL (empty) +d SMALLINT NO 0 (empty) +e DATE NO '1970-01-01' (empty) + + +statement ok +DROP VIEW IF EXISTS v1 + +statement ok +DROP TABLE IF EXISTS t1 + +statement ok +CREATE TABLE t1(a bigint null, b int null, c char(255) null, d smallint not null, e Date not null, f char(120) not null default '' ) ENGINE = Null + +statement ok +CREATE VIEW v1 AS SELECT * FROM t1 + +onlyif http +query VVVVV +DESCRIBE VIEW v1 +---- +a BIGINT YES NULL (empty) +b INT YES NULL (empty) +c VARCHAR YES NULL (empty) +d SMALLINT NO 0 (empty) +e DATE NO '1970-01-01' (empty) +f VARCHAR NO '' (empty) + +onlyif http +query VVVVV +DESC VIEW v1 +---- +a BIGINT YES NULL (empty) +b INT YES NULL (empty) +c VARCHAR YES NULL (empty) +d SMALLINT NO 0 (empty) +e DATE NO '1970-01-01' (empty) +f VARCHAR NO '' (empty) + + +query VVV +select column_name, nullable, is_nullable from INFORMATION_SCHEMA.COLUMNS where table_name='tables_with_history' and column_name in ('num_rows', 'dropped_on') order by column_name +---- +dropped_on 1 YES +num_rows 1 YES + +statement ok +DROP VIEW IF EXISTS v + +statement ok +DROP VIEW IF EXISTS v1 + +statement ok +DROP TABLE IF EXISTS t + +statement ok +DROP TABLE IF EXISTS t1 + +statement ok +DROP TABLE IF EXISTS t2 + +statement ok +CREATE TABLE t(c1 varchar, c2 varchar) ENGINE = Null + +statement ok +CREATE TABLE t1(c1 varchar, c2 varchar) ENGINE = Null + +statement ok +CREATE TABLE t2(c1 varchar, c2 varchar) ENGINE = Null + +statement ok +DROP VIEW IF EXISTS v2 + +statement ok +DROP VIEW IF EXISTS v3 + +statement ok +CREATE VIEW v1 AS SELECT * FROM t + +statement ok +CREATE VIEW v2 AS SELECT c1 AS v2_c1, c2 AS v2_c2 FROM (SELECT t1.c1, t2.c2 from t1 JOIN t2 ON t1.c1 = t2.c1) AS v_t + +statement ok +CREATE VIEW v3 AS SELECT * FROM v2 UNION ALL select * from v1 + +onlyif http +query VVVVV +DESC VIEW v1 +---- +c1 VARCHAR YES NULL (empty) +c2 VARCHAR YES NULL (empty) + +onlyif http +query VVVVV +DESC VIEW v2 +---- +v2_c1 VARCHAR YES NULL (empty) +v2_c2 VARCHAR YES NULL (empty) + +onlyif http +query VVVVV +DESC VIEW v3 +---- +v2_c1 VARCHAR YES NULL (empty) +v2_c2 VARCHAR YES NULL (empty) + +statement ok +DROP VIEW IF EXISTS v1 + +statement ok +DROP VIEW IF EXISTS v2 + +statement ok +DROP VIEW IF EXISTS v3 + +statement ok +DROP TABLE IF EXISTS t + +statement ok +DROP TABLE IF EXISTS t1 + +statement ok +DROP TABLE IF EXISTS t2 + + diff --git a/tests/sqllogictests/suites/base/06_show/06_0023_show_views.test b/tests/sqllogictests/suites/base/06_show/06_0023_show_views.test new file mode 100644 index 0000000000000..f151fbd68b822 --- /dev/null +++ b/tests/sqllogictests/suites/base/06_show/06_0023_show_views.test @@ -0,0 +1,107 @@ +statement ok +DROP DATABASE IF EXISTS showview + +statement ok +CREATE DATABASE showview + +statement ok +CREATE TABLE showview.t1(c1 int) ENGINE = Null + +statement ok +CREATE TABLE showview.t2(c1 int) ENGINE = Null + +statement ok +CREATE TABLE showview.t3(c1 int) ENGINE = Null + +statement ok +CREATE VIEW showview.v1 AS SELECT * FROM showview.t1 + +statement ok +CREATE VIEW showview.v2 AS SELECT * FROM showview.t2 + +statement ok +CREATE VIEW showview.v3 AS SELECT * FROM showview.t3 + + +statement ok +use showview + +query T +SHOW VIEWS +---- +v1 +v2 +v3 + +query T +SHOW VIEWS LIKE 'v%' +---- +v1 +v2 +v3 + +query T +SHOW VIEWS LIKE 'v2' +---- +v2 + +statement ok +SHOW VIEWS LIKE 'v' + +query T +SHOW VIEWS WHERE name LIKE 'v%' +---- +v1 +v2 +v3 + +statement ok +SHOW VIEWS WHERE name = 'v%' AND 1 = 0 + +query T +SHOW VIEWS WHERE name = 'v2' OR 1 = 1 +---- +v1 +v2 +v3 + +query T +SHOW VIEWS WHERE name = 'v2' AND 1 = 1 +---- +v2 + +statement ok +USE default + +statement error 1003 +SHOW VIEWS FROM showviews WHERE name LIKE 'v%' + +statement error 1003 +SHOW VIEWS FROM showviews WHERE name = 'v%' AND 1 = 0 + +statement error 1003 +SHOW VIEWS FROM showviews WHERE name = 'v2' OR 1 = 1 + +statement error 1003 +SHOW VIEWS FROM showviews WHERE name = 'v2' AND 1 = 1 + +statement ok +DROP DATABASE showview + +statement ok +drop database if exists `rust-lang` + +statement ok +create database `rust-lang` + +statement ok +create table `rust-lang`.t(id int); + +statement ok +use `rust-lang` + +statement ok +show views + +statement ok +drop database `rust-lang`