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 hostname
  • MEMGRAPH_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_end
  • Function / Method — name, fqn (fully qualified name), signature, line_start
  • Class — name, fqn, line_start, line_end
  • Variable — name, type_hint (when available)

Relationships encode semantic connections:

  • CALLS — function-to-function invocations
  • IMPORTS — file-to-file module dependencies
  • DECLARES — 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:

  1. Parses profiler output (execution traces with call stacks)
  2. Resolves symbols to source file locations
  3. 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

  • MemgraphIngestor in codebase_rag/services/graph_service.py abstracts all Memgraph interactions—connection management, batched writes, and query execution.
  • Code entities map to labeled nodes (File, Function, Class, Variable) with fqn and location properties.
  • Relationships use directed edges (CALLS, IMPORTS, DECLARES, EXTENDS) with optional metadata.
  • Batching via batch_size parameter 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.py or 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →