How Memgraph Stores and Queries Code Graph Relationships in code-graph-rag
Memgraph persists code relationships as labeled property graphs, with MemgraphIngestor handling batched Cypher writes and the CLI exposing direct query capabilities.
The code-graph-rag repository transforms source code into queryable graph structures for retrieval-augmented generation (RAG) workflows. At its core, Memgraph serves as the primary storage engine for code entities—files, functions, classes, imports, and their relationships—enabling fast traversal of complex dependency and call graphs.
Connecting to Memgraph: The connect_memgraph Factory
Before any data flows, the system establishes a driver connection through a centralized factory. In codebase_rag/main.py, the connect_memgraph function instantiates MemgraphIngestor with credentials sourced from environment or config:
from codebase_rag.main import connect_memgraph
# Batch size controls transaction frequency for bulk inserts
with connect_memgraph(batch_size=500) as ingestor:
# ingestor ready for node/edge operations
pass
Configuration parameters (defined in codebase_rag/config.py):
MEMGRAPH_HOST— database hostnameMEMGRAPH_PORT— Bolt protocol port (default 7687)MEMGRAPH_USERNAME/MEMGRAPH_PASSWORD— optional authentication
How Memgraph Stores Code Graph Relationships
The MemgraphIngestor Wrapper
The core storage abstraction lives in codebase_rag/services/graph_service.py. The MemgraphIngestor class wraps the official mgclient driver and exposes three critical operations:
| Method | Purpose | Cypher Pattern |
|---|---|---|
add_node(label, properties) |
Create vertices | CREATE (n:Label {props}) |
add_edge(from_node, to_node, rel_type, properties) |
Create relationships | MATCH (a), (b) CREATE (a)-[r:TYPE {props}]->(b) |
run_cypher(query) |
Execute arbitrary queries | User-supplied Cypher |
Node and Relationship Schema
Nodes represent code artifacts with consistent property sets:
File—path,line_start,line_endFunction/Method—name,fqn(fully qualified name),signature,line_startClass—name,fqn,line_start,line_endVariable—name,type_hint(when available)
Relationships encode semantic connections:
CALLS— function-to-function invocationsIMPORTS— file-to-file module dependenciesDECLARES— container-to-member definitions (class→method, file→function)EXTENDS— inheritance hierarchies
Batched Insertion for Performance
The ingestor accumulates operations in memory and commits transactions when the batch threshold is reached. This minimizes network round-trips during large codebase ingestion:
# From trace ingestion workflow
with connect_memgraph(batch_size=1000) as ingestor:
for record in parsed_trace:
ingestor.add_node("Function", {
"name": record.func_name,
"fqn": record.fqn,
"line_start": record.lineno
})
ingestor.add_edge(
from_node={"fqn": record.caller_fqn},
to_node={"fqn": record.callee_fqn},
rel_type="CALLS",
properties={"line": record.call_site}
)
# Final commit on context exit
How Memgraph Queries Code Graph Relationships
CLI Query Interface
The cgr graph query command in codebase_rag/cli.py provides direct Cypher access:
# Find all functions calling a specific target
cgr graph query 'MATCH (f:Function)-[:CALLS]->(t:Function {name:"process_data"}) RETURN f.fqn'
Implementation (lines 298-306):
with connect_memgraph(batch_size=1) as ingestor:
result = ingestor.run_cypher(user_query)
for row in result:
print(row)
Programmatic Query Patterns
1. Call Graph Traversal
// Direct callers of a function
MATCH (caller:Function)-[:CALLS]->(callee:Function {fqn: "mymod.utils.process"})
RETURN caller.fqn AS upstream_function, caller.path AS source_file
2. Import Dependency Tree
// Transitive imports from an entry point (limited depth)
MATCH (entry:File {path: "/src/main.py"})-[:IMPORTS*1..3]->(dep:File)
RETURN entry.path AS root, dep.path AS dependency, length(path) AS depth
3. Class Hierarchy Analysis
// Full inheritance chain
MATCH (subclass:Class)-[:EXTENDS*]->(base:Class)
WITH subclass, base ORDER BY length(path)
RETURN subclass.fqn AS class_name, collect(base.fqn) AS inherits_from
4. Symbol Definition Lookup
// Where is this function declared?
MATCH (f:Function {name: "handle_request"})
MATCH (parent)-[:DECLARES]->(f)
RETURN parent.path AS defined_in, f.line_start AS line_number
Exporting Graph Data
The export_graph_to_dict() method serializes the entire graph for external processing:
with connect_memgraph(batch_size=1000) as ingestor:
graph_data = ingestor.export_graph_to_dict()
# Returns: {"nodes": [...], "relationships": [...]}
# Suitable for JSON export or visualization tools
Trace Ingestion: From Runtime Data to Stored Graph
The cgr trace ingest command bridges runtime profiling and graph storage. Located in codebase_rag/trace/cli.py (lines 41-56), it:
- Parses profiler output (execution traces with call stacks)
- Resolves symbols to source file locations
- Streams normalized records to
MemgraphIngestor
# Simplified trace ingestion flow
from codebase_rag.trace.ingest import ingest_trace
from pathlib import Path
trace_file = Path("profiling_output.trace")
repo_root = Path("/path/to/analyzed/repo")
with connect_memgraph(batch_size=500) as ingestor:
ingest_trace(
trace_path=trace_file,
ingestor=ingestor,
repo_path=repo_root,
project_name="my_service"
)
This populates CALLS relationships with actual runtime frequency data, augmenting static analysis graphs with dynamic execution patterns.
Configuration and Deployment
Environment variables control Memgraph connectivity without code changes:
export MEMGRAPH_HOST=memgraph.internal
export MEMGRAPH_PORT=7687
export MEMGRAPH_USERNAME=codegraph
export MEMGRAPH_PASSWORD=secure_token
# Now all CLI commands use these credentials
cgr trace ingest --trace data.trace --repo ./src
For local development, the default localhost:7687 with no authentication applies.
Summary
MemgraphIngestorincodebase_rag/services/graph_service.pyabstracts all Memgraph interactions—connection management, batched writes, and query execution.- Code entities map to labeled nodes (
File,Function,Class,Variable) withfqnand location properties. - Relationships use directed edges (
CALLS,IMPORTS,DECLARES,EXTENDS) with optional metadata. - Batching via
batch_sizeparameter optimizes bulk ingestion throughput. - CLI entry points (
cgr trace ingest,cgr graph query) and programmatic APIs provide flexible access patterns. - Configuration through
codebase_rag/config.pyor environment variables enables deployment portability.
Frequently Asked Questions
What Cypher statements does MemgraphIngestor generate internally?
The ingestor produces standard Cypher CREATE and MERGE statements. Nodes use parameterized CREATE (n:Label {props}) with unique fqn values as identifiers. Relationships execute as MATCH followed by CREATE to link resolved node references, ensuring idempotent inserts when MERGE is specified.
How does batching affect transaction boundaries?
Each batch accumulates in memory until reaching batch_size, then commits as a single transaction. Larger batches improve throughput but increase memory usage; smaller batches provide more frequent persistence points. The context manager ensures final commit on normal exit and rollback on exceptions.
Can I query Memgraph directly without the Python wrapper?
Yes. The database exposes Bolt protocol access on the configured port. You can connect with any Cypher client (mgconsole, Neo4j Browser, or language drivers) using the same host/port/credentials specified in codebase_rag/config.py. The export_graph_to_dict() method provides a bridge for external tools expecting JSON graph formats.
What distinguishes CALLS relationships from DECLARES?
CALLS captures dynamic invocation at runtime or static analysis call sites—function A executes function B. DECLARES captures static containment—file or class B contains definition of member A. Call graphs answer "what executes," while declaration trees answer "where defined."
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →