Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): support show create [materialized] view command #6921

Merged
merged 5 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions e2e_test/ddl/show.slt
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,25 @@ query T
show sources;
----

query TT
show create materialized view mv3;
----
public.mv3 CREATE MATERIALIZED VIEW mv3 AS SELECT sum(v1) AS sum_v1 FROM t3

statement ok
create view v1 as select * from t3;

query TT
show create view v1;
----
public.v1 CREATE VIEW v1 AS SELECT * FROM t3

statement ok
drop materialized view mv3;

statement ok
drop view v1;

statement ok
drop table t3;

Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ pub async fn handle(
}
Statement::Describe { name } => describe::handle_describe(handler_args, name),
Statement::ShowObjects(show_object) => show::handle_show_object(handler_args, show_object),
Statement::ShowCreateObject { create_type, name } => {
show::handle_show_create_object(handler_args, create_type, name)
}
Statement::Drop(DropStatement {
object_type,
object_name,
Expand Down
74 changes: 72 additions & 2 deletions src/frontend/src/handler/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use itertools::Itertools;
use pgwire::pg_field_descriptor::PgFieldDescriptor;
use pgwire::pg_response::{PgResponse, StatementType};
use pgwire::types::Row;
use risingwave_common::catalog::{ColumnDesc, DEFAULT_SCHEMA_NAME};
use risingwave_common::error::Result;
use risingwave_common::error::{ErrorCode, Result, RwError};
use risingwave_common::types::DataType;
use risingwave_sqlparser::ast::{Ident, ObjectName, ShowObject};
use risingwave_sqlparser::ast::{Ident, ObjectName, ShowCreateType, ShowObject};
use risingwave_sqlparser::parser::Parser;

use super::RwPgResponse;
use crate::binder::{Binder, Relation};
Expand Down Expand Up @@ -131,6 +133,74 @@ pub fn handle_show_object(handler_args: HandlerArgs, command: ShowObject) -> Res
))
}

pub fn handle_show_create_object(
handle_args: HandlerArgs,
show_create_type: ShowCreateType,
name: ObjectName,
) -> Result<RwPgResponse> {
let session = handle_args.session;
let catalog_reader = session.env().catalog_reader().read_guard();
let (schema_name, object_name) =
Binder::resolve_schema_qualified_name(session.database(), name.clone())?;
let schema_name = schema_name.unwrap_or(DEFAULT_SCHEMA_NAME.to_string());
let schema = catalog_reader.get_schema_by_name(session.database(), &schema_name)?;
let sql = match show_create_type {
ShowCreateType::MaterializedView => {
let table = schema.get_table_by_name(&object_name).ok_or_else(|| {
RwError::from(CatalogError::NotFound(
"materialized view",
name.to_string(),
))
})?;
if !table.is_mview() {
return Err(CatalogError::NotFound("materialized view", name.to_string()).into());
}
// We only stored the definition of materialized view, format and return directly.
format!(
"CREATE MATERIALIZED VIEW {} AS {}",
table.name, table.definition
)
}
ShowCreateType::View => {
let view = schema
.get_view_by_name(&object_name)
.ok_or_else(|| RwError::from(CatalogError::NotFound("view", name.to_string())))?;

// We only stored original sql in catalog, uses parser to parse and format.
let stmt = Parser::parse_sql(&view.sql)
.map_err(|err| anyhow!("Failed to parse view create sql: {}, {}", view.sql, err))?;
assert!(stmt.len() == 1);
stmt[0].to_string()
Comment on lines +169 to +173
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SQL unparsed from the AST might be incorrect. :( #6801 Will this be a problem, if this is for some programs to read?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, I guess we need to carefully review the fmt::Display for all AST nodes and add a comprehensive test suite for it.

Copy link
Member Author

@yezizp2012 yezizp2012 Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥵 I think this is just for user to check how the mview and view are created.

If so, I guess we need to carefully review the fmt::Display for all AST nodes and add a comprehensive test suite for it.

But this's also needed, does the tests in sql_parser meet demand? It seems that we only have few tests there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. cc @lmatz

Copy link
Member

@xxchan xxchan Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can test roundtrip parsing (parse(unparse(parse(sql))) == parse(sql) ) in sqlsmith. Created an issue #6935

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree.

Copy link
Member

@BugenZhao BugenZhao Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can test roundtrip parsing (parse(unparse(parse(sql))) == parse(sql) ) in sqlsmith.

I'm afraid there's a case that some different SQLs are parsed to the same AST, which requires some binder context to distinguish. So when we unparse it, we cannot find the original representation, and this roundtrip test cannot cover this. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's exactly why using AST instead of original sql to compare in xxchan's proposal?

}
_ => {
return Err(ErrorCode::NotImplemented(
format!("show create on: {}", show_create_type),
None.into(),
)
.into());
}
};
let name = format!("{}.{}", schema_name, object_name);

Ok(PgResponse::new_for_stream(
StatementType::SHOW_COMMAND,
Some(1),
vec![Row::new(vec![Some(name.into()), Some(sql.into())])].into(),
vec![
PgFieldDescriptor::new(
"Name".to_owned(),
DataType::VARCHAR.to_oid(),
DataType::VARCHAR.type_len(),
),
PgFieldDescriptor::new(
"Create Sql".to_owned(),
DataType::VARCHAR.to_oid(),
DataType::VARCHAR.type_len(),
),
],
))
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;
Expand Down
37 changes: 36 additions & 1 deletion src/sqlparser/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,30 @@ impl fmt::Display for ShowObject {
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ShowCreateType {
Table,
MaterializedView,
View,
Index,
Source,
Sink,
}

impl fmt::Display for ShowCreateType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ShowCreateType::Table => f.write_str("TABLE"),
ShowCreateType::MaterializedView => f.write_str("MATERIALIZED VIEW"),
ShowCreateType::View => f.write_str("VIEW"),
ShowCreateType::Index => f.write_str("INDEX"),
ShowCreateType::Source => f.write_str("SOURCE"),
ShowCreateType::Sink => f.write_str("SINK"),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CommentObject {
Expand Down Expand Up @@ -920,8 +944,15 @@ pub enum Statement {
/// Table or Source name
name: ObjectName,
},
/// SHOW COMMAND
/// SHOW OBJECT COMMAND
ShowObjects(ShowObject),
/// SHOW CREATE COMMAND
ShowCreateObject {
/// Show create object type
create_type: ShowCreateType,
/// Show create object name
name: ObjectName,
},
/// DROP
Drop(DropStatement),
/// SET <variable>
Expand Down Expand Up @@ -1061,6 +1092,10 @@ impl fmt::Display for Statement {
write!(f, "SHOW {}", show_object)?;
Ok(())
}
Statement::ShowCreateObject{ create_type: show_type, name } => {
write!(f, "SHOW CREATE {} {}", show_type, name)?;
Ok(())
}
Statement::Insert {
table_name,
columns,
Expand Down
42 changes: 41 additions & 1 deletion src/sqlparser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,13 @@ impl Parser {
Keyword::ALTER => Ok(self.parse_alter()?),
Keyword::COPY => Ok(self.parse_copy()?),
Keyword::SET => Ok(self.parse_set()?),
Keyword::SHOW => Ok(self.parse_show()?),
Keyword::SHOW => {
if self.parse_keyword(Keyword::CREATE) {
Ok(self.parse_show_create()?)
} else {
Ok(self.parse_show()?)
}
}
Keyword::DESCRIBE => Ok(Statement::Describe {
name: self.parse_object_name()?,
}),
Expand Down Expand Up @@ -2925,6 +2931,40 @@ impl Parser {
}
}

/// Parse object type and name after `show create`.
pub fn parse_show_create(&mut self) -> Result<Statement, ParserError> {
if let Token::Word(w) = self.next_token() {
let show_type = match w.keyword {
Keyword::TABLE => ShowCreateType::Table,
Keyword::MATERIALIZED => {
if self.parse_keyword(Keyword::VIEW) {
ShowCreateType::MaterializedView
} else {
return self.expected("VIEW after MATERIALIZED", self.peek_token());
}
}
Keyword::VIEW => ShowCreateType::View,
Keyword::INDEX => ShowCreateType::Index,
Keyword::SOURCE => ShowCreateType::Source,
Keyword::SINK => ShowCreateType::Sink,
_ => {
return self.expected(
"TABLE, MATERIALIZED VIEW, VIEW, INDEX, SOURCE or SINK",
self.peek_token(),
)
}
};
return Ok(Statement::ShowCreateObject {
create_type: show_type,
name: self.parse_object_name()?,
});
}
self.expected(
"TABLE, MATERIALIZED VIEW, VIEW, INDEX, SOURCE or SINK",
self.peek_token(),
)
}

pub fn parse_table_and_joins(&mut self) -> Result<TableWithJoins, ParserError> {
let relation = self.parse_table_factor()?;

Expand Down
8 changes: 8 additions & 0 deletions src/sqlparser/tests/testdata/show.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@
formatted_ast: |
ShowObjects(Columns { table: ObjectName([Ident { value: "schema", quote_style: None }, Ident { value: "t", quote_style: None }]) })

- input: SHOW CREATE MATERIALIZED VIEW schema.mv
formatted_sql: SHOW CREATE MATERIALIZED VIEW schema.mv
formatted_ast: |
ShowCreateObject { create_type: MaterializedView, name: ObjectName([Ident { value: "schema", quote_style: None }, Ident { value: "mv", quote_style: None }]) }

- input: SHOW CREATE VIEW schema.v
formatted_sql: SHOW CREATE VIEW schema.v
formatted_ast: |
ShowCreateObject { create_type: View, name: ObjectName([Ident { value: "schema", quote_style: None }, Ident { value: "v", quote_style: None }]) }