sqry_db/queries/
callers.rs1use std::sync::Arc;
15
16use sqry_core::graph::unified::concurrent::GraphSnapshot;
17use sqry_core::graph::unified::node::id::NodeId;
18
19use crate::QueryDb;
20use crate::query::DerivedQuery;
21
22use super::relation::{RelationKey, RelationKind, compute_relation_source_set};
23
24pub struct CallersQuery;
31
32impl DerivedQuery for CallersQuery {
33 type Key = RelationKey;
34 type Value = Arc<Vec<NodeId>>;
35 const QUERY_TYPE_ID: u32 = crate::queries::type_ids::CALLERS;
36 const TRACKS_EDGE_REVISION: bool = true;
37
38 fn execute(key: &RelationKey, _db: &QueryDb, snapshot: &GraphSnapshot) -> Arc<Vec<NodeId>> {
39 compute_relation_source_set(RelationKind::Callers, key, snapshot)
40 }
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::QueryDbConfig;
47 use sqry_core::graph::unified::concurrent::CodeGraph;
48 use sqry_core::graph::unified::edge::kind::EdgeKind;
49 use sqry_core::graph::unified::node::kind::NodeKind;
50 use sqry_core::graph::unified::storage::arena::NodeEntry;
51 use std::path::Path;
52 use std::sync::Arc;
53
54 #[test]
55 fn callers_query_matches_planner_semantics() {
56 let mut graph = CodeGraph::new();
59 let file = graph.files_mut().register(Path::new("lib.rs")).unwrap();
60 let main_name = graph.strings_mut().intern("main").unwrap();
61 let target_name = graph.strings_mut().intern("target").unwrap();
62 let unrelated_name = graph.strings_mut().intern("unrelated").unwrap();
63
64 let main_fn = graph
65 .nodes_mut()
66 .alloc(
67 NodeEntry::new(NodeKind::Function, main_name, file).with_qualified_name(main_name),
68 )
69 .unwrap();
70 let target = graph
71 .nodes_mut()
72 .alloc(
73 NodeEntry::new(NodeKind::Function, target_name, file)
74 .with_qualified_name(target_name),
75 )
76 .unwrap();
77 let unrelated = graph
78 .nodes_mut()
79 .alloc(
80 NodeEntry::new(NodeKind::Function, unrelated_name, file)
81 .with_qualified_name(unrelated_name),
82 )
83 .unwrap();
84
85 graph.edges_mut().add_edge(
86 main_fn,
87 target,
88 EdgeKind::Calls {
89 argument_count: 0,
90 is_async: false,
91 },
92 file,
93 );
94
95 let snapshot = Arc::new(graph.snapshot());
96 let mut db = QueryDb::new(Arc::clone(&snapshot), QueryDbConfig::default());
97 db.register::<CallersQuery>();
98
99 let matches = db.get::<CallersQuery>(&RelationKey::exact("main"));
100 assert!(matches.contains(&target));
101 assert!(!matches.contains(&main_fn));
102 assert!(!matches.contains(&unrelated));
103 }
104
105 #[test]
106 fn callers_query_dynamic_language_method_segment_fallback() {
107 let mut graph = CodeGraph::new();
112 let file = graph.files_mut().register(Path::new("game.rb")).unwrap();
113 assert!(
114 graph
115 .files_mut()
116 .set_language(file, sqry_core::graph::node::Language::Ruby)
117 );
118
119 let caller_name = graph.strings_mut().intern("Game::update").unwrap();
120 let callee_name = graph.strings_mut().intern("Enemy::takeDamage").unwrap();
121
122 let caller = graph
123 .nodes_mut()
124 .alloc(
125 NodeEntry::new(NodeKind::Method, caller_name, file)
126 .with_qualified_name(caller_name),
127 )
128 .unwrap();
129 let callee = graph
130 .nodes_mut()
131 .alloc(
132 NodeEntry::new(NodeKind::Method, callee_name, file)
133 .with_qualified_name(callee_name),
134 )
135 .unwrap();
136 graph.edges_mut().add_edge(
137 caller,
138 callee,
139 EdgeKind::Calls {
140 argument_count: 0,
141 is_async: false,
142 },
143 file,
144 );
145
146 let snapshot = Arc::new(graph.snapshot());
147 let matches = compute_relation_source_set(
159 RelationKind::Callees,
160 &RelationKey::exact("Player::takeDamage"),
161 &snapshot,
162 );
163 assert!(
164 matches.contains(&caller),
165 "Game::update calls Enemy::takeDamage, whose trailing segment \
166 `takeDamage` matches `Player::takeDamage` under the dynamic \
167 fallback."
168 );
169 }
170}