use crate::parser::parse;
#[test]
fn test_recursive_cte_valid() {
let input = "WITH RECURSIVE tree(id, parent_id, depth) AS (\
get nodes fields id, parent_id where parent_id is null \
UNION ALL \
get nodes join tree on parent_id = id fields id, parent_id\
) get tree";
let result = parse(input);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let cmd = result.unwrap();
assert_eq!(cmd.ctes.len(), 1);
let cte = &cmd.ctes[0];
assert_eq!(cte.name, "tree");
assert!(cte.recursive);
assert_eq!(cte.columns, vec!["id", "parent_id", "depth"]);
assert!(
cte.recursive_query.is_some(),
"recursive_query must be populated"
);
assert_eq!(cte.base_query.table, "nodes");
let rq = cte.recursive_query.as_ref().unwrap();
assert_eq!(rq.table, "nodes");
assert!(!rq.joins.is_empty(), "recursive query must have a JOIN");
}
#[test]
fn test_recursive_cte_missing_union_all() {
let input = "WITH RECURSIVE tree AS (\
get nodes where parent_id is null\
) get tree";
let result = parse(input);
assert!(
result.is_err(),
"should fail: no UNION ALL in recursive CTE"
);
}
#[test]
fn test_recursive_cte_multiple_union_all() {
let input = "WITH RECURSIVE tree AS (\
get a UNION ALL get b UNION ALL get c\
) get tree";
let result = parse(input);
assert!(result.is_err(), "should fail: multiple top-level UNION ALL");
}
#[test]
fn test_recursive_cte_bare_union_rejected() {
let input = "WITH RECURSIVE tree AS (\
get nodes where parent_id is null \
UNION \
get nodes join tree on parent_id = id\
) get tree";
let result = parse(input);
assert!(result.is_err(), "should fail: bare UNION not allowed");
}
#[test]
fn test_recursive_cte_base_self_reference() {
let input = "WITH RECURSIVE tree AS (\
get tree where parent_id is null \
UNION ALL \
get nodes join tree on parent_id = id\
) get tree";
let result = parse(input);
assert!(result.is_err(), "should fail: base references CTE name");
}
#[test]
fn test_recursive_cte_no_self_reference_in_recursive() {
let input = "WITH RECURSIVE tree AS (\
get nodes where parent_id is null \
UNION ALL \
get nodes where parent_id is not null\
) get tree";
let result = parse(input);
assert!(
result.is_err(),
"should fail: recursive part doesn't reference CTE name"
);
}
#[test]
fn test_union_all_inside_quotes_ignored() {
use crate::parser::grammar::cte::split_top_level_union_all;
let body = "get t1 where name = 'UNION ALL' UNION ALL get t2";
let result = split_top_level_union_all(body);
assert!(result.is_ok(), "should find exactly one real UNION ALL");
let (base, recursive) = result.unwrap();
assert!(
base.contains("'UNION ALL'"),
"base should contain quoted UNION ALL"
);
assert!(
recursive.trim().starts_with("get"),
"recursive should be the second query"
);
}
#[test]
fn test_union_all_inside_comment_ignored() {
use crate::parser::grammar::cte::split_top_level_union_all;
let body = "get t1 -- UNION ALL this is a comment\n UNION ALL get t2";
let result = split_top_level_union_all(body);
assert!(result.is_ok(), "should find exactly one real UNION ALL");
}
#[test]
fn test_union_all_inside_block_comment_ignored() {
use crate::parser::grammar::cte::split_top_level_union_all;
let body = "get t1 /* UNION ALL inside block */ UNION ALL get t2";
let result = split_top_level_union_all(body);
assert!(result.is_ok(), "should find exactly one real UNION ALL");
}
#[test]
fn test_union_all_inside_nested_parens_ignored() {
use crate::parser::grammar::cte::split_top_level_union_all;
let body = "get t1 where id in (SELECT 1 UNION ALL SELECT 2) UNION ALL get t2";
let result = split_top_level_union_all(body);
assert!(
result.is_ok(),
"should find exactly one top-level UNION ALL"
);
let (base, _) = result.unwrap();
assert!(
base.contains("UNION ALL SELECT 2)"),
"nested UNION ALL should remain in base"
);
}
#[test]
fn test_non_recursive_cte_raw_sql_rejected() {
let input = "WITH summary AS (SELECT id, count(*) as cnt FROM orders GROUP BY id) get summary";
let result = parse(input);
assert!(result.is_err(), "non-recursive raw SQL must be rejected");
}
#[test]
fn test_non_recursive_cte_qail_valid() {
let input = "WITH summary AS (get orders fields id, total) get summary";
let result = parse(input);
assert!(
result.is_ok(),
"non-recursive QAIL CTE should parse: {:?}",
result.err()
);
let cmd = result.unwrap();
assert_eq!(cmd.ctes.len(), 1);
assert_eq!(cmd.ctes[0].name, "summary");
assert_eq!(cmd.ctes[0].base_query.table, "orders");
}
#[test]
fn test_cte_rejects_malformed_identifiers() {
for query in [
"WITH .summary AS (get orders fields id) get summary",
"WITH summary. AS (get orders fields id) get summary",
"WITH summary(.id) AS (get orders fields id) get summary",
"WITH summary(id.) AS (get orders fields id) get summary",
"WITH RECURSIVE .tree AS (get nodes UNION ALL get nodes join tree on parent_id = id) get tree",
"WITH RECURSIVE tree(.id) AS (get nodes UNION ALL get nodes join tree on parent_id = id) get tree",
] {
assert!(
parse(query).is_err(),
"malformed CTE identifier parsed: {query}"
);
}
}
#[test]
fn test_recursive_cte_with_columns() {
let input = "WITH RECURSIVE ancestors(id, name, level) AS (\
get people fields id, name where parent_id is null \
UNION ALL \
get people join ancestors on parent_id = id fields id, name\
) get ancestors";
let result = parse(input);
assert!(result.is_ok(), "failed: {:?}", result.err());
let cmd = result.unwrap();
let cte = &cmd.ctes[0];
assert_eq!(cte.columns, vec!["id", "name", "level"]);
assert!(cte.recursive_query.is_some());
}
#[test]
fn test_union_all_case_insensitive() {
use crate::parser::grammar::cte::split_top_level_union_all;
let body = "get t1 union all get t2";
let result = split_top_level_union_all(body);
assert!(result.is_ok(), "should match case-insensitive UNION ALL");
let body2 = "get t1 Union All get t2";
let result2 = split_top_level_union_all(body2);
assert!(result2.is_ok(), "should match mixed-case UNION ALL");
}
#[test]
fn test_contains_ident_case_insensitive() {
use crate::parser::grammar::cte::contains_ident_outside_quotes_comments;
assert!(contains_ident_outside_quotes_comments(
"get nodes join Tree on parent_id = id",
"tree"
));
assert!(contains_ident_outside_quotes_comments(
"get nodes join TREE on parent_id = id",
"tree"
));
assert!(!contains_ident_outside_quotes_comments(
"get nodes where label = 'tree'",
"tree"
));
assert!(!contains_ident_outside_quotes_comments("get trees", "tree"));
}