@@ -104,23 +104,27 @@ ON DUPLICATE KEY UPDATE description = 'SurrealDB functions for graph analysis';
104104-- =============================================================================
105105-- Helper Functions
106106-- =============================================================================
107+ -- Optional but recommended: index to speed edge-type filtering
108+ DEFINE INDEX idx_edges_edge_type ON TABLE edges COLUMNS edge_type;
109+
110+ -- ===== Node info helper (fixed) =====
107111DEFINE FUNCTION fn::node_info($node_id: string) {
108- RETURN (
109- SELECT
110- id ,
111- name ,
112- node_type AS kind ,
113- language ,
114- content ,
115- metadata,
116- {
117- file_path ,
118- start_line,
119- end_line
120- } AS location
121- FROM ONLY $node_id
122- LIMIT 1
123- ) [0];
112+ LET $res = SELECT
113+ id,
114+ name ,
115+ node_type AS kind ,
116+ language ,
117+ content ,
118+ metadata ,
119+ {
120+ file_path: file_path,
121+ start_line: start_line ,
122+ end_line: end_line
123+ } AS location
124+ FROM ONLY $node_id
125+ LIMIT 1;
126+
127+ RETURN $res [0];
124128};
125129
126130DEFINE FUNCTION fn::node_reference($node_id: string) {
@@ -225,23 +229,34 @@ DEFINE FUNCTION fn::get_transitive_dependencies($node_id: string, $edge_type: st
225229DEFINE FUNCTION fn::detect_circular_dependencies($edge_type: string) {
226230 LET $edge_name = $edge_type ?? 'Calls';
227231
232+ -- Candidate directed pairs (exclude self-loops early)
228233 LET $pairs = (
229- SELECT from AS node1_id, to AS node2_id
234+ SELECT
235+ from AS node1_id,
236+ to AS node2_id
230237 FROM edges
231238 WHERE edge_type = $edge_name
239+ AND from != to
232240 );
233241
242+ -- Keep undirected pairs once (node1_id < node2_id) that have the reverse edge
234243 LET $cycles = (
235- SELECT DISTINCT node1_id, node2_id
244+ SELECT
245+ node1_id,
246+ node2_id
236247 FROM $pairs
237- WHERE node1_id != node2_id
238- AND node1_id < node2_id
248+ WHERE node1_id < node2_id
239249 AND (
240- SELECT count() FROM edges
241- WHERE from = node2_id AND to = node1_id AND edge_type = $edge_name
242- ) > 0
250+ SELECT VALUE count()
251+ FROM edges
252+ WHERE edge_type = $edge_name
253+ AND from = node2_id
254+ AND to = node1_id
255+ ) > 0
256+ GROUP BY node1_id, node2_id
243257 );
244258
259+ -- Enrich with node info
245260 LET $raw = (
246261 SELECT
247262 node1_id,
@@ -251,6 +266,7 @@ DEFINE FUNCTION fn::detect_circular_dependencies($edge_type: string) {
251266 FROM $cycles
252267 );
253268
269+ -- Final shape, filter missing nodes defensively
254270 RETURN SELECT
255271 node1_id,
256272 node2_id,
@@ -350,60 +366,70 @@ DEFINE FUNCTION fn::get_hub_nodes($min_degree: int) {
350366 LET $threshold = IF $min_degree != NONE AND $min_degree > 0 THEN $min_degree ELSE 5 END;
351367 LET $edge_list = fn::edge_types();
352368
353- LET $incoming = (
354- SELECT to AS node_id, edge_type, count() AS count
369+ LET $incoming_by_type = (
370+ SELECT
371+ to AS node_id,
372+ edge_type,
373+ count() AS count
355374 FROM edges
356375 WHERE edge_type INSIDE $edge_list
357376 GROUP BY to, edge_type
358377 );
359378
360- LET $outgoing = (
361- SELECT from AS node_id, edge_type, count() AS count
379+ LET $outgoing_by_type = (
380+ SELECT
381+ from AS node_id,
382+ edge_type,
383+ count() AS count
362384 FROM edges
363385 WHERE edge_type INSIDE $edge_list
364386 GROUP BY from, edge_type
365387 );
366388
367389 LET $incoming_totals = (
368- SELECT node_id, math::sum(count) AS total
369- FROM $incoming
370- GROUP BY node_id
390+ SELECT
391+ to AS node_id,
392+ count() AS total
393+ FROM edges
394+ WHERE edge_type INSIDE $edge_list
395+ GROUP BY to
371396 );
372397
373398 LET $outgoing_totals = (
374- SELECT node_id, math::sum(count) AS total
375- FROM $outgoing
376- GROUP BY node_id
399+ SELECT
400+ from AS node_id,
401+ count() AS total
402+ FROM edges
403+ WHERE edge_type INSIDE $edge_list
404+ GROUP BY from
377405 );
378406
379- LET $candidates = (
380- SELECT DISTINCT node_id
381- FROM (
382- SELECT node_id FROM $incoming
383- UNION
384- SELECT node_id FROM $outgoing
385- )
407+ -- safest: concat then dedupe
408+ LET $candidates = array::distinct(
409+ array::concat(
410+ (SELECT VALUE node_id FROM $incoming_totals),
411+ (SELECT VALUE node_id FROM $outgoing_totals)
412+ )
386413 );
387414
388415 LET $raw = (
389416 SELECT
390417 candidate_id,
391418 fn::node_info(candidate_id) AS node,
392- ((SELECT VALUE total FROM $incoming_totals WHERE node_id = candidate_id LIMIT 1)[0] ?? 0) AS afferent_degree,
393- ((SELECT VALUE total FROM $outgoing_totals WHERE node_id = candidate_id LIMIT 1)[0] ?? 0) AS efferent_degree,
419+ (array::first (SELECT VALUE total FROM $incoming_totals WHERE node_id = candidate_id LIMIT 1) ?? 0) AS afferent_degree,
420+ (array::first (SELECT VALUE total FROM $outgoing_totals WHERE node_id = candidate_id LIMIT 1) ?? 0) AS efferent_degree,
394421 (
395422 SELECT edge_type, count
396- FROM $incoming
423+ FROM $incoming_by_type
397424 WHERE node_id = candidate_id
398425 ) AS incoming_by_type,
399426 (
400427 SELECT edge_type, count
401- FROM $outgoing
428+ FROM $outgoing_by_type
402429 WHERE node_id = candidate_id
403430 ) AS outgoing_by_type
404431 FROM (
405- SELECT node_id AS candidate_id
406- FROM $candidates
432+ SELECT node_id AS candidate_id FROM $candidates
407433 )
408434 );
409435
@@ -416,10 +442,12 @@ DEFINE FUNCTION fn::get_hub_nodes($min_degree: int) {
416442 incoming_by_type,
417443 outgoing_by_type
418444 FROM $raw
419- WHERE (afferent_degree + efferent_degree) >= $threshold
445+ WHERE node != NONE
446+ AND (afferent_degree + efferent_degree) >= $threshold
420447 ORDER BY total_degree DESC;
421448};
422449
450+
423451DEFINE FUNCTION fn::get_reverse_dependencies($node_id: string, $edge_type: string, $depth: int) {
424452 LET $safe_depth = IF $depth > 0 AND $depth <= 10 THEN $depth ELSE 3 END;
425453 LET $edge_name = $edge_type ?? 'Calls';
0 commit comments