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:
dennis zhuang
2024-05-14 16:03:29 +08:00
committed by GitHub
parent f16ce3ca27
commit efd3f04b7c
36 changed files with 1966 additions and 116 deletions

View File

@@ -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 { .. }));
}
}

View File

@@ -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;

View File

@@ -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),
}
}
}