use std::path::Path;
use std::sync::Arc;
use sqry_core::graph::Language;
use sqry_core::graph::unified::concurrent::{CodeGraph, GraphSnapshot};
use sqry_core::graph::unified::node::id::NodeId;
use sqry_core::graph::unified::node::kind::NodeKind;
use sqry_core::graph::unified::storage::arena::NodeEntry;
use sqry_db::planner::{execute_plan, parse_query};
use sqry_db::{QueryDb, QueryDbConfig};
fn add_node(graph: &mut CodeGraph, entry: NodeEntry) -> NodeId {
let id = graph.nodes_mut().alloc(entry.clone()).expect("alloc node");
graph
.indices_mut()
.add(id, entry.kind, entry.name, entry.qualified_name, entry.file);
id
}
fn planner_name_set(db: &QueryDb, name: &str) -> Vec<NodeId> {
let plan = parse_query(&format!("name:{name}"))
.unwrap_or_else(|err| panic!("parse_query(\"name:{name}\") failed: {err}"));
execute_plan(&plan, db)
}
fn cli_exact_set(snapshot: &GraphSnapshot, name: &str) -> Vec<NodeId> {
let mut ids = snapshot.find_by_exact_name(name);
ids.sort_unstable_by_key(|id| (id.index(), id.generation()));
ids.dedup();
ids
}
fn make_db(graph: CodeGraph) -> (Arc<GraphSnapshot>, QueryDb) {
let snapshot = Arc::new(graph.snapshot());
let db = QueryDb::new(Arc::clone(&snapshot), QueryDbConfig::default());
(snapshot, db)
}
#[test]
fn name_predicate_aligns_with_exact_for_go_badliveware_field_fixture() {
let mut graph = CodeGraph::new();
let file = graph
.files_mut()
.register_with_language(Path::new("main.go"), Some(Language::Go))
.expect("register go file");
let property_qname = graph
.strings_mut()
.intern("main.SelectorSource.NeedTags")
.expect("intern property qname");
let property_simple = graph
.strings_mut()
.intern("NeedTags")
.expect("intern property simple");
let local_var_name = graph
.strings_mut()
.intern("NeedTags")
.expect("intern local var");
let synthetic_field_name = graph
.strings_mut()
.intern("<field:selector.NeedTags>")
.expect("intern synthetic field");
let synthetic_offset_name = graph
.strings_mut()
.intern("NeedTags@469")
.expect("intern synthetic offset");
let unrelated_name = graph
.strings_mut()
.intern("NeedTagsHelper")
.expect("intern unrelated");
let property_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Property, property_simple, file)
.with_byte_range(10, 80)
.with_qualified_name(property_qname),
);
let local_var_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, local_var_name, file).with_byte_range(100, 160),
);
let syn_field_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, synthetic_field_name, file).with_byte_range(200, 260),
);
let syn_offset_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, synthetic_offset_name, file).with_byte_range(300, 360),
);
let _unrelated_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, unrelated_name, file).with_byte_range(400, 460),
);
graph.macro_metadata_mut().mark_synthetic(syn_offset_id);
let (snapshot, db) = make_db(graph);
let planner = planner_name_set(&db, "NeedTags");
let cli = cli_exact_set(&snapshot, "NeedTags");
assert_eq!(
planner, cli,
"planner `name:NeedTags` and CLI `--exact NeedTags` must return the same set"
);
assert!(
planner.contains(&property_id),
"Property `main.SelectorSource.NeedTags` must surface (qualified-name match)"
);
assert!(
planner.contains(&local_var_id),
"Variable `NeedTags` must surface (simple-name match)"
);
assert!(
!planner.contains(&syn_field_id),
"synthetic `<field:selector.NeedTags>` must not surface"
);
assert!(
!planner.contains(&syn_offset_id),
"synthetic `NeedTags@469` must not surface"
);
}
#[test]
fn name_predicate_aligns_with_exact_for_rust_function_fixture() {
let mut graph = CodeGraph::new();
let file = graph
.files_mut()
.register_with_language(Path::new("src/lib.rs"), Some(Language::Rust))
.expect("register rust file");
let parse_expr_name = graph.strings_mut().intern("parse_expr").expect("intern");
let parse_expr_qname = graph
.strings_mut()
.intern("my_crate::parser::parse_expr")
.expect("intern qname");
let parse_stmt_name = graph.strings_mut().intern("parse_stmt").expect("intern");
let synthetic_at_offset = graph
.strings_mut()
.intern("parse_expr@128")
.expect("intern synthetic");
let target_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, parse_expr_name, file)
.with_byte_range(10, 80)
.with_qualified_name(parse_expr_qname),
);
let _other_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, parse_stmt_name, file).with_byte_range(100, 160),
);
let syn_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, synthetic_at_offset, file).with_byte_range(200, 260),
);
graph.macro_metadata_mut().mark_synthetic(syn_id);
let (snapshot, db) = make_db(graph);
assert_eq!(
planner_name_set(&db, "parse_expr"),
cli_exact_set(&snapshot, "parse_expr"),
"Rust function: planner and CLI must agree on simple-name lookup"
);
assert_eq!(planner_name_set(&db, "parse_expr"), vec![target_id]);
assert_eq!(
planner_name_set(&db, "my_crate::parser::parse_expr"),
cli_exact_set(&snapshot, "my_crate::parser::parse_expr"),
"Rust function: planner and CLI must agree on qualified-name lookup"
);
assert!(
!planner_name_set(&db, "parse_expr").contains(&syn_id),
"synthetic `parse_expr@128` must not surface for `name:parse_expr`"
);
assert!(planner_name_set(&db, "parse_expr@128").is_empty());
}
#[test]
fn name_predicate_aligns_with_exact_for_python_class_fixture() {
let mut graph = CodeGraph::new();
let file = graph
.files_mut()
.register_with_language(Path::new("visitor.py"), Some(Language::Python))
.expect("register python file");
let visitor_name = graph.strings_mut().intern("Visitor").expect("intern");
let visitor_qname = graph
.strings_mut()
.intern("ast.visitor.Visitor")
.expect("intern qname");
let other_name = graph.strings_mut().intern("AsyncVisitor").expect("intern");
let synthetic_field_name = graph
.strings_mut()
.intern("<field:node.Visitor>")
.expect("intern synthetic");
let target_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Class, visitor_name, file)
.with_byte_range(10, 80)
.with_qualified_name(visitor_qname),
);
let _other_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Class, other_name, file).with_byte_range(100, 160),
);
let syn_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, synthetic_field_name, file).with_byte_range(200, 260),
);
let (snapshot, db) = make_db(graph);
assert_eq!(
planner_name_set(&db, "Visitor"),
cli_exact_set(&snapshot, "Visitor"),
"Python class: planner and CLI must agree on simple-name lookup"
);
assert_eq!(planner_name_set(&db, "Visitor"), vec![target_id]);
assert_eq!(
planner_name_set(&db, "ast.visitor.Visitor"),
cli_exact_set(&snapshot, "ast.visitor.Visitor"),
"Python class: planner and CLI must agree on qualified-name lookup"
);
assert!(!planner_name_set(&db, "Visitor").contains(&_other_id));
assert!(!planner_name_set(&db, "Visitor").contains(&syn_id));
}
#[test]
fn name_predicate_excludes_synthetic_nodes_via_metadata_bit_only() {
let mut graph = CodeGraph::new();
let file = graph
.files_mut()
.register_with_language(Path::new("main.go"), Some(Language::Go))
.expect("register go file");
let foo_name = graph.strings_mut().intern("Foo").expect("intern");
let real_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, foo_name, file).with_byte_range(10, 80),
);
let synthetic_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, foo_name, file).with_byte_range(100, 160),
);
graph.macro_metadata_mut().mark_synthetic(synthetic_id);
let (snapshot, db) = make_db(graph);
let planner = planner_name_set(&db, "Foo");
let cli = cli_exact_set(&snapshot, "Foo");
assert_eq!(
planner, cli,
"planner and CLI must agree on synthetic exclusion"
);
assert_eq!(
planner,
vec![real_id],
"metadata-flagged synthetic must be invisible to `name:` even with a natural-shaped name"
);
assert!(!planner.contains(&synthetic_id));
}
#[test]
fn name_predicate_excludes_synthetic_nodes_via_structural_shape_only() {
let mut graph = CodeGraph::new();
let file = graph
.files_mut()
.register_with_language(Path::new("main.go"), Some(Language::Go))
.expect("register go file");
let bar_name = graph.strings_mut().intern("Bar").expect("intern");
let field_shape = graph
.strings_mut()
.intern("<field:s.Bar>")
.expect("intern field shape");
let offset_shape = graph
.strings_mut()
.intern("Bar@317")
.expect("intern offset shape");
let real_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, bar_name, file).with_byte_range(10, 80),
);
let _field_synthetic = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, field_shape, file).with_byte_range(100, 160),
);
let _offset_synthetic = add_node(
&mut graph,
NodeEntry::new(NodeKind::Variable, offset_shape, file).with_byte_range(200, 260),
);
let (snapshot, db) = make_db(graph);
assert!(snapshot.find_by_exact_name("<field:s.Bar>").is_empty());
assert!(snapshot.find_by_exact_name("Bar@317").is_empty());
let planner = planner_name_set(&db, "Bar");
let cli = cli_exact_set(&snapshot, "Bar");
assert_eq!(planner, cli);
assert_eq!(planner, vec![real_id]);
}
#[test]
fn name_predicate_disambiguates_across_languages_in_a_shared_graph() {
let mut graph = CodeGraph::new();
let py_file = graph
.files_mut()
.register_with_language(Path::new("a.py"), Some(Language::Python))
.expect("register py file");
let rs_file = graph
.files_mut()
.register_with_language(Path::new("b.rs"), Some(Language::Rust))
.expect("register rs file");
let go_file = graph
.files_mut()
.register_with_language(Path::new("c.go"), Some(Language::Go))
.expect("register go file");
let visitor_name = graph.strings_mut().intern("Visitor").expect("intern");
let py_visitor_qname = graph
.strings_mut()
.intern("ast.visitor.Visitor")
.expect("intern py qname");
let rs_visitor_qname = graph
.strings_mut()
.intern("my_crate::ast::Visitor")
.expect("intern rs qname");
let go_visitor_qname = graph
.strings_mut()
.intern("ast.Visitor")
.expect("intern go qname");
let py_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Class, visitor_name, py_file)
.with_byte_range(10, 80)
.with_qualified_name(py_visitor_qname),
);
let rs_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Trait, visitor_name, rs_file)
.with_byte_range(10, 80)
.with_qualified_name(rs_visitor_qname),
);
let go_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Interface, visitor_name, go_file)
.with_byte_range(10, 80)
.with_qualified_name(go_visitor_qname),
);
let (snapshot, db) = make_db(graph);
let planner_simple = planner_name_set(&db, "Visitor");
let cli_simple = cli_exact_set(&snapshot, "Visitor");
assert_eq!(planner_simple, cli_simple);
let mut expected = vec![py_id, rs_id, go_id];
expected.sort_unstable_by_key(|id| (id.index(), id.generation()));
assert_eq!(planner_simple, expected);
assert_eq!(
planner_name_set(&db, "my_crate::ast::Visitor"),
cli_exact_set(&snapshot, "my_crate::ast::Visitor")
);
assert_eq!(planner_name_set(&db, "my_crate::ast::Visitor"), vec![rs_id]);
assert_eq!(
planner_name_set(&db, "ast.visitor.Visitor"),
vec![py_id],
"Python qualified name must match Python only"
);
assert_eq!(
planner_name_set(&db, "ast.Visitor"),
vec![go_id],
"Go qualified name must match Go only"
);
}
#[test]
fn name_predicate_glob_meta_promotes_to_glob_match_in_planner_only() {
let mut graph = CodeGraph::new();
let file = graph
.files_mut()
.register_with_language(Path::new("src/lib.rs"), Some(Language::Rust))
.expect("register rust file");
let parse_expr_name = graph.strings_mut().intern("parse_expr").expect("intern");
let parse_stmt_name = graph.strings_mut().intern("parse_stmt").expect("intern");
let parse_block_name = graph.strings_mut().intern("parse_block").expect("intern");
let literal_glob_name = graph.strings_mut().intern("parse_*").expect("intern");
let parse_expr_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, parse_expr_name, file).with_byte_range(10, 80),
);
let parse_stmt_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, parse_stmt_name, file).with_byte_range(100, 160),
);
let parse_block_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, parse_block_name, file).with_byte_range(200, 260),
);
let literal_glob_id = add_node(
&mut graph,
NodeEntry::new(NodeKind::Function, literal_glob_name, file).with_byte_range(300, 360),
);
let (snapshot, db) = make_db(graph);
let planner_literal = planner_name_set(&db, "parse_expr");
let cli_literal = cli_exact_set(&snapshot, "parse_expr");
assert_eq!(
planner_literal, cli_literal,
"literal `name:parse_expr` must equal `--exact parse_expr`"
);
assert_eq!(planner_literal, vec![parse_expr_id]);
let planner_glob = planner_name_set(&db, "parse_*");
let mut expected_glob = vec![
parse_expr_id,
parse_stmt_id,
parse_block_id,
literal_glob_id,
];
expected_glob.sort_unstable_by_key(|id| (id.index(), id.generation()));
assert_eq!(
planner_glob, expected_glob,
"planner glob `name:parse_*` must match every `parse_`-prefixed name"
);
let cli_glob_value = cli_exact_set(&snapshot, "parse_*");
assert_eq!(
cli_glob_value,
vec![literal_glob_id],
"`--exact parse_*` is literal-only — must match only the literal `parse_*` node, never `parse_expr` etc."
);
assert_ne!(
planner_glob, cli_glob_value,
"glob and exact paths intentionally diverge — the contract holds for literals only"
);
let mut graph2 = CodeGraph::new();
let file2 = graph2
.files_mut()
.register_with_language(Path::new("src/lib.rs"), Some(Language::Rust))
.expect("register rust file");
let parse_expr_name2 = graph2.strings_mut().intern("parse_expr").expect("intern");
let bit_synthetic_name = graph2.strings_mut().intern("parse_inner").expect("intern");
let shape_synthetic_name = graph2
.strings_mut()
.intern("parse_helper@921")
.expect("intern structural-shape simple name");
let parse_id2 = add_node(
&mut graph2,
NodeEntry::new(NodeKind::Function, parse_expr_name2, file2).with_byte_range(10, 80),
);
let bit_synthetic_id = add_node(
&mut graph2,
NodeEntry::new(NodeKind::Variable, bit_synthetic_name, file2).with_byte_range(100, 160),
);
let _shape_synthetic_id = add_node(
&mut graph2,
NodeEntry::new(NodeKind::Variable, shape_synthetic_name, file2).with_byte_range(200, 260),
);
graph2.macro_metadata_mut().mark_synthetic(bit_synthetic_id);
let (_snap2, db2) = make_db(graph2);
assert_eq!(
planner_name_set(&db2, "parse_*"),
vec![parse_id2],
"synthetic placeholders must not surface through the glob path \
(would fail without `is_node_synthetic` because both synthetic \
simple names match the glob)"
);
}