feat: show create view and creating view with columns (#4086)

* feat: parse column names when creating view

* feat: save the view definition into view info

* feat: supports view columns and show create view

* feat: save plan columns for validation

* fix: typo

* chore: comments and style

* chore: apply suggestions

* test: assert CreateView display result

* chore: style

Co-authored-by: Weny Xu <wenymedia@gmail.com>

* chore: avoid the clone

Co-authored-by: Weny Xu <wenymedia@gmail.com>

* fix: compile error after rebeasing

---------

Co-authored-by: Weny Xu <wenymedia@gmail.com>
This commit is contained in:
dennis zhuang
2024-07-09 11:35:11 -07:00
committed by GitHub
parent 458e5d7e66
commit 33ed745049
33 changed files with 1372 additions and 159 deletions

View File

@@ -24,7 +24,7 @@ use sqlparser::ast::{ColumnOption, ColumnOptionDef, DataType, Expr, KeyOrIndexDi
use sqlparser::dialect::keywords::Keyword;
use sqlparser::keywords::ALL_KEYWORDS;
use sqlparser::parser::IsOptional::Mandatory;
use sqlparser::parser::Parser;
use sqlparser::parser::{Parser, ParserError};
use sqlparser::tokenizer::{Token, TokenWithLocation, Word};
use table::requests::validate_table_option;
@@ -125,6 +125,8 @@ impl<'a> ParserContext<'a> {
let if_not_exists = self.parse_if_not_exist()?;
let view_name = self.intern_parse_table_name()?;
let columns = self.parse_view_columns()?;
self.parser
.expect_keyword(Keyword::AS)
.context(SyntaxSnafu)?;
@@ -133,12 +135,36 @@ impl<'a> ParserContext<'a> {
Ok(Statement::CreateView(CreateView {
name: view_name,
columns,
or_replace,
query: Box::new(query),
if_not_exists,
}))
}
fn parse_view_columns(&mut self) -> Result<Vec<Ident>> {
let mut columns = vec![];
if !self.parser.consume_token(&Token::LParen) || self.parser.consume_token(&Token::RParen) {
return Ok(columns);
}
loop {
let name = self.parse_column_name().context(SyntaxSnafu)?;
columns.push(name);
let comma = self.parser.consume_token(&Token::Comma);
if self.parser.consume_token(&Token::RParen) {
// allow a trailing comma, even though it's not in standard
break;
} else if !comma {
return self.expected("',' or ')' after column name", self.parser.peek_token());
}
}
Ok(columns)
}
fn parse_create_external_table(&mut self) -> Result<Statement> {
let _ = self.parser.next_token();
self.parser
@@ -547,10 +573,26 @@ impl<'a> ParserContext<'a> {
Ok(())
}
/// Parse the column name and check if it's valid.
fn parse_column_name(&mut self) -> std::result::Result<Ident, ParserError> {
let name = self.parser.parse_identifier(false)?;
if name.quote_style.is_none() &&
// "ALL_KEYWORDS" are sorted.
ALL_KEYWORDS.binary_search(&name.value.to_uppercase().as_str()).is_ok()
{
return Err(ParserError::ParserError(format!(
"Cannot use keyword '{}' as column name. Hint: add quotes to the name.",
&name.value
)));
}
Ok(name)
}
pub fn parse_column_def(&mut self) -> Result<Column> {
let name = self.parse_column_name().context(SyntaxSnafu)?;
let parser = &mut self.parser;
let name = parser.parse_identifier(false).context(SyntaxSnafu)?;
ensure!(
!(name.quote_style.is_none() &&
// "ALL_KEYWORDS" are sorted.
@@ -2009,4 +2051,85 @@ CREATE TABLE log (
.to_string()
.contains("invalid FULLTEXT option"));
}
#[test]
fn test_parse_create_view_with_columns() {
let sql = "CREATE VIEW test () AS SELECT * FROM NUMBERS";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
match &result[0] {
Statement::CreateView(c) => {
assert_eq!(c.to_string(), "CREATE VIEW test AS SELECT * FROM NUMBERS");
assert!(!c.or_replace);
assert!(!c.if_not_exists);
assert_eq!("test", c.name.to_string());
}
_ => unreachable!(),
}
assert_eq!(
"CREATE VIEW test AS SELECT * FROM NUMBERS",
result[0].to_string()
);
let sql = "CREATE VIEW test (n1) AS SELECT * FROM NUMBERS";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
match &result[0] {
Statement::CreateView(c) => {
assert_eq!(c.to_string(), sql);
assert!(!c.or_replace);
assert!(!c.if_not_exists);
assert_eq!("test", c.name.to_string());
}
_ => unreachable!(),
}
assert_eq!(sql, result[0].to_string());
let sql = "CREATE VIEW test (n1, n2) AS SELECT * FROM NUMBERS";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
match &result[0] {
Statement::CreateView(c) => {
assert_eq!(c.to_string(), sql);
assert!(!c.or_replace);
assert!(!c.if_not_exists);
assert_eq!("test", c.name.to_string());
}
_ => unreachable!(),
}
assert_eq!(sql, result[0].to_string());
// Some invalid syntax cases
let sql = "CREATE VIEW test (n1 AS select * from demo";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
assert!(result.is_err());
let sql = "CREATE VIEW test (n1, AS select * from demo";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
assert!(result.is_err());
let sql = "CREATE VIEW test n1,n2) AS select * from demo";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
assert!(result.is_err());
let sql = "CREATE VIEW test (1) AS select * from demo";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
assert!(result.is_err());
// keyword
let sql = "CREATE VIEW test (n1, select) AS select * from demo";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
assert!(result.is_err());
}
}

View File

@@ -21,8 +21,8 @@ use crate::error::{
};
use crate::parser::ParserContext;
use crate::statements::show::{
ShowColumns, ShowCreateFlow, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowStatus,
ShowTableStatus, ShowTables, ShowVariables,
ShowColumns, ShowCreateFlow, ShowCreateTable, ShowCreateView, ShowDatabases, ShowIndex,
ShowKind, ShowStatus, ShowTableStatus, ShowTables, ShowVariables,
};
use crate::statements::statement::Statement;
@@ -74,6 +74,8 @@ impl<'a> ParserContext<'a> {
self.parse_show_create_table()
} else if self.consume_token("FLOW") {
self.parse_show_create_flow()
} else if self.consume_token("VIEW") {
self.parse_show_create_view()
} else {
self.unsupported(self.peek_token_as_string())
}
@@ -141,6 +143,24 @@ impl<'a> ParserContext<'a> {
Ok(Statement::ShowCreateFlow(ShowCreateFlow { flow_name }))
}
fn parse_show_create_view(&mut self) -> Result<Statement> {
let raw_view_name = self
.parse_object_name()
.with_context(|_| error::UnexpectedSnafu {
sql: self.sql,
expected: "a view name",
actual: self.peek_token_as_string(),
})?;
let view_name = Self::canonicalize_object_name(raw_view_name);
ensure!(
!view_name.0.is_empty(),
InvalidTableNameSnafu {
name: view_name.to_string(),
}
);
Ok(Statement::ShowCreateView(ShowCreateView { view_name }))
}
fn parse_show_table_name(&mut self) -> Result<String> {
self.parser.next_token();
let table_name = self
@@ -906,4 +926,20 @@ mod tests {
assert!(matches!(stmt.kind, ShowKind::Where(_)));
assert_eq!(sql, stmt.to_string());
}
#[test]
pub fn test_show_create_view() {
let sql = "SHOW CREATE VIEW test";
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
let stmts = result.unwrap();
assert_eq!(1, stmts.len());
assert_eq!(
stmts[0],
Statement::ShowCreateView(ShowCreateView {
view_name: ObjectName(vec![Ident::new("test")]),
})
);
assert_eq!(sql, stmts[0].to_string());
}
}

View File

@@ -385,6 +385,8 @@ impl Display for CreateFlow {
pub struct CreateView {
/// View name
pub name: ObjectName,
/// An optional list of names to be used for columns of the view
pub columns: Vec<Ident>,
/// The clause after `As` that defines the VIEW.
/// Can only be either [Statement::Query] or [Statement::Tql].
pub query: Box<Statement>,
@@ -405,6 +407,9 @@ impl Display for CreateView {
write!(f, "IF NOT EXISTS ")?;
}
write!(f, "{} ", &self.name)?;
if !self.columns.is_empty() {
write!(f, "({}) ", format_list_comma!(self.columns))?;
}
write!(f, "AS {}", &self.query)
}
}

View File

@@ -187,6 +187,19 @@ impl Display for ShowCreateFlow {
}
}
/// SQL structure for `SHOW CREATE VIEW`.
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ShowCreateView {
pub view_name: ObjectName,
}
impl Display for ShowCreateView {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let view_name = &self.view_name;
write!(f, "SHOW CREATE VIEW {view_name}")
}
}
/// SQL structure for `SHOW VARIABLES xxx`.
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ShowVariables {

View File

@@ -31,8 +31,8 @@ use crate::statements::insert::Insert;
use crate::statements::query::Query;
use crate::statements::set_variables::SetVariables;
use crate::statements::show::{
ShowColumns, ShowCreateFlow, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowStatus,
ShowTableStatus, ShowTables, ShowVariables,
ShowColumns, ShowCreateFlow, ShowCreateTable, ShowCreateView, ShowDatabases, ShowIndex,
ShowKind, ShowStatus, ShowTableStatus, ShowTables, ShowVariables,
};
use crate::statements::tql::Tql;
use crate::statements::truncate::TruncateTable;
@@ -85,6 +85,8 @@ pub enum Statement {
ShowCreateTable(ShowCreateTable),
// SHOW CREATE FLOW
ShowCreateFlow(ShowCreateFlow),
// SHOW CREATE VIEW
ShowCreateView(ShowCreateView),
// SHOW STATUS
ShowStatus(ShowStatus),
// DESCRIBE TABLE
@@ -126,6 +128,7 @@ impl Display for Statement {
Statement::ShowIndex(s) => s.fmt(f),
Statement::ShowCreateTable(s) => s.fmt(f),
Statement::ShowCreateFlow(s) => s.fmt(f),
Statement::ShowCreateView(s) => s.fmt(f),
Statement::ShowStatus(s) => s.fmt(f),
Statement::DescribeTable(s) => s.fmt(f),
Statement::Explain(s) => s.fmt(f),