mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-20 23:10:37 +00:00
feat: create view (#3807)
* add statement Signed-off-by: Ruihang Xia <waynestxia@gmail.com> * feat: rebase with main * fix: create flow * feat: adds gRPC stuff * feat: impl create_view ddl in operator * feat: impl CreateViewProcedure * chore: update cargo lock * fix: format * chore: compile error after rebasing main * chore: refactor and test create view parser * chore: fixed todo list and comments * fix: compile error after rebeasing * test: add create view test * test: test view_info keys * test: adds test for CreateViewProcedure and clean code * test: adds more sqlness test for creating views * chore: update cargo lock * fix: don't replace normal table in CreateViewProcedure * chore: apply suggestion Co-authored-by: Jeremyhi <jiachun_feng@proton.me> * chore: style Co-authored-by: Jeremyhi <jiachun_feng@proton.me> --------- Signed-off-by: Ruihang Xia <waynestxia@gmail.com> Co-authored-by: Ruihang Xia <waynestxia@gmail.com> Co-authored-by: Jeremyhi <jiachun_feng@proton.me>
This commit is contained in:
@@ -33,8 +33,8 @@ use crate::error::{
|
||||
};
|
||||
use crate::parser::{ParserContext, FLOW};
|
||||
use crate::statements::create::{
|
||||
CreateDatabase, CreateExternalTable, CreateFlow, CreateTable, CreateTableLike, Partitions,
|
||||
TIME_INDEX,
|
||||
CreateDatabase, CreateExternalTable, CreateFlow, CreateTable, CreateTableLike, CreateView,
|
||||
Partitions, TIME_INDEX,
|
||||
};
|
||||
use crate::statements::statement::Statement;
|
||||
use crate::statements::{get_data_type_by_alias_name, OptionMap};
|
||||
@@ -70,6 +70,7 @@ impl<'a> ParserContext<'a> {
|
||||
.context(SyntaxSnafu)?;
|
||||
match self.parser.next_token().token {
|
||||
Token::Word(w) => match w.keyword {
|
||||
Keyword::VIEW => self.parse_create_view(true),
|
||||
Keyword::NoKeyword => {
|
||||
let uppercase = w.value.to_uppercase();
|
||||
match uppercase.as_str() {
|
||||
@@ -83,6 +84,11 @@ impl<'a> ParserContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Keyword::VIEW => {
|
||||
let _ = self.parser.next_token();
|
||||
self.parse_create_view(false)
|
||||
}
|
||||
|
||||
Keyword::NoKeyword => {
|
||||
let _ = self.parser.next_token();
|
||||
let uppercase = w.value.to_uppercase();
|
||||
@@ -91,13 +97,31 @@ impl<'a> ParserContext<'a> {
|
||||
_ => self.unsupported(w.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
_ => self.unsupported(w.to_string()),
|
||||
},
|
||||
unexpected => self.unsupported(unexpected.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `CREAVE VIEW` statement.
|
||||
fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement> {
|
||||
let if_not_exists = self.parse_if_not_exist()?;
|
||||
let view_name = self.intern_parse_table_name()?;
|
||||
|
||||
self.parser
|
||||
.expect_keyword(Keyword::AS)
|
||||
.context(SyntaxSnafu)?;
|
||||
|
||||
let query = self.parse_query()?;
|
||||
|
||||
Ok(Statement::CreateView(CreateView {
|
||||
name: view_name,
|
||||
or_replace,
|
||||
query: Box::new(query),
|
||||
if_not_exists,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_create_external_table(&mut self) -> Result<Statement> {
|
||||
let _ = self.parser.next_token();
|
||||
self.parser
|
||||
@@ -1770,4 +1794,46 @@ non TIMESTAMP(6) TIME INDEX,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_create_view() {
|
||||
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(), sql);
|
||||
assert!(!c.or_replace);
|
||||
assert!(!c.if_not_exists);
|
||||
assert_eq!("test", c.name.to_string());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS 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(), sql);
|
||||
assert!(c.or_replace);
|
||||
assert!(c.if_not_exists);
|
||||
assert_eq!("test", c.name.to_string());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_create_view_invalid_query() {
|
||||
let sql = "CREATE VIEW test AS DELETE from demo";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
|
||||
assert!(result.is_err());
|
||||
assert_matches!(result, Err(crate::error::Error::Syntax { .. }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use sqlparser::ast::{Expr, Query};
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::ast::{ColumnDef, Ident, ObjectName, TableConstraint, Value as SqlValue};
|
||||
use crate::statements::statement::Statement;
|
||||
use crate::statements::OptionMap;
|
||||
|
||||
const LINE_SEP: &str = ",\n";
|
||||
@@ -284,6 +285,35 @@ impl Display for CreateFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create SQL view statement.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
|
||||
pub struct CreateView {
|
||||
/// View name
|
||||
pub name: ObjectName,
|
||||
/// The clause after `As` that defines the VIEW.
|
||||
/// Can only be either [Statement::Query] or [Statement::Tql].
|
||||
pub query: Box<Statement>,
|
||||
/// Whether to replace existing VIEW
|
||||
pub or_replace: bool,
|
||||
/// Create VIEW only when it doesn't exists
|
||||
pub if_not_exists: bool,
|
||||
}
|
||||
|
||||
impl Display for CreateView {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "CREATE ")?;
|
||||
if self.or_replace {
|
||||
write!(f, "OR REPLACE ")?;
|
||||
}
|
||||
write!(f, "VIEW ")?;
|
||||
if self.if_not_exists {
|
||||
write!(f, "IF NOT EXISTS ")?;
|
||||
}
|
||||
write!(f, "{} ", &self.name)?;
|
||||
write!(f, "AS {}", &self.query)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
@@ -21,7 +21,7 @@ use sqlparser_derive::{Visit, VisitMut};
|
||||
use crate::error::{ConvertToDfStatementSnafu, Error};
|
||||
use crate::statements::alter::AlterTable;
|
||||
use crate::statements::create::{
|
||||
CreateDatabase, CreateExternalTable, CreateFlow, CreateTable, CreateTableLike,
|
||||
CreateDatabase, CreateExternalTable, CreateFlow, CreateTable, CreateTableLike, CreateView,
|
||||
};
|
||||
use crate::statements::delete::Delete;
|
||||
use crate::statements::describe::DescribeTable;
|
||||
@@ -56,6 +56,8 @@ pub enum Statement {
|
||||
CreateFlow(CreateFlow),
|
||||
// DROP FLOW
|
||||
DropFlow(DropFlow),
|
||||
// CREATE VIEW ... AS
|
||||
CreateView(CreateView),
|
||||
// DROP TABLE
|
||||
DropTable(DropTable),
|
||||
// DROP DATABASE
|
||||
@@ -126,6 +128,7 @@ impl Display for Statement {
|
||||
Statement::ShowCollation(kind) => {
|
||||
write!(f, "SHOW COLLATION {kind}")
|
||||
}
|
||||
Statement::CreateView(s) => s.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user