froGQL knight-frog mascot holding a shield with a graph network
ISO GQL · written in Rust

froGQL

A single-file graph database that speaks ISO GQL. Worst-case-optimal joins, a real typechecker, and one .gdb file you can carry anywhere — the SQLite of property graphs.

$ pip install frogql
4097×
LTJ join speedup
8.7 ms
LDBC IC2 (SF0.1)
1 file
portable .gdb storage
4
targets: CLI · Py · Node · WASM
// live in your browser

Run GQL right here

This page loads the real frogql-wasm engine and a 171-node movies graph. Nothing is sent to a server — the database lives in your tab.

loading frogql-wasm… graph nodes · edges labels
query.gql
result ready
Press Run to execute the query.

Try INSERT, SET, DELETE too — mutations apply in-memory. Reload the page to reset the graph.

// the REPL

Query graphs like you query SQLite

Open a database, ask a path-pattern question, get rows back. No server, no setup ceremony.

frogql movies.gdb
# open an existing single-file database $ frogql movies.gdb gql> MATCH (p:Person) -[:ACTED_IN]-> (m:Movie) . WHERE m.released = 1999 . RETURN p.name, m.title p.name | m.title -------------------+------------ "Keanu Reeves" | "The Matrix" "Carrie-Anne Moss" | "The Matrix" "Laurence Fishburne"| "The Matrix" gql> -- comma-joins run on a worst-case-optimal join . MATCH (a)-[:ACTED_IN]->(m), (d)-[:DIRECTED]->(m) . RETURN a.name, d.name

The full ISO GQL surface

froGQL parses, elaborates, typechecks, optimizes, and runs. Path patterns are first-class: concatenation, union, repetition, and filters compose freely.

  • MATCH / OPTIONAL MATCH, comma-joins, WHERE, RETURN, EXISTS
  • Repetition {n,m}, label algebra, and a lattice-based typechecker
  • INSERT / SET / REMOVE / DELETE (ISO §13) with an in-RAM overlay until .save
  • Persistent named graph types with CREATE / USE / VALIDATE
// why froGQL

A graph database forged for speed

Single-file storage meets a worst-case-optimal join engine and embeddings for every runtime you ship to.

Single-file storage

Everything lives in one .gdb file with 4 KB slotted pages — SQLite's portability for property graphs. Copy it, ship it, version it.

Worst-case-optimal joins

Comma-joins and chains run on Leapfrog Triejoin: edges as triples in six sorted orderings, variables bound one at a time. 14×–4097× over pairwise hash-join.

Real typechecker

A lattice-based type system with meet, join, and subtyping checks every query against the active graph type before it runs.

Bindings everywhere

One Rust core, four faces: a frogql CLI, a PyPI wheel, an npm native module, and a WASM build that runs in the browser.

Auto + declared indexes

Hash and B-tree indexes auto-build on every uniquely-keyed property at open. Declare more with CREATE INDEX — persisted in the file header.

Schemas you can validate

Named graph types persist alongside the data. The reserved DEFAULT type is inferred from your graph automatically at import.

// born from research

From a theorem to a database

froGQL started as a research prototype and grew into a working engine. We are transparent about where AI helped and where it did not.

The differentiator is a typechecker grounded in a gradual type system for GQL: it detects queries that cannot return results because of type mismatches, before they run.

We mix hand-written research code with AI-assisted engineering. The formal core was written by hand; AI helped port and scale it. Each step below is tagged with how it was built.
  1. 1

    Theory first research

    The project grew out of our paper on a gradual type system for GQL: its formalization and soundness theorems. The central result is Emptiness — statically detecting queries guaranteed to return nothing because of type mismatches.

  2. 2

    A hand-written prototype by hand, no AI

    We implemented the typechecker and the runtime semantics of the formal model in Python, by hand. This is the reference everything else is checked against.

  3. 3

    The question worth testing research

    Does the typechecker earn its cost? Is typecheck + run worthwhile against blindly running a query only to find it returns nothing? Answering this needed performance Python could not give.

  4. 4

    Port to Rust AI-assisted

    We rewrote the engine in Rust with AI assistance, taking inspiration from SQLite's single-file storage architecture.

  5. 5

    Worst-case-optimal joins AI + paper

    Joins were slow. Colleagues in databases pointed us to the CompactLTJ paper and its C++ reference. We implemented Leapfrog Triejoin with generative AI, keeping the test suite green throughout.

  6. 6

    LDBC and language growth careful by hand

    To run the LDBC Social Network Benchmark we extended the language with more features, including our centerpiece, the typechecker — carefully, to avoid breaking the theorems we had proved formally.

  7. 7

    Where AI stopped being enough design + research

    The benchmarks were strong on some fronts and weak on others. Closing the gaps took design decisions and research that AI could not deliver alone: LTJ interacts poorly with GQL features like UNION, and even with plain ORDER BY. We grow the system slowly, testing several strategies before each feature lands.

Benchmark charts from the LDBC Social Network Benchmark are coming here as the numbers stabilize.

// install

Pick your runtime

froGQL ships to PyPI, npm (native + WASM), and as a standalone Rust CLI.

# Wheels for CPython 3.8+ on Linux, macOS, Windows
pip install frogql

# then, in Python
import frogql
conn = frogql.open("movies.gdb")
rows = conn.execute("MATCH (p:Person) RETURN p.name", limit=10)
# build the frogql binary from source
git clone https://github.com/pleiad/frogql
cd frogql && cargo build --release

# import a CSV dataset and open the REPL
./target/release/frogql movies.gdb --import-csv path/to/csv_dir/
# native addon, prebuilt for mac / linux / windows
npm install frogql

# then, in JS
const frogql = require("frogql");
const conn = frogql.open("movies.gdb");
conn.execute("MATCH (p:Person) RETURN p.name", 10);
# runs entirely in the browser, no server
npm install frogql-wasm

import init, { open_json } from "frogql-wasm";
await init();
const conn = open_json(graphJsonString);
conn.execute("MATCH (n) RETURN n", 10);