codegraphcontext-rust 0.5.5__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. codegraphcontext/__init__.py +2 -0
  2. codegraphcontext/__main__.py +5 -0
  3. codegraphcontext/_cgc_rust.cp310-win_amd64.pyd +0 -0
  4. codegraphcontext/cli/__init__.py +1 -0
  5. codegraphcontext/cli/cli_helpers.py +774 -0
  6. codegraphcontext/cli/config_manager.py +1051 -0
  7. codegraphcontext/cli/main.py +2386 -0
  8. codegraphcontext/cli/registry_commands.py +404 -0
  9. codegraphcontext/cli/setup_macos.py +93 -0
  10. codegraphcontext/cli/setup_wizard.py +991 -0
  11. codegraphcontext/cli/visualizer.py +50 -0
  12. codegraphcontext/core/__init__.py +165 -0
  13. codegraphcontext/core/bundle_registry.py +181 -0
  14. codegraphcontext/core/cgc_bundle.py +858 -0
  15. codegraphcontext/core/cgcignore.py +118 -0
  16. codegraphcontext/core/database.py +273 -0
  17. codegraphcontext/core/database_falkordb.py +480 -0
  18. codegraphcontext/core/database_falkordb_remote.py +199 -0
  19. codegraphcontext/core/database_kuzu.py +636 -0
  20. codegraphcontext/core/falkor_worker.py +132 -0
  21. codegraphcontext/core/jobs.py +132 -0
  22. codegraphcontext/core/watcher.py +260 -0
  23. codegraphcontext/prompts.py +125 -0
  24. codegraphcontext/server.py +471 -0
  25. codegraphcontext/tool_definitions.py +220 -0
  26. codegraphcontext/tools/__init__.py +0 -0
  27. codegraphcontext/tools/advanced_language_query_tool.py +103 -0
  28. codegraphcontext/tools/code_finder.py +1160 -0
  29. codegraphcontext/tools/graph_builder.py +337 -0
  30. codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
  31. codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
  32. codegraphcontext/tools/handlers/management_handlers.py +339 -0
  33. codegraphcontext/tools/handlers/query_handlers.py +83 -0
  34. codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
  35. codegraphcontext/tools/indexing/__init__.py +1 -0
  36. codegraphcontext/tools/indexing/constants.py +25 -0
  37. codegraphcontext/tools/indexing/discovery.py +64 -0
  38. codegraphcontext/tools/indexing/engine.py +109 -0
  39. codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
  40. codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
  41. codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
  42. codegraphcontext/tools/indexing/pipeline.py +171 -0
  43. codegraphcontext/tools/indexing/pre_scan.py +105 -0
  44. codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
  45. codegraphcontext/tools/indexing/resolution/calls.py +253 -0
  46. codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
  47. codegraphcontext/tools/indexing/sanitize.py +41 -0
  48. codegraphcontext/tools/indexing/schema.py +79 -0
  49. codegraphcontext/tools/indexing/schema_contract.py +44 -0
  50. codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
  51. codegraphcontext/tools/languages/c.py +563 -0
  52. codegraphcontext/tools/languages/cpp.py +616 -0
  53. codegraphcontext/tools/languages/csharp.py +551 -0
  54. codegraphcontext/tools/languages/dart.py +378 -0
  55. codegraphcontext/tools/languages/elixir.py +460 -0
  56. codegraphcontext/tools/languages/go.py +499 -0
  57. codegraphcontext/tools/languages/haskell.py +426 -0
  58. codegraphcontext/tools/languages/java.py +500 -0
  59. codegraphcontext/tools/languages/javascript.py +589 -0
  60. codegraphcontext/tools/languages/kotlin.py +640 -0
  61. codegraphcontext/tools/languages/perl.py +260 -0
  62. codegraphcontext/tools/languages/php.py +519 -0
  63. codegraphcontext/tools/languages/python.py +576 -0
  64. codegraphcontext/tools/languages/ruby.py +537 -0
  65. codegraphcontext/tools/languages/rust.py +295 -0
  66. codegraphcontext/tools/languages/scala.py +519 -0
  67. codegraphcontext/tools/languages/swift.py +491 -0
  68. codegraphcontext/tools/languages/typescript.py +575 -0
  69. codegraphcontext/tools/languages/typescriptjsx.py +151 -0
  70. codegraphcontext/tools/package_resolver.py +472 -0
  71. codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
  72. codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
  73. codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
  74. codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
  75. codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
  76. codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
  77. codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
  78. codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
  79. codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
  80. codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
  81. codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
  82. codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
  83. codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
  84. codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
  85. codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
  86. codegraphcontext/tools/scip_indexer.py +467 -0
  87. codegraphcontext/tools/scip_pb2.py +2455 -0
  88. codegraphcontext/tools/system.py +133 -0
  89. codegraphcontext/tools/tree_sitter_parser.py +103 -0
  90. codegraphcontext/utils/debug_log.py +90 -0
  91. codegraphcontext/utils/path_ignore.py +55 -0
  92. codegraphcontext/utils/repo_path.py +27 -0
  93. codegraphcontext/utils/tree_sitter_manager.py +285 -0
  94. codegraphcontext/utils/visualize_graph.py +0 -0
  95. codegraphcontext/viz/server.py +282 -0
  96. codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
  97. codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
  98. codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
  99. codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
  100. codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
  101. codegraphcontext_rust-0.5.5.dist-info/sboms/cgc-python.cyclonedx.json +2333 -0
@@ -0,0 +1,643 @@
1
+ """Parquet bulk loader for KuzuDB — uses COPY FROM for 10-50x faster DB writes.
2
+
3
+ Strategy (like GKG/LadybugDB):
4
+ 1. Serialize parsed data → Parquet files (type-safe, no escaping issues)
5
+ 2. Drop existing tables → Recreate schema
6
+ 3. COPY FROM parquet (KuzuDB native bulk import)
7
+
8
+ This replaces the MERGE-based batch writer for initial indexing.
9
+ For incremental updates, the MERGE writer is still available.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import time
16
+ import tempfile
17
+ from pathlib import Path
18
+ from typing import Any, Callable, Dict, List, Optional, Tuple
19
+ from collections import defaultdict
20
+
21
+ from ....utils.debug_log import info_logger, warning_logger
22
+
23
+
24
+ def _ensure_pyarrow():
25
+ """Import pyarrow lazily."""
26
+ try:
27
+ import pyarrow as pa
28
+ import pyarrow.parquet as pq
29
+ return pa, pq
30
+ except ImportError:
31
+ raise ImportError(
32
+ "pyarrow is required for Parquet bulk loading. "
33
+ "Install with: pip install pyarrow"
34
+ )
35
+
36
+
37
+ def bulk_load_parquet(
38
+ conn: Any,
39
+ all_file_data: List[Dict[str, Any]],
40
+ repo_path: str,
41
+ call_groups: Tuple[List, List, List, List, List, List],
42
+ inheritance_links: List[Dict] = None,
43
+ on_progress: Optional[Callable] = None,
44
+ ) -> Dict[str, int]:
45
+ """Bulk load all data via Parquet COPY FROM.
46
+
47
+ Args:
48
+ conn: Raw kuzu.Connection (not the wrapper)
49
+ all_file_data: List of parsed file dicts
50
+ repo_path: Repository root path
51
+ call_groups: Tuple of 6 call edge lists (fn_fn, fn_cls, cls_fn, cls_cls, file_fn, file_cls)
52
+ inheritance_links: Optional list of inheritance dicts
53
+ on_progress: Optional progress callback
54
+
55
+ Returns:
56
+ Dict with counts of loaded entities.
57
+ """
58
+ pa, pq = _ensure_pyarrow()
59
+ t_start = time.time()
60
+ repo_path_str = str(Path(repo_path).resolve())
61
+ repo_name = Path(repo_path).name
62
+
63
+ pq_dir = tempfile.mkdtemp(prefix="cgc_parquet_")
64
+ counts = {}
65
+
66
+ def progress(msg):
67
+ if on_progress:
68
+ on_progress(0, 0, msg)
69
+ info_logger(f"[ParquetBulk] {msg}")
70
+
71
+ def write_pq(name, table):
72
+ path = os.path.join(pq_dir, f"{name}.parquet")
73
+ pq.write_table(table, path, compression='snappy')
74
+ return path
75
+
76
+ # ── Phase 1: Serialize to Parquet ──
77
+ progress("Serializing to Parquet...")
78
+ t0 = time.time()
79
+
80
+ # Helper: make uid
81
+ def uid(name, path, line):
82
+ return f"{name}|{path}|{line}"
83
+
84
+ # Collect all data
85
+ file_paths, file_names, file_rels, file_deps = [], [], [], []
86
+ dir_paths, dir_names = [], []
87
+ dir_from, dir_to = defaultdict(list), defaultdict(list) # parent_label → lists
88
+ file_parent_from, file_parent_to = defaultdict(list), defaultdict(list)
89
+
90
+ # Symbols per label: {label: {col_name: [values]}}
91
+ sym_data = {}
92
+ sym_contains = {} # {label: (file_paths, symbol_uids)}
93
+
94
+ param_uids, param_names, param_paths, param_flines = [], [], [], []
95
+ param_rel_fn, param_rel_p = [], []
96
+
97
+ class_fn_cls, class_fn_fn = [], []
98
+ nested_outer, nested_inner = [], []
99
+
100
+ mod_names, mod_fulls = [], []
101
+ imp_file, imp_mod, imp_alias, imp_imported, imp_line = [], [], [], [], []
102
+
103
+ seen_dirs = set()
104
+ seen_symbols = set()
105
+ seen_modules = set()
106
+
107
+ for fd in all_file_data:
108
+ fp = str(Path(fd["path"]).resolve())
109
+ fname = Path(fp).name
110
+ try:
111
+ rel = str(Path(fp).relative_to(repo_path_str))
112
+ except ValueError:
113
+ rel = fname
114
+ is_dep = fd.get("is_dependency", False)
115
+ lang = fd.get("lang", "")
116
+
117
+ file_paths.append(fp)
118
+ file_names.append(fname)
119
+ file_rels.append(rel)
120
+ file_deps.append(is_dep)
121
+
122
+ # Directory chain
123
+ try:
124
+ rel_to_file = Path(fp).relative_to(repo_path_str)
125
+ except ValueError:
126
+ rel_to_file = Path(fname)
127
+
128
+ parent_path = repo_path_str
129
+ parent_label = "Repository"
130
+ for part in rel_to_file.parts[:-1]:
131
+ cur = str(Path(parent_path) / part)
132
+ if cur not in seen_dirs:
133
+ seen_dirs.add(cur)
134
+ dir_paths.append(cur)
135
+ dir_names.append(part)
136
+ dir_from[parent_label].append(parent_path)
137
+ dir_to[parent_label].append(cur)
138
+ parent_path = cur
139
+ parent_label = "Directory"
140
+
141
+ file_parent_from[parent_label].append(parent_path)
142
+ file_parent_to[parent_label].append(fp)
143
+
144
+ # Symbols
145
+ label_mappings = [
146
+ ("functions", "Function"), ("classes", "Class"), ("variables", "Variable"),
147
+ ("traits", "Trait"), ("interfaces", "Interface"), ("macros", "Macro"),
148
+ ("structs", "Struct"), ("enums", "Enum"), ("unions", "Union"),
149
+ ("records", "Record"), ("properties", "Property"),
150
+ ]
151
+ for key, label in label_mappings:
152
+ items = fd.get(key, [])
153
+ if not items:
154
+ continue
155
+ if label not in sym_data:
156
+ sym_data[label] = defaultdict(list)
157
+ sym_contains[label] = ([], [])
158
+
159
+ for item in items:
160
+ name = item.get("name", "")
161
+ line = item.get("line_number", 0)
162
+ u = uid(name, fp, line)
163
+ if u in seen_symbols:
164
+ continue
165
+ seen_symbols.add(u)
166
+
167
+ d = sym_data[label]
168
+ d["uid"].append(u)
169
+ d["name"].append(name)
170
+ d["path"].append(fp)
171
+ d["line_number"].append(line)
172
+ d["end_line"].append(item.get("end_line", 0))
173
+ d["source"].append(item.get("source", ""))
174
+ d["docstring"].append(item.get("docstring", ""))
175
+ d["lang"].append(item.get("lang", lang))
176
+ d["is_dependency"].append(item.get("is_dependency", is_dep))
177
+
178
+ # Function-specific fields
179
+ if label == "Function":
180
+ d["cyclomatic_complexity"].append(item.get("cyclomatic_complexity", 1))
181
+ d["context"].append(item.get("context", ""))
182
+ d["context_type"].append(item.get("context_type", ""))
183
+ d["class_context"].append(item.get("class_context", ""))
184
+
185
+ args = item.get("args", [])
186
+ decorators = item.get("decorators", [])
187
+ d["args"].append(args if args else [])
188
+ d["decorators"].append(decorators if decorators else [])
189
+
190
+ # Parameters
191
+ for arg_name in args:
192
+ if not arg_name:
193
+ continue
194
+ pu = f"{arg_name}|{fp}|{line}"
195
+ param_uids.append(pu)
196
+ param_names.append(arg_name)
197
+ param_paths.append(fp)
198
+ param_flines.append(line)
199
+ param_rel_fn.append(u)
200
+ param_rel_p.append(pu)
201
+
202
+ # Class containment
203
+ cc = item.get("class_context", "")
204
+ if cc:
205
+ class_fn_cls.append(f"{cc}|{fp}")
206
+ class_fn_fn.append(u)
207
+
208
+ # Nested functions
209
+ if item.get("context_type") == "function_definition" and item.get("context"):
210
+ nested_outer.append(f"{item['context']}|{fp}")
211
+ nested_inner.append(u)
212
+
213
+ elif label == "Class":
214
+ d.setdefault("decorators", []).append(item.get("decorators", []) or [])
215
+
216
+ elif label == "Variable":
217
+ d["value"] = d.get("value", [])
218
+ d["value"].append(item.get("value", ""))
219
+ d["context"] = d.get("context", [])
220
+ d["context"].append(item.get("context", ""))
221
+
222
+ sym_contains[label][0].append(fp)
223
+ sym_contains[label][1].append(u)
224
+
225
+ # Imports
226
+ for imp in fd.get("imports", []):
227
+ name = imp.get("name", "")
228
+ alias = imp.get("alias", "")
229
+ source = imp.get("source", "")
230
+ # Bug 1 fix: full_import_name fallback chain
231
+ full = imp.get("full_import_name", "") or source or name
232
+ line_n = imp.get("line_number", 0)
233
+ mod_name = source if source else name
234
+ imported = name if source else ""
235
+ if not mod_name:
236
+ continue
237
+
238
+ if mod_name not in seen_modules:
239
+ seen_modules.add(mod_name)
240
+ mod_names.append(mod_name)
241
+ mod_fulls.append(full)
242
+
243
+ imp_file.append(fp)
244
+ imp_mod.append(mod_name)
245
+ imp_alias.append(alias)
246
+ imp_imported.append(imported)
247
+ imp_line.append(line_n)
248
+
249
+ t_serialize_data = time.time() - t0
250
+
251
+ # ── Write Parquet files ──
252
+ t0 = time.time()
253
+ pq_files = {}
254
+
255
+ # Repository (single row, COPY FROM)
256
+ pq_files["Repository"] = write_pq("repository", pa.table({
257
+ "path": [repo_path_str],
258
+ "name": [repo_name],
259
+ "is_dependency": [False],
260
+ }))
261
+
262
+ # Files
263
+ if file_paths:
264
+ pq_files["File"] = write_pq("files", pa.table({
265
+ "path": file_paths,
266
+ "name": file_names,
267
+ "relative_path": file_rels,
268
+ "is_dependency": file_deps,
269
+ }))
270
+ counts["files"] = len(file_paths)
271
+
272
+ # Directories
273
+ if dir_paths:
274
+ pq_files["Directory"] = write_pq("directories", pa.table({
275
+ "path": dir_paths,
276
+ "name": dir_names,
277
+ }))
278
+ counts["directories"] = len(dir_paths)
279
+
280
+ # Symbols
281
+ for label, d in sym_data.items():
282
+ if label == "Function":
283
+ tbl = pa.table({
284
+ "uid": d["uid"], "name": d["name"], "path": d["path"],
285
+ "line_number": pa.array(d["line_number"], type=pa.int64()),
286
+ "end_line": pa.array(d["end_line"], type=pa.int64()),
287
+ "source": d["source"], "docstring": d["docstring"],
288
+ "lang": d["lang"],
289
+ "cyclomatic_complexity": pa.array(d["cyclomatic_complexity"], type=pa.int64()),
290
+ "context": d["context"], "context_type": d["context_type"],
291
+ "class_context": d["class_context"],
292
+ "is_dependency": d["is_dependency"],
293
+ "decorators": pa.array(d["decorators"], type=pa.list_(pa.string())),
294
+ "args": pa.array(d["args"], type=pa.list_(pa.string())),
295
+ })
296
+ elif label == "Class":
297
+ tbl = pa.table({
298
+ "uid": d["uid"], "name": d["name"], "path": d["path"],
299
+ "line_number": pa.array(d["line_number"], type=pa.int64()),
300
+ "end_line": pa.array(d["end_line"], type=pa.int64()),
301
+ "source": d["source"], "docstring": d["docstring"],
302
+ "lang": d["lang"], "is_dependency": d["is_dependency"],
303
+ "decorators": pa.array(d.get("decorators", [[] for _ in d["uid"]]), type=pa.list_(pa.string())),
304
+ })
305
+ elif label == "Variable":
306
+ tbl = pa.table({
307
+ "uid": d["uid"], "name": d["name"], "path": d["path"],
308
+ "line_number": pa.array(d["line_number"], type=pa.int64()),
309
+ "source": d["source"], "docstring": d["docstring"],
310
+ "lang": d["lang"],
311
+ "value": d.get("value", ["" for _ in d["uid"]]),
312
+ "context": d.get("context", ["" for _ in d["uid"]]),
313
+ "is_dependency": d["is_dependency"],
314
+ })
315
+ elif label == "Parameter":
316
+ continue # handled separately
317
+ else:
318
+ # Generic: Trait, Interface, Macro, Struct, Enum, Union, Annotation, Record, Property
319
+ tbl = pa.table({
320
+ "uid": d["uid"], "name": d["name"], "path": d["path"],
321
+ "line_number": pa.array(d["line_number"], type=pa.int64()),
322
+ "end_line": pa.array(d["end_line"], type=pa.int64()),
323
+ "source": d["source"], "docstring": d["docstring"],
324
+ "lang": d["lang"], "is_dependency": d["is_dependency"],
325
+ })
326
+
327
+ pq_files[label] = write_pq(f"sym_{label.lower()}", tbl)
328
+ counts[f"sym_{label.lower()}"] = len(d["uid"])
329
+
330
+ # Parameters
331
+ if param_uids:
332
+ pq_files["Parameter"] = write_pq("params", pa.table({
333
+ "uid": param_uids,
334
+ "name": param_names,
335
+ "path": param_paths,
336
+ "function_line_number": pa.array(param_flines, type=pa.int64()),
337
+ }))
338
+ counts["params"] = len(param_uids)
339
+
340
+ # Modules
341
+ if mod_names:
342
+ pq_files["Module"] = write_pq("modules", pa.table({
343
+ "name": mod_names,
344
+ "lang": ["" for _ in mod_names],
345
+ "full_import_name": mod_fulls,
346
+ }))
347
+ counts["modules"] = len(mod_names)
348
+
349
+ # ── Relationship Parquet files ──
350
+ rel_files = {}
351
+
352
+ # Directory CONTAINS (Repository→Directory, Directory→Directory)
353
+ for parent_label in ("Repository", "Directory"):
354
+ froms = dir_from.get(parent_label, [])
355
+ tos = dir_to.get(parent_label, [])
356
+ if froms:
357
+ key = f"CONTAINS_{parent_label}_Directory"
358
+ rel_files[key] = {
359
+ "path": write_pq(f"rel_contains_{parent_label.lower()}_dir", pa.table({
360
+ "from": froms, "to": tos,
361
+ })),
362
+ "from": parent_label, "to": "Directory",
363
+ }
364
+
365
+ # File parent CONTAINS (Repository→File, Directory→File)
366
+ for parent_label in ("Repository", "Directory"):
367
+ froms = file_parent_from.get(parent_label, [])
368
+ tos = file_parent_to.get(parent_label, [])
369
+ if froms:
370
+ key = f"CONTAINS_{parent_label}_File"
371
+ rel_files[key] = {
372
+ "path": write_pq(f"rel_contains_{parent_label.lower()}_file", pa.table({
373
+ "from": froms, "to": tos,
374
+ })),
375
+ "from": parent_label, "to": "File",
376
+ }
377
+
378
+ # File CONTAINS Symbol
379
+ for label, (fps, uids) in sym_contains.items():
380
+ if fps:
381
+ escaped = label
382
+ key = f"CONTAINS_File_{label}"
383
+ rel_files[key] = {
384
+ "path": write_pq(f"rel_file_contains_{label.lower()}", pa.table({
385
+ "from": fps, "to": uids,
386
+ })),
387
+ "from": "File", "to": escaped,
388
+ }
389
+
390
+ # HAS_PARAMETER
391
+ if param_rel_fn:
392
+ rel_files["HAS_PARAMETER"] = {
393
+ "path": write_pq("rel_has_parameter", pa.table({
394
+ "from": param_rel_fn, "to": param_rel_p,
395
+ })),
396
+ "from": "Function", "to": "Parameter",
397
+ }
398
+
399
+ # IMPORTS (File→Module)
400
+ if imp_file:
401
+ rel_files["IMPORTS"] = {
402
+ "path": write_pq("rel_imports", pa.table({
403
+ "from": imp_file, "to": imp_mod,
404
+ "alias": imp_alias,
405
+ "full_import_name": imp_imported,
406
+ "imported_name": imp_imported,
407
+ "line_number": pa.array(imp_line, type=pa.int64()),
408
+ })),
409
+ "from": "File", "to": "Module",
410
+ }
411
+
412
+ # CALLS (6 categories)
413
+ # Build (name, path) → uid lookup for resolving called targets
414
+ func_uid_lookup = {} # (name, path) → first uid
415
+ class_uid_lookup = {}
416
+ for label, d in sym_data.items():
417
+ lookup = func_uid_lookup if label == "Function" else class_uid_lookup if label == "Class" else None
418
+ if lookup is None:
419
+ continue
420
+ for i, u in enumerate(d["uid"]):
421
+ key = (d["name"][i], d["path"][i])
422
+ if key not in lookup:
423
+ lookup[key] = u
424
+
425
+ call_labels = [
426
+ ("fn_fn", "Function", "Function", func_uid_lookup),
427
+ ("fn_cls", "Function", "Class", class_uid_lookup),
428
+ ("cls_fn", "Class", "Function", func_uid_lookup),
429
+ ("cls_cls", "Class", "Class", class_uid_lookup),
430
+ ("file_fn", "File", "Function", func_uid_lookup),
431
+ ("file_cls", "File", "Class", class_uid_lookup),
432
+ ]
433
+ fn_to_fn, fn_to_cls, cls_to_fn, cls_to_cls, file_to_fn, file_to_cls = call_groups
434
+ call_lists = [fn_to_fn, fn_to_cls, cls_to_fn, cls_to_cls, file_to_fn, file_to_cls]
435
+
436
+ for (name, from_label, to_label, to_lookup), edges in zip(call_labels, call_lists):
437
+ if not edges:
438
+ continue
439
+ froms, tos, lines, args_list, fcns = [], [], [], [], []
440
+ skipped = 0
441
+ for e in edges:
442
+ # Resolve called target uid
443
+ called_key = (e.get("called_name", ""), e.get("called_file_path", ""))
444
+ called_uid = to_lookup.get(called_key)
445
+ if not called_uid:
446
+ skipped += 1
447
+ continue
448
+
449
+ # Resolve caller
450
+ if from_label == "File":
451
+ froms.append(e.get("caller_file_path", ""))
452
+ else:
453
+ caller_label_lookup = func_uid_lookup if from_label == "Function" else class_uid_lookup
454
+ caller_uid = uid(
455
+ e.get("caller_name", ""),
456
+ e.get("caller_file_path", ""),
457
+ e.get("caller_line_number", 0),
458
+ )
459
+ froms.append(caller_uid)
460
+
461
+ tos.append(called_uid)
462
+ lines.append(e.get("line_number", 0))
463
+ args_list.append(e.get("args", []) or [])
464
+ fcns.append(e.get("full_call_name", ""))
465
+
466
+ if froms:
467
+ key = f"CALLS_{name}"
468
+ rel_files[key] = {
469
+ "path": write_pq(f"rel_calls_{name}", pa.table({
470
+ "from": froms,
471
+ "to": tos,
472
+ "line_number": pa.array(lines, type=pa.int64()),
473
+ "args": pa.array(args_list, type=pa.list_(pa.string())),
474
+ "full_call_name": fcns,
475
+ })),
476
+ "from": from_label, "to": to_label,
477
+ "is_calls": True,
478
+ }
479
+ counts[f"calls_{name}"] = len(froms)
480
+ if skipped:
481
+ info_logger(f"[ParquetBulk] CALLS {name}: {len(froms)} written, {skipped} skipped (target not found)")
482
+
483
+ # INHERITS
484
+ if inheritance_links:
485
+ child_uids, parent_uids = [], []
486
+ for link in inheritance_links:
487
+ child_uids.append(uid(
488
+ link.get("child_name", ""),
489
+ link.get("child_path", ""),
490
+ link.get("child_line", 0),
491
+ ))
492
+ parent_uids.append(uid(
493
+ link.get("parent_name", ""),
494
+ link.get("parent_path", ""),
495
+ link.get("parent_line", 0),
496
+ ))
497
+ rel_files["INHERITS_Class_Class"] = {
498
+ "path": write_pq("rel_inherits", pa.table({
499
+ "from": child_uids, "to": parent_uids,
500
+ })),
501
+ "from": "Class", "to": "Class",
502
+ }
503
+ counts["inheritance"] = len(inheritance_links)
504
+
505
+ t_write_pq = time.time() - t0
506
+ progress(f"Parquet written in {t_write_pq:.1f}s ({len(pq_files)} node files, {len(rel_files)} rel files)")
507
+
508
+ # ── Phase 2: Drop & recreate tables, COPY FROM ──
509
+ progress("Dropping and recreating tables...")
510
+ t0 = time.time()
511
+
512
+ _drop_all_tables(conn)
513
+ _create_schema(conn)
514
+
515
+ t_schema = time.time() - t0
516
+
517
+ # ── Phase 3: COPY FROM ──
518
+ progress("COPY FROM parquet (bulk import)...")
519
+ t0 = time.time()
520
+
521
+ # Node tables first
522
+ node_order = [
523
+ "Repository", "File", "Directory", "Module", "Parameter",
524
+ "Function", "Class", "Variable", "Trait", "Interface",
525
+ "Macro", "Struct", "Enum", "Union", "Annotation", "Record", "Property",
526
+ ]
527
+ for table_name in node_order:
528
+ if table_name in pq_files:
529
+ escaped = f"`{table_name}`" if table_name in ("Macro", "Union", "Property") else table_name
530
+ path = pq_files[table_name]
531
+ try:
532
+ conn.execute(f"COPY {escaped} FROM '{path}'")
533
+ except Exception as e:
534
+ warning_logger(f"COPY {table_name} failed: {e}")
535
+
536
+ # Relationship tables
537
+ for key, info in rel_files.items():
538
+ path = info["path"]
539
+ from_t = info["from"]
540
+ to_t = info["to"]
541
+ is_calls = info.get("is_calls", False)
542
+
543
+ # Determine rel table name
544
+ if "CONTAINS" in key:
545
+ rel_name = "CONTAINS"
546
+ elif "HAS_PARAMETER" in key:
547
+ rel_name = "HAS_PARAMETER"
548
+ elif "IMPORTS" in key:
549
+ rel_name = "IMPORTS"
550
+ elif "CALLS" in key:
551
+ rel_name = "CALLS"
552
+ elif "INHERITS" in key:
553
+ rel_name = "INHERITS"
554
+ else:
555
+ continue
556
+
557
+ # Escape reserved words in from/to
558
+ from_escaped = f"`{from_t}`" if from_t in ("Macro", "Union", "Property") else from_t
559
+ to_escaped = f"`{to_t}`" if to_t in ("Macro", "Union", "Property") else to_t
560
+
561
+ try:
562
+ conn.execute(
563
+ f"COPY {rel_name} FROM '{path}' (from='{from_escaped}', to='{to_escaped}')"
564
+ )
565
+ except Exception as e:
566
+ warning_logger(f"COPY {rel_name} ({from_t}→{to_t}) failed: {e}")
567
+
568
+ t_copy = time.time() - t0
569
+
570
+ # Cleanup temp dir
571
+ import shutil
572
+ shutil.rmtree(pq_dir, ignore_errors=True)
573
+
574
+ total = time.time() - t_start
575
+ progress(f"Done in {total:.1f}s (serialize={t_serialize_data:.1f}s, write_pq={t_write_pq:.1f}s, schema={t_schema:.1f}s, copy={t_copy:.1f}s)")
576
+ counts["total_time"] = round(total, 1)
577
+ return counts
578
+
579
+
580
+ def _drop_all_tables(conn):
581
+ """Drop all node and rel tables."""
582
+ # Drop rel tables first (depend on node tables)
583
+ for rel in ["CALLS", "CONTAINS", "IMPORTS", "INHERITS", "HAS_PARAMETER", "INCLUDES", "IMPLEMENTS"]:
584
+ try:
585
+ conn.execute(f"DROP TABLE {rel}")
586
+ except Exception:
587
+ pass
588
+
589
+ # Drop node tables
590
+ for node in ["Parameter", "Function", "Class", "Variable", "Trait", "Interface",
591
+ "`Macro`", "Struct", "Enum", "`Union`", "Annotation", "Record",
592
+ "`Property`", "Module", "File", "Directory", "Repository"]:
593
+ try:
594
+ conn.execute(f"DROP TABLE {node}")
595
+ except Exception:
596
+ pass
597
+
598
+
599
+ def _create_schema(conn):
600
+ """Recreate all node and rel tables."""
601
+ node_tables = [
602
+ ("Repository", "path STRING, name STRING, is_dependency BOOLEAN, PRIMARY KEY (path)"),
603
+ ("File", "path STRING, name STRING, relative_path STRING, is_dependency BOOLEAN, PRIMARY KEY (path)"),
604
+ ("Directory", "path STRING, name STRING, PRIMARY KEY (path)"),
605
+ ("Module", "name STRING, lang STRING, full_import_name STRING, PRIMARY KEY (name)"),
606
+ ("Function", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, cyclomatic_complexity INT64, context STRING, context_type STRING, class_context STRING, is_dependency BOOLEAN, decorators STRING[], args STRING[], PRIMARY KEY (uid)"),
607
+ ("Class", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, decorators STRING[], PRIMARY KEY (uid)"),
608
+ ("Variable", "uid STRING, name STRING, path STRING, line_number INT64, source STRING, docstring STRING, lang STRING, value STRING, context STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
609
+ ("Trait", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
610
+ ("Interface", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
611
+ ("Macro", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
612
+ ("Struct", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
613
+ ("Enum", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
614
+ ("Union", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
615
+ ("Annotation", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
616
+ ("Record", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
617
+ ("Property", "uid STRING, name STRING, path STRING, line_number INT64, end_line INT64, source STRING, docstring STRING, lang STRING, is_dependency BOOLEAN, PRIMARY KEY (uid)"),
618
+ ("Parameter", "uid STRING, name STRING, path STRING, function_line_number INT64, PRIMARY KEY (uid)"),
619
+ ]
620
+ rel_tables = [
621
+ ("CONTAINS", "FROM File TO Function, FROM File TO Class, FROM File TO Variable, FROM File TO Trait, FROM File TO Interface, FROM `Macro` TO `Macro`, FROM File TO `Macro`, FROM File TO Struct, FROM File TO Enum, FROM File TO `Union`, FROM File TO Annotation, FROM File TO Record, FROM File TO `Property`, FROM Repository TO Directory, FROM Directory TO Directory, FROM Directory TO File, FROM Repository TO File, FROM Class TO Function, FROM Function TO Function", True),
622
+ ("CALLS", "FROM Function TO Function, FROM Function TO Class, FROM File TO Function, FROM File TO Class, FROM Class TO Function, FROM Class TO Class, line_number INT64, args STRING[], full_call_name STRING", True),
623
+ ("IMPORTS", "FROM File TO Module, alias STRING, full_import_name STRING, imported_name STRING, line_number INT64", False),
624
+ ("INHERITS", "FROM Class TO Class, FROM Record TO Record, FROM Interface TO Interface", True),
625
+ ("HAS_PARAMETER", "FROM Function TO Parameter", False),
626
+ ("INCLUDES", "FROM Class TO Module", False),
627
+ ("IMPLEMENTS", "FROM Class TO Interface, FROM Struct TO Interface, FROM Record TO Interface", True),
628
+ ]
629
+
630
+ for table_name, schema in node_tables:
631
+ try:
632
+ conn.execute(f"CREATE NODE TABLE `{table_name}`({schema})")
633
+ except Exception:
634
+ pass
635
+
636
+ for table_name, schema, use_group in rel_tables:
637
+ try:
638
+ if use_group:
639
+ conn.execute(f"CREATE REL TABLE GROUP `{table_name}`({schema})")
640
+ else:
641
+ conn.execute(f"CREATE REL TABLE `{table_name}`({schema})")
642
+ except Exception:
643
+ pass