Code-Graph: SQL-Callable LSP Inside Your Database

Code search is normally a service. You run an indexer, it scans your repo, it persists into its own storage, an API server fronts it, and your tools talk HTTP to that server. The reasoning is sensible: code is large, parsers are heavy, and trees change all the time. But the cost is real — another daemon to run, another auth model, another invalidation story, another thing that drifts out of sync with what the database thinks the codebase is.

HeliosDB Nano takes a different approach. The AST is a table. Symbols are rows. Edges are rows. CREATE EXTENSION hdb_code; CREATE AST INDEX ON src/, and your codebase is queryable in SQL through the same engine that already serves your application data.

The Design: Tables, Not Daemons

Code-graph projects code into three plain user tables, created idempotently on first code_index:

  • _hdb_code.files — one row per file. Path, language, content hash.
  • _hdb_code.symbols — one row per definition. Name, kind, location, signature, parent file. v3.19 adds a lazy body_vec VECTOR(n) column populated by the indexer when an embedder is attached.
  • _hdb_code.symbol_refs — one row per use site. Source symbol, target symbol (or unresolved name), kind (CALLS, REFERENCES, IMPORTS), location.

These are not virtual tables and they are not magic. They are normal HeliosDB tables — queryable, joinable, branch-aware, time-travel-able. You can SELECT from them directly, you can join them against your application tables, you can index them for fast lookup.

The SQL Surface

The familiar LSP operations surface as SQL table functions. Each pushes its predicate through the existing FilteredScan path, so bloom-filter / zone-map / SIMD selection accelerates them automatically.

-- Where is this defined?
SELECT path, line, signature
FROM lsp_definition('execute_query');

-- Who uses this symbol?
SELECT caller_path, caller_line, caller_kind
FROM lsp_references(symbol_id);

-- Call hierarchy, BFS over CALLS edges
SELECT *
FROM lsp_call_hierarchy(symbol_id, 'incoming', 3);

-- Hover (signature lookup)
SELECT signature, doc_comment
FROM lsp_hover(symbol_id);

-- File outline, ordered by line, optional kind filter (v3.19)
SELECT *
FROM helios_lsp_document_symbols('src/auth.rs');

v3.19 Adds Write-Side Refactoring

Read-only code search is a useful feature; refactoring is a moat. v3.19 lands helios_lsp_rename_preview (preview-only edit list) and helios_lsp_rename_apply (write-back). Both are identifier-boundary aware. apply performs a sha256 conflict check — if file contents at the edit sites changed since the preview, the rename is rejected with a structured error.

-- Step 1: preview the edit list
SELECT path, line, before, after
FROM helios_lsp_rename_preview('old_name', 'new_name');

-- Step 2: apply (or dry-run)
SELECT applied, conflicts, files_changed
FROM helios_lsp_rename_apply(
  'old_name', 'new_name',
  dry_run := false
);

Time-Travel and Branch on Every Call

Every lsp_* table function accepts ON BRANCH '<name>' per-call override (v3.19) and an AS OF ref — in either order. Internally a RAII branch guard restores the prior branch on every early-return path.

-- Where was this defined three commits ago?
SELECT *
FROM lsp_definition('execute_query')
AS OF TIMESTAMP '2026-04-15T00:00:00Z';

-- ...on a feature branch
SELECT *
FROM lsp_definition('execute_query')
ON BRANCH 'feat/refactor'
AS OF TIMESTAMP '2026-04-15T00:00:00Z';

-- Diff helpers (v3.19)
SELECT *
FROM helios_lsp_references_diff('main', 'feat/refactor');

Multi-Language and the Runtime Grammar Registry

Eight languages ship out of the box: Rust, Python, TypeScript, TSX, JavaScript, Go, Markdown, SQL. The interesting part is the runtime grammar registry. Any caller can register additional grammars at runtime via register_grammar + register_extractor:

-- Inspect what's registered (v3.19 system view)
SELECT name, source FROM hdb_code_languages;
--  rust, python, typescript, tsx, javascript, go, markdown, sql -- static
--  java, kotlin -- runtime (caller-registered)

Runtime grammars can ship paired symbol extractors so they produce real symbols rather than empty parse trees.

v3.19's Scope-Chain Resolver

Cross-file resolution used to be best-effort. v3.19 ships a deterministic scope-chain resolver: unresolved CALLS / REFERENCES upgrade to Exact when an unambiguous IMPORTS edge in the same file ends in the bare name. Handles four import shapes natively:

  • Rust use foo::bar; → bar in this file means foo::bar.
  • Python from foo import bar → same.
  • TypeScript import { bar } from './foo'; → same.
  • Go imports → same.

In-Process Embeddings (v3.19)

The biggest practical lift in v3.19. Compile with --features code-embed and Nano ships fastembed-rs + BGESmallENV15 (384-dim, ~30 MB model cache on first run, no on-disk impact on the binary itself). The indexer populates the lazy body_vec VECTOR(n) column on each symbol; semantic-similar code search becomes a HNSW query against a regular table.

Pluggability remains: NoopEmbedder is the default for tests, HttpEmbedder hits any external POST {input} → {embedding} endpoint, and code-embed is the in-process option.

How This Compares

  • Sourcegraph Cody local index — bolt-on indexing daemon with its own storage, its own auth, its own update loop. Useful in its niche; nothing about it is queryable from your application code's database.
  • Cursor's local index — runs inside Cursor; not externally queryable; not time-travel-aware.
  • tree-sitter + custom queries — what most code-search projects build on top of. Code-Graph uses tree-sitter under the hood and adds the storage, the SQL surface, predicate pushdown, branching, time-travel, and the runtime grammar registry on top.

What's Next

The next post covers Graph-RAG — how code-graph projects into a universal cross-modal graph and the WITH CONTEXT clause that grounds LLM queries. After that, the MCP server.

HeliosDB Nano is open source under Apache 2.0. Code-graph lives behind the code-graph Cargo feature; default builds remain small.

← Back to Blog