mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-06-03 05:40:40 +00:00
feat: Implement SHOW CREATE FLOW (#4040)
* feat: Implement SHOW CREATE FLOW * fmt * stmt for display * Update src/operator/src/statement.rs Co-authored-by: Yingwen <realevenyag@gmail.com> * test: add sqlness test * fix test * parse query in parser * test: move test to standalone * reuse ParserContext::new() * Update tests/cases/standalone/show_create_flow.result Co-authored-by: Weny Xu <wenymedia@gmail.com> * add line breaks --------- Co-authored-by: Yingwen <realevenyag@gmail.com> Co-authored-by: Weny Xu <wenymedia@gmail.com>
This commit is contained in:
@@ -141,6 +141,9 @@ pub enum Error {
|
||||
#[snafu(display("Invalid table name: {}", name))]
|
||||
InvalidTableName { name: String },
|
||||
|
||||
#[snafu(display("Invalid flow name: {}", name))]
|
||||
InvalidFlowName { name: String },
|
||||
|
||||
#[snafu(display("Invalid default constraint, column: {}", column))]
|
||||
InvalidDefault {
|
||||
column: String,
|
||||
@@ -274,6 +277,7 @@ impl ErrorExt for Error {
|
||||
| InvalidDatabaseOption { .. }
|
||||
| ColumnTypeMismatch { .. }
|
||||
| InvalidTableName { .. }
|
||||
| InvalidFlowName { .. }
|
||||
| InvalidSqlValue { .. }
|
||||
| TimestampOverflow { .. }
|
||||
| InvalidTableOption { .. }
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use snafu::ResultExt;
|
||||
use sqlparser::ast::Ident;
|
||||
use sqlparser::ast::{Ident, Query};
|
||||
use sqlparser::dialect::Dialect;
|
||||
use sqlparser::keywords::Keyword;
|
||||
use sqlparser::parser::{Parser, ParserError, ParserOptions};
|
||||
@@ -38,6 +38,21 @@ pub struct ParserContext<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ParserContext<'a> {
|
||||
/// Construct a new ParserContext.
|
||||
pub fn new(dialect: &'a dyn Dialect, sql: &'a str) -> Result<ParserContext<'a>> {
|
||||
let parser = Parser::new(dialect)
|
||||
.with_options(ParserOptions::new().with_trailing_commas(true))
|
||||
.try_with_sql(sql)
|
||||
.context(SyntaxSnafu)?;
|
||||
|
||||
Ok(ParserContext { parser, sql })
|
||||
}
|
||||
|
||||
/// Parses parser context to Query.
|
||||
pub fn parser_query(&mut self) -> Result<Box<Query>> {
|
||||
Ok(Box::new(self.parser.parse_query().context(SyntaxSnafu)?))
|
||||
}
|
||||
|
||||
/// Parses SQL with given dialect
|
||||
pub fn create_with_dialect(
|
||||
sql: &'a str,
|
||||
@@ -46,11 +61,7 @@ impl<'a> ParserContext<'a> {
|
||||
) -> Result<Vec<Statement>> {
|
||||
let mut stmts: Vec<Statement> = Vec::new();
|
||||
|
||||
let parser = Parser::new(dialect)
|
||||
.with_options(ParserOptions::new().with_trailing_commas(true))
|
||||
.try_with_sql(sql)
|
||||
.context(SyntaxSnafu)?;
|
||||
let mut parser_ctx = ParserContext { sql, parser };
|
||||
let mut parser_ctx = ParserContext::new(dialect, sql)?;
|
||||
|
||||
let mut expecting_statement_delimiter = false;
|
||||
loop {
|
||||
|
||||
@@ -16,11 +16,13 @@ use snafu::{ensure, ResultExt};
|
||||
use sqlparser::keywords::Keyword;
|
||||
use sqlparser::tokenizer::Token;
|
||||
|
||||
use crate::error::{self, InvalidDatabaseNameSnafu, InvalidTableNameSnafu, Result};
|
||||
use crate::error::{
|
||||
self, InvalidDatabaseNameSnafu, InvalidFlowNameSnafu, InvalidTableNameSnafu, Result,
|
||||
};
|
||||
use crate::parser::ParserContext;
|
||||
use crate::statements::show::{
|
||||
ShowColumns, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowStatus, ShowTables,
|
||||
ShowVariables,
|
||||
ShowColumns, ShowCreateFlow, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowStatus,
|
||||
ShowTables, ShowVariables,
|
||||
};
|
||||
use crate::statements::statement::Statement;
|
||||
|
||||
@@ -62,6 +64,8 @@ impl<'a> ParserContext<'a> {
|
||||
} else if self.consume_token("CREATE") {
|
||||
if self.consume_token("TABLE") {
|
||||
self.parse_show_create_table()
|
||||
} else if self.consume_token("FLOW") {
|
||||
self.parse_show_create_flow()
|
||||
} else {
|
||||
self.unsupported(self.peek_token_as_string())
|
||||
}
|
||||
@@ -109,6 +113,24 @@ impl<'a> ParserContext<'a> {
|
||||
Ok(Statement::ShowCreateTable(ShowCreateTable { table_name }))
|
||||
}
|
||||
|
||||
fn parse_show_create_flow(&mut self) -> Result<Statement> {
|
||||
let raw_flow_name = self
|
||||
.parse_object_name()
|
||||
.with_context(|_| error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "a flow name",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
let flow_name = Self::canonicalize_object_name(raw_flow_name);
|
||||
ensure!(
|
||||
!flow_name.0.is_empty(),
|
||||
InvalidFlowNameSnafu {
|
||||
name: flow_name.to_string(),
|
||||
}
|
||||
);
|
||||
Ok(Statement::ShowCreateFlow(ShowCreateFlow { flow_name }))
|
||||
}
|
||||
|
||||
fn parse_show_table_name(&mut self) -> Result<String> {
|
||||
self.parser.next_token();
|
||||
let table_name = self
|
||||
|
||||
@@ -269,17 +269,17 @@ impl Display for CreateFlow {
|
||||
if self.or_replace {
|
||||
write!(f, "OR REPLACE ")?;
|
||||
}
|
||||
write!(f, "TASK ")?;
|
||||
write!(f, "FLOW ")?;
|
||||
if self.if_not_exists {
|
||||
write!(f, "IF NOT EXISTS ")?;
|
||||
}
|
||||
write!(f, "{} ", &self.flow_name)?;
|
||||
write!(f, "OUTPUT AS {} ", &self.sink_table_name)?;
|
||||
writeln!(f, "{}", &self.flow_name)?;
|
||||
writeln!(f, "SINK TO {}", &self.sink_table_name)?;
|
||||
if let Some(expire_after) = &self.expire_after {
|
||||
write!(f, "EXPIRE AFTER {} ", expire_after)?;
|
||||
writeln!(f, "EXPIRE AFTER {} ", expire_after)?;
|
||||
}
|
||||
if let Some(comment) = &self.comment {
|
||||
write!(f, "COMMENT '{}' ", comment)?;
|
||||
writeln!(f, "COMMENT '{}'", comment)?;
|
||||
}
|
||||
write!(f, "AS {}", &self.query)
|
||||
}
|
||||
@@ -604,4 +604,37 @@ WITH(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_create_flow() {
|
||||
let sql = r"CREATE FLOW filter_numbers
|
||||
SINK TO out_num_cnt
|
||||
AS SELECT number FROM numbers_input where number > 10;";
|
||||
let result =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
|
||||
.unwrap();
|
||||
assert_eq!(1, result.len());
|
||||
|
||||
match &result[0] {
|
||||
Statement::CreateFlow(c) => {
|
||||
let new_sql = format!("\n{}", c);
|
||||
assert_eq!(
|
||||
r#"
|
||||
CREATE FLOW filter_numbers
|
||||
SINK TO out_num_cnt
|
||||
AS SELECT number FROM numbers_input WHERE number > 10"#,
|
||||
&new_sql
|
||||
);
|
||||
|
||||
let new_result = ParserContext::create_with_dialect(
|
||||
&new_sql,
|
||||
&GreptimeDbDialect {},
|
||||
ParseOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result, new_result);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,19 @@ impl Display for ShowCreateTable {
|
||||
}
|
||||
}
|
||||
|
||||
/// SQL structure for `SHOW CREATE FLOW`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
|
||||
pub struct ShowCreateFlow {
|
||||
pub flow_name: ObjectName,
|
||||
}
|
||||
|
||||
impl Display for ShowCreateFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let flow_name = &self.flow_name;
|
||||
write!(f, "SHOW CREATE FLOW {flow_name}")
|
||||
}
|
||||
}
|
||||
|
||||
/// SQL structure for `SHOW VARIABLES xxx`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
|
||||
pub struct ShowVariables {
|
||||
@@ -241,6 +254,35 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_create_flow() {
|
||||
let sql = "SHOW CREATE FLOW test";
|
||||
let stmts: Vec<Statement> =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
|
||||
.unwrap();
|
||||
assert_eq!(1, stmts.len());
|
||||
assert_matches!(&stmts[0], Statement::ShowCreateFlow { .. });
|
||||
match &stmts[0] {
|
||||
Statement::ShowCreateFlow(show) => {
|
||||
let flow_name = show.flow_name.to_string();
|
||||
assert_eq!(flow_name, "test");
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
pub fn test_show_create_missing_flow() {
|
||||
let sql = "SHOW CREATE FLOW";
|
||||
assert!(ParserContext::create_with_dialect(
|
||||
sql,
|
||||
&GreptimeDbDialect {},
|
||||
ParseOptions::default()
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_show_variables() {
|
||||
let sql = r"show variables v1;";
|
||||
|
||||
@@ -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, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowStatus, ShowTables,
|
||||
ShowVariables,
|
||||
ShowColumns, ShowCreateFlow, ShowCreateTable, ShowDatabases, ShowIndex, ShowKind, ShowStatus,
|
||||
ShowTables, ShowVariables,
|
||||
};
|
||||
use crate::statements::tql::Tql;
|
||||
use crate::statements::truncate::TruncateTable;
|
||||
@@ -81,6 +81,8 @@ pub enum Statement {
|
||||
ShowIndex(ShowIndex),
|
||||
// SHOW CREATE TABLE
|
||||
ShowCreateTable(ShowCreateTable),
|
||||
// SHOW CREATE FLOW
|
||||
ShowCreateFlow(ShowCreateFlow),
|
||||
// SHOW STATUS
|
||||
ShowStatus(ShowStatus),
|
||||
// DESCRIBE TABLE
|
||||
@@ -118,6 +120,7 @@ impl Display for Statement {
|
||||
Statement::ShowColumns(s) => s.fmt(f),
|
||||
Statement::ShowIndex(s) => s.fmt(f),
|
||||
Statement::ShowCreateTable(s) => s.fmt(f),
|
||||
Statement::ShowCreateFlow(s) => s.fmt(f),
|
||||
Statement::ShowStatus(s) => s.fmt(f),
|
||||
Statement::DescribeTable(s) => s.fmt(f),
|
||||
Statement::Explain(s) => s.fmt(f),
|
||||
|
||||
Reference in New Issue
Block a user