3
3
from typing import List , Optional
4
4
from falkordb import FalkorDB , Node , QueryResult
5
5
6
+ FALKORDB_HOST = "localhost"
7
+ FALKORDB_PORT = 6379
8
+ FALKORDB_USERNAME = None
9
+ FALKORDB_PASSWORD = None
10
+
11
+ def list_repos () -> List [str ]:
12
+ """
13
+ List processed repositories
14
+ """
15
+
16
+ db = FalkorDB (host = FALKORDB_HOST , port = FALKORDB_PORT ,
17
+ username = FALKORDB_USERNAME , password = FALKORDB_PASSWORD )
18
+
19
+ graphs = db .list_graphs ()
20
+ print (f"graphs: { graphs } " )
21
+ graphs = [g for g in graphs if not g .endswith ('_git' )]
22
+ return graphs
23
+
6
24
class Graph ():
7
25
"""
8
26
Represents a connection to a graph database using FalkorDB.
@@ -34,6 +52,12 @@ def __init__(self, name: str, host: str = 'localhost', port: int = 6379,
34
52
except Exception :
35
53
pass
36
54
55
+ # index Function using full-text search
56
+ try :
57
+ self .g .create_node_fulltext_index ("Searchable" , "name" )
58
+ except Exception :
59
+ pass
60
+
37
61
def clone (self , clone : str ) -> "Graph" :
38
62
"""
39
63
Create a copy of the graph under the name clone
@@ -71,7 +95,7 @@ def add_class(self, c: Class) -> None:
71
95
c (Class): The Class object to be added.
72
96
"""
73
97
74
- q = """MERGE (c:Class {name: $name, path: $path, src_start: $src_start,
98
+ q = """MERGE (c:Class:Searchable {name: $name, path: $path, src_start: $src_start,
75
99
src_end: $src_end})
76
100
SET c.doc = $doc
77
101
RETURN ID(c)"""
@@ -133,7 +157,7 @@ def add_function(self, func: Function) -> None:
133
157
func (Function): The Function object to be added.
134
158
"""
135
159
136
- q = """MERGE (f:Function {path: $path, name: $name,
160
+ q = """MERGE (f:Function:Searchable {path: $path, name: $name,
137
161
src_start: $src_start, src_end: $src_end})
138
162
SET f.args = $args, f.ret_type = $ret_type, f.src = $src, f.doc = $doc
139
163
RETURN ID(f)"""
@@ -151,7 +175,6 @@ def add_function(self, func: Function) -> None:
151
175
'ret_type' : func .ret_type
152
176
}
153
177
154
-
155
178
res = self .g .query (q , params )
156
179
func .id = res .result_set [0 ][0 ]
157
180
@@ -215,6 +238,53 @@ def get_function_by_name(self, name: str) -> Optional[Function]:
215
238
216
239
return self ._function_from_node (res [0 ][0 ])
217
240
241
+ def prefix_search (self , prefix : str ) -> str :
242
+ """
243
+ Search for entities by prefix using a full-text search on the graph.
244
+ The search is limited to 10 nodes. Each node's name and labels are retrieved,
245
+ and the results are sorted based on their labels.
246
+
247
+ Args:
248
+ prefix (str): The prefix string to search for in the graph database.
249
+
250
+ Returns:
251
+ str: A list of entity names and corresponding labels, sorted by label.
252
+ If no results are found or an error occurs, an empty list is returned.
253
+ """
254
+
255
+ # Append a wildcard '*' to the prefix for full-text search.
256
+ search_prefix = f"{ prefix } *"
257
+
258
+ # Cypher query to perform full-text search and limit the result to 10 nodes.
259
+ # The 'CALL db.idx.fulltext.queryNodes' method searches for nodes labeled 'Searchable'
260
+ # that match the given prefix, collects the nodes, and returns the result.
261
+ query = """
262
+ CALL db.idx.fulltext.queryNodes('Searchable', $prefix)
263
+ YIELD node
264
+ WITH node
265
+ LIMIT 10
266
+ RETURN collect(node)
267
+ """
268
+
269
+ try :
270
+ # Execute the query using the provided graph database connection.
271
+ completions = self .g .query (query , {'prefix' : search_prefix }).result_set [0 ][0 ]
272
+
273
+ # Remove label Searchable from each node
274
+ for node in completions :
275
+ node .labels .remove ('Searchable' )
276
+
277
+ # Sort the results by the label for better organization.
278
+ completions = sorted (completions , key = lambda x : x .labels )
279
+
280
+ return completions
281
+
282
+ except Exception as e :
283
+ # Log any errors encountered during the search and return an empty list.
284
+ logging .error (f"Error while searching for entities with prefix '{ prefix } ': { e } " )
285
+ return []
286
+
287
+
218
288
def get_function (self , func_id : int ) -> Optional [Function ]:
219
289
q = """MATCH (f:Function)
220
290
WHERE ID(f) = $func_id
@@ -269,7 +339,7 @@ def add_file(self, file: File) -> None:
269
339
file_ext (str): Extension of the file.
270
340
"""
271
341
272
- q = """MERGE (f:File {path: $path, name: $name, ext: $ext})
342
+ q = """MERGE (f:File:Searchable {path: $path, name: $name, ext: $ext})
273
343
RETURN ID(f)"""
274
344
params = {'path' : file .path , 'name' : file .name , 'ext' : file .ext }
275
345
@@ -400,7 +470,7 @@ def add_struct(self, s: Struct) -> None:
400
470
s (Struct): The Struct object to be added.
401
471
"""
402
472
403
- q = """MERGE (s:Struct {name: $name, path: $path, src_start: $src_start,
473
+ q = """MERGE (s:Struct:Searchable {name: $name, path: $path, src_start: $src_start,
404
474
src_end: $src_end})
405
475
SET s.doc = $doc, s.fields = $fields
406
476
RETURN ID(s)"""
@@ -484,3 +554,4 @@ def rerun_query(self, q: str, params: dict) -> QueryResult:
484
554
"""
485
555
486
556
return self .g .query (q , params )
557
+
0 commit comments