use sqry_core::graph::unified::bind::scope::arena::ScopeKind;
use sqry_core::graph::unified::edge::kind::EdgeKind;
use sqry_core::graph::unified::node::kind::NodeKind;
use sqry_core::schema::Visibility;
use sqry_db::planner::{
Direction, ParseError, PlanNode, Predicate, PredicateValue, QueryPlan, SetOperation,
StringPattern, parse_query,
};
fn parse(src: &str) -> QueryPlan {
parse_query(src).unwrap_or_else(|err| panic!("parse({src:?}) failed: {err}"))
}
fn chain_steps(plan: QueryPlan) -> Vec<PlanNode> {
let PlanNode::Chain { steps } = plan.root else {
panic!("expected Chain root");
};
steps
}
#[test]
fn kind_only_produces_single_nodescan() {
let steps = chain_steps(parse("kind:function"));
assert_eq!(steps.len(), 1);
match &steps[0] {
PlanNode::NodeScan {
kind: Some(NodeKind::Function),
visibility,
name_pattern,
} => {
assert!(visibility.is_none());
assert!(name_pattern.is_none());
}
other => panic!("unexpected root: {other:?}"),
}
}
#[test]
fn visibility_folds_into_leading_scan() {
for (vis_text, expected) in [
("public", Visibility::Public),
("private", Visibility::Private),
] {
let src = format!("kind:function visibility:{vis_text}");
let steps = chain_steps(parse(&src));
assert_eq!(steps.len(), 1, "{src}");
match &steps[0] {
PlanNode::NodeScan {
kind: Some(NodeKind::Function),
visibility: Some(vis),
..
} => assert_eq!(*vis, expected),
other => panic!("unexpected root in {src:?}: {other:?}"),
}
}
}
#[test]
fn name_pattern_folds_into_leading_scan() {
let steps = chain_steps(parse("kind:function name:parse_*"));
assert_eq!(steps.len(), 1);
match &steps[0] {
PlanNode::NodeScan {
kind: Some(NodeKind::Function),
name_pattern: Some(pat),
..
} => {
assert_eq!(pat.raw, "parse_*");
assert_eq!(pat.mode, sqry_db::planner::MatchMode::Glob);
}
other => panic!("unexpected root: {other:?}"),
}
}
#[test]
fn exact_name_pattern_is_chosen_when_no_glob_meta() {
let steps = chain_steps(parse("kind:struct name:Parser"));
assert_eq!(steps.len(), 1);
match &steps[0] {
PlanNode::NodeScan {
name_pattern: Some(pat),
..
} => {
assert_eq!(pat.raw, "Parser");
assert_eq!(pat.mode, sqry_db::planner::MatchMode::Exact);
}
other => panic!("unexpected root: {other:?}"),
}
}
#[test]
fn kind_visibility_and_name_fold_together_into_single_scan() {
let steps = chain_steps(parse("kind:function visibility:public name:parse_*"));
assert_eq!(steps.len(), 1);
match &steps[0] {
PlanNode::NodeScan {
kind: Some(NodeKind::Function),
visibility: Some(Visibility::Public),
name_pattern: Some(pat),
} => {
assert_eq!(pat.raw, "parse_*");
}
other => panic!("unexpected root: {other:?}"),
}
}
#[test]
fn has_caller_and_has_callee() {
let steps = chain_steps(parse("kind:function has:caller"));
assert!(matches!(
steps[1],
PlanNode::Filter {
predicate: Predicate::HasCaller
}
));
let steps = chain_steps(parse("kind:function has:callee"));
assert!(matches!(
steps[1],
PlanNode::Filter {
predicate: Predicate::HasCallee
}
));
}
#[test]
fn unused_is_isunused_filter() {
let steps = chain_steps(parse("kind:function unused"));
assert!(matches!(
steps[1],
PlanNode::Filter {
predicate: Predicate::IsUnused
}
));
}
#[test]
fn in_path_glob_filter() {
let steps = chain_steps(parse("kind:function in:src/api/**"));
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::InFile(path),
} => {
assert_eq!(path.as_str(), "src/api/**");
}
other => panic!("expected InFile, got {other:?}"),
}
}
#[test]
fn scope_kind_filter() {
for (text, expected) in [
("module", ScopeKind::Module),
("function", ScopeKind::Function),
("class", ScopeKind::Class),
("namespace", ScopeKind::Namespace),
("trait", ScopeKind::Trait),
("impl", ScopeKind::Impl),
] {
let src = format!("kind:function scope:{text}");
let steps = chain_steps(parse(&src));
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::InScope(kind),
} => assert_eq!(*kind, expected, "{src}"),
other => panic!("{src:?}: expected InScope, got {other:?}"),
}
}
}
#[test]
fn relation_predicates_with_pattern_values() {
type Check = fn(&Predicate) -> bool;
let cases: &[(&str, Check)] = &[
("callers", |p| matches!(p, Predicate::Callers(_))),
("callees", |p| matches!(p, Predicate::Callees(_))),
("imports", |p| matches!(p, Predicate::Imports(_))),
("exports", |p| matches!(p, Predicate::Exports(_))),
("implements", |p| matches!(p, Predicate::Implements(_))),
("impl", |p| matches!(p, Predicate::Implements(_))),
("references", |p| matches!(p, Predicate::References(_))),
];
for (keyword, check) in cases {
let src = format!("kind:function {keyword}:parse_expr");
let steps = chain_steps(parse(&src));
match &steps[1] {
PlanNode::Filter { predicate } => {
assert!(
check(predicate),
"keyword {keyword}: unexpected predicate {predicate:?}"
);
}
other => panic!("{src:?}: expected Filter, got {other:?}"),
}
}
}
#[test]
fn callers_with_subquery_produces_plannode_subquery() {
let steps = chain_steps(parse("kind:function callers:(kind:method)"));
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::Callers(PredicateValue::Subquery(inner)),
} => match inner.as_ref() {
PlanNode::Chain { steps: sub_steps } => {
assert_eq!(sub_steps.len(), 1);
assert!(matches!(
sub_steps[0],
PlanNode::NodeScan {
kind: Some(NodeKind::Method),
..
}
));
}
other => panic!("expected Chain subquery, got {other:?}"),
},
other => panic!("expected Callers(Subquery), got {other:?}"),
}
}
#[test]
fn nested_subquery_inside_outer_subquery() {
let src = "kind:function callers:(kind:method callees:(kind:function))";
let steps = chain_steps(parse(src));
let PlanNode::Filter {
predicate: Predicate::Callers(PredicateValue::Subquery(outer_sub)),
} = &steps[1]
else {
panic!("expected outer Callers(Subquery)");
};
let PlanNode::Chain { steps: outer_steps } = outer_sub.as_ref() else {
panic!("outer subquery should be a Chain");
};
let PlanNode::Filter {
predicate: Predicate::Callees(PredicateValue::Subquery(inner_sub)),
} = &outer_steps[1]
else {
panic!("expected inner Callees(Subquery)");
};
match inner_sub.as_ref() {
PlanNode::Chain { steps: inner_steps } => {
assert!(matches!(
inner_steps[0],
PlanNode::NodeScan {
kind: Some(NodeKind::Function),
..
}
));
}
other => panic!("inner subquery Chain expected, got {other:?}"),
}
}
#[test]
fn references_with_regex_and_flags() {
let plan = parse("kind:function references ~= /handle_.*/ims");
let steps = chain_steps(plan);
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::References(PredicateValue::Regex(re)),
} => {
assert_eq!(re.pattern, "handle_.*");
assert!(re.flags.case_insensitive);
assert!(re.flags.multiline);
assert!(re.flags.dot_all);
}
other => panic!("expected References(Regex), got {other:?}"),
}
}
#[test]
fn references_colon_still_accepts_pattern_form() {
let steps = chain_steps(parse("kind:function references:parse_expr"));
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::References(PredicateValue::Pattern(pat)),
} => {
assert_eq!(pat.raw, "parse_expr");
}
other => panic!("expected References(Pattern), got {other:?}"),
}
}
#[test]
fn quoted_string_value_preserves_spaces_and_escapes() {
let steps = chain_steps(parse(r#"kind:function callers:"has space""#));
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::Callers(PredicateValue::Pattern(pat)),
} => assert_eq!(pat.raw, "has space"),
other => panic!("unexpected: {other:?}"),
}
let steps = chain_steps(parse(r#"kind:function callers:"with\"quote""#));
match &steps[1] {
PlanNode::Filter {
predicate: Predicate::Callers(PredicateValue::Pattern(pat)),
} => assert_eq!(pat.raw, "with\"quote"),
other => panic!("unexpected: {other:?}"),
}
}
#[test]
fn traverse_with_every_direction_alias() {
let cases = [
("forward", Direction::Forward),
("outgoing", Direction::Forward),
("out", Direction::Forward),
("reverse", Direction::Reverse),
("incoming", Direction::Reverse),
("in", Direction::Reverse),
("both", Direction::Both),
];
for (text, expected) in cases {
let src = format!("kind:function traverse:{text}(calls,2)");
let steps = chain_steps(parse(&src));
match &steps[1] {
PlanNode::EdgeTraversal {
direction,
max_depth,
..
} => {
assert_eq!(*direction, expected, "{src}");
assert_eq!(*max_depth, 2);
}
other => panic!("{src}: expected EdgeTraversal, got {other:?}"),
}
}
}
#[test]
fn traverse_accepts_every_supported_edge_kind() {
let cases = [
(
"calls",
std::mem::discriminant(&EdgeKind::Calls {
argument_count: 0,
is_async: false,
}),
),
("references", std::mem::discriminant(&EdgeKind::References)),
(
"imports",
std::mem::discriminant(&EdgeKind::Imports {
alias: None,
is_wildcard: false,
}),
),
("implements", std::mem::discriminant(&EdgeKind::Implements)),
("inherits", std::mem::discriminant(&EdgeKind::Inherits)),
("defines", std::mem::discriminant(&EdgeKind::Defines)),
("contains", std::mem::discriminant(&EdgeKind::Contains)),
];
for (text, expected_disc) in cases {
let src = format!("kind:function traverse:forward({text},1)");
let steps = chain_steps(parse(&src));
match &steps[1] {
PlanNode::EdgeTraversal {
edge_kind: Some(kind),
..
} => {
assert_eq!(std::mem::discriminant(kind), expected_disc, "{src}");
}
other => panic!("{src}: expected EdgeTraversal with edge_kind, got {other:?}"),
}
}
}
#[test]
fn design_doc_example_roundtrips() {
let plan = parse("kind:function has:caller traverse:reverse(calls,3) in:src/api/**");
let steps = chain_steps(plan);
assert_eq!(steps.len(), 4);
assert!(matches!(
steps[0],
PlanNode::NodeScan {
kind: Some(NodeKind::Function),
..
}
));
assert!(matches!(
steps[1],
PlanNode::Filter {
predicate: Predicate::HasCaller
}
));
match &steps[2] {
PlanNode::EdgeTraversal {
direction: Direction::Reverse,
max_depth: 3,
edge_kind: Some(ek),
} => {
assert_eq!(
std::mem::discriminant(ek),
std::mem::discriminant(&EdgeKind::Calls {
argument_count: 0,
is_async: false,
})
);
}
other => panic!("unexpected step[2]: {other:?}"),
}
match &steps[3] {
PlanNode::Filter {
predicate: Predicate::InFile(path),
} => assert_eq!(path.as_str(), "src/api/**"),
other => panic!("unexpected step[3]: {other:?}"),
}
}
#[test]
fn empty_query_fails_build() {
let err = parse_query("").unwrap_err();
assert!(matches!(err, ParseError::Build(_)));
}
#[test]
fn whitespace_only_query_fails_build() {
let err = parse_query(" \n\t ").unwrap_err();
assert!(matches!(err, ParseError::Build(_)));
}
#[test]
fn unknown_step_keyword_reports_unknown_ident() {
let err = parse_query("kind:function notakeyword:foo").unwrap_err();
match err {
ParseError::UnknownIdent { kind, value, .. } => {
assert_eq!(kind, "step keyword");
assert_eq!(value, "notakeyword");
}
other => panic!("unexpected: {other:?}"),
}
}
#[test]
fn unknown_node_kind_reports_unknown_ident() {
let err = parse_query("kind:gadget").unwrap_err();
match err {
ParseError::UnknownIdent { kind, value, .. } => {
assert_eq!(kind, "node kind");
assert_eq!(value, "gadget");
}
other => panic!("unexpected: {other:?}"),
}
}
#[test]
fn unknown_scope_kind_reports_unknown_ident() {
let err = parse_query("kind:function scope:submarine").unwrap_err();
assert!(matches!(
err,
ParseError::UnknownIdent {
kind: "scope kind",
..
}
));
}
#[test]
fn unknown_edge_kind_reports_unknown_ident() {
let err = parse_query("kind:function traverse:forward(rollercoaster,2)").unwrap_err();
assert!(matches!(
err,
ParseError::UnknownIdent {
kind: "edge kind",
..
}
));
}
#[test]
fn unknown_direction_reports_unknown_ident() {
let err = parse_query("kind:function traverse:sideways(calls,2)").unwrap_err();
assert!(matches!(
err,
ParseError::UnknownIdent {
kind: "traversal direction",
..
}
));
}
#[test]
fn invalid_has_target_reports_unknown_ident() {
let err = parse_query("kind:function has:parent").unwrap_err();
assert!(matches!(err, ParseError::UnknownIdent { .. }));
}
#[test]
fn missing_colon_after_kind_errors() {
let err = parse_query("kind function").unwrap_err();
match err {
ParseError::UnexpectedChar { expected, .. } => {
assert_eq!(expected, "':' after 'kind'");
}
other => panic!("unexpected: {other:?}"),
}
}
#[test]
fn unterminated_quoted_string_errors() {
let err = parse_query(r#"kind:function callers:"unclosed"#).unwrap_err();
assert!(matches!(err, ParseError::UnexpectedEnd { .. }));
}
#[test]
fn unterminated_regex_errors() {
let err = parse_query("kind:function references ~= /unclosed").unwrap_err();
assert!(matches!(err, ParseError::UnexpectedEnd { .. }));
}
#[test]
fn unterminated_subquery_errors() {
let err = parse_query("kind:function callers:(kind:method").unwrap_err();
assert!(matches!(
err,
ParseError::UnexpectedChar { .. } | ParseError::UnexpectedEnd { .. }
));
}
#[test]
fn non_digit_depth_reports_unexpectedchar() {
let err = parse_query("kind:function traverse:forward(calls,notnumber)").unwrap_err();
assert!(matches!(err, ParseError::UnexpectedChar { .. }));
}
#[test]
fn zero_depth_surfaces_build_error() {
let err = parse_query("kind:function traverse:forward(calls,0)").unwrap_err();
assert!(matches!(err, ParseError::Build(_)));
}
#[test]
fn parsing_identical_queries_yields_equal_plans() {
let a = parse("kind:function has:caller in:src/**");
let b = parse("kind:function has:caller in:src/**");
assert_eq!(a, b);
}
#[test]
fn bar_pipe_character_is_rejected_for_now() {
let err = parse_query("kind:function | kind:method").unwrap_err();
assert!(matches!(
err,
ParseError::UnexpectedChar { .. } | ParseError::UnknownIdent { .. }
));
let _ = SetOperation::Union;
let _ = StringPattern::exact("sentinel");
}