fix(flow): support complex SQL full-query flows (#8242)

* fix(flow): skip TWE detection for complex plans

Signed-off-by: discord9 <discord9@163.com>

* chore: helper

Signed-off-by: discord9 <discord9@163.com>

* fix(flow): allow SQL CTE flow parsing

Signed-off-by: discord9 <discord9@163.com>

* fix(flow): execute unscoped SQL snapshots as SQL

Signed-off-by: discord9 <discord9@163.com>

* fix: column list

Signed-off-by: discord9 <discord9@163.com>

* refactor: per review

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
This commit is contained in:
discord9
2026-06-08 21:07:43 +08:00
committed by GitHub
parent e403133eb2
commit 1c7b4c11fc
6 changed files with 352 additions and 28 deletions

View File

@@ -410,7 +410,7 @@ impl<'a> ParserContext<'a> {
.fail();
};
if !utils::is_simple_tql_cte_query(query) {
if utils::has_tql_cte(query) && !utils::is_simple_tql_cte_query(query) {
return InvalidFlowQuerySnafu {
reason: "WITH is only supported for the simplest TQL CTE in CREATE FLOW"
.to_string(),
@@ -1962,7 +1962,7 @@ SELECT * FROM tql;
}
#[test]
fn test_parse_create_flow_with_sql_cte_is_unsupported() {
fn test_parse_create_flow_with_sql_cte_is_supported() {
let sql = r#"
CREATE FLOW f
SINK TO s
@@ -1970,11 +1970,17 @@ AS
WITH cte AS (SELECT 1) SELECT * FROM cte;
"#;
let err =
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap_err();
let msg = err.to_string();
assert!(msg.to_uppercase().contains("WITH"), "err: {msg}");
.unwrap();
assert_eq!(1, stmts.len());
let Statement::CreateFlow(create_flow) = &stmts[0] else {
panic!("unexpected stmt: {:?}", stmts[0]);
};
assert_eq!(
"WITH cte AS (SELECT 1) SELECT * FROM cte",
create_flow.query.to_string()
);
}
#[test]

View File

@@ -98,6 +98,14 @@ pub(crate) fn is_simple_tql_cte_query(query: &Query) -> bool {
reference == cte_name
}
pub(crate) fn has_tql_cte(query: &Query) -> bool {
query.hybrid_cte.as_ref().is_some_and(|with| {
with.cte_tables
.iter()
.any(|cte| matches!(cte.content, CteContent::Tql(_)))
})
}
fn has_only_hybrid_tql_cte(query: &Query) -> bool {
query
.inner

View File

@@ -146,15 +146,14 @@ impl ParserContext<'_> {
// Parse the main query
let main_query = self.parser.parse_query().context(error::SyntaxSnafu)?;
// Convert the hybrid CTEs to a standard query with hybrid metadata
let hybrid_cte = HybridCteWith {
recursive,
cte_tables: tql_cte_tables,
};
// Create a Query statement with hybrid CTE metadata
let mut query = Query::try_from(*main_query)?;
query.hybrid_cte = Some(hybrid_cte);
if !tql_cte_tables.is_empty() {
query.hybrid_cte = Some(HybridCteWith {
recursive,
cte_tables: tql_cte_tables,
});
}
query.inner.with = Some(With {
recursive,
cte_tables: sql_cte_tables,

View File

@@ -108,5 +108,11 @@ mod test {
.to_string(),
"WITH tql_cte AS (TQL EVAL (0, 100, '5s') up) SELECT * FROM tql_cte"
);
assert_eq!(
create_query("WITH sql_cte AS (SELECT 1) SELECT * FROM sql_cte")
.unwrap()
.to_string(),
"WITH sql_cte AS (SELECT 1) SELECT * FROM sql_cte"
);
}
}