The Drivine cow

DRIVINE

Graph database client for TypeScript, Python & the JVM

Community project by Liberation Data.

NewWatch: graph RAG on any database the video · 4:26

THE DIVINE DRIVER

Get ready! You’re about to obtain supernatural graph database powers.

Drivine maps graphs to objects by composition. Declare the view a use case needs, made of node fragments and the relationships between them, and Drivine writes the statement. When you would rather write the Cypher yourself, the persistence manager is still there.

Drivine in four and a half minutes: graph RAG on any database. With captions.

TYPESCRIPT · NODE.JS

Drivine

Decorated classes, a typed query DSL whose references come from the classes themselves, and a NestJS module, optional, with @InjectGraphObjectManager.

npm install @liberation-data/drivine

PYTHON · ASYNCIO

drivine-python

Async first, with a sync API written from it. Pydantic models, a query DSL that stubs generated from your models type, and assemblies with pi-pilgrim.

pip install drivine

KOTLIN · JAVA

drivine4j

The graph client for Embabel. Annotated models in Kotlin or Java, a DSL generated by KSP, and Spring Boot auto-configuration with test containers built in.

implementation("org.drivine:drivine4j:<version>")

One storage format. A store written from Node, Python or the JVM is read, stamped and checked alike from the others.

COMPOSITION OVER INHERITANCE

Compose views, not models.

A person can play many roles. Rather than one model that serves every use case, declare a view for each, and load it in a single statement.

  • @NodeFragment maps one node.
  • @GraphView composes fragments and relationships from a @Root.
  • The DSL’s references come from your classes, or from code generated from them: by KSP on the JVM, as stubs in Python.
@NodeFragment({ labels: ['Person'] })
export class Person {
    @NodeId() uuid: string;
    name: string;
    bio?: string;
}

@GraphView()
export class PersonCareer {
    @Root() person: Person;

    @GraphRelationship({ type: 'WORKS_FOR', target: () => WorkHistory })
    employmentHistory: WorkHistory[] = [];
}

// Everyone whose bio mentions leading
const leads = await gom.loadAll(PersonCareer, (q) => ({
    where: q.person.bio.containsIgnoreCase('lead')
}));

IN EVERY LIBRARY

Everything an object manager should do, and the Cypher when you want it.

A typed query DSL

Filters, ordering, keyset paging and index advice, accepted by loads, counts, deletes, patches and grouped reads.

Saves, patches and edges

save and saveAll with null policies and replace modes, update, create, patch, cascading deletes, relate and unrelate.

Optimistic locking

@NodeStamp refuses a save when the node changed since it was loaded, in a stamp format Node, Python and the JVM share.

Vector and full-text search

loadNearest and loadMatching on views and fragments, with one score scale on every engine.

Schema management

Range, uniqueness, vector and full-text indexes declared on your classes, enforced at startup, with drift detection.

Test containers, or yours

A container for each datasource in CI. While you iterate, USE_LOCAL_NEO4J=true points tests at your own instance.

CYPHER WHEN YOU NEED IT

Views where they’re easy. Cypher for the rest, on any store.

Load and save through the object manager wherever you can: Drivine writes the statement for each store. For your custom queries, give a persistence manager a Cypher statement. Neo4j, Memgraph, FalkorDB and Neptune run it as written. On Postgres, Oracle and SQLite, Drivine compiles it to SQL over two tables, one of nodes and one of relationships, and gives back the same rows.

How to run Cypher on a relational database →

Neo4jMemgraphFalkorDBNeptunePostgresOracleSQLite
MATCH p = (a)-[:KNOWS*]->(:Character {name: 'Jesse'})
          -[:APPEARS_IN]->(e {title: 'Pilot'})
WHERE a.name = 'Hank'
RETURN [n IN nodes(p) | coalesce(n.name, n.title)]

// The row, on Neo4j, Postgres and Oracle:
// ["Hank", "Walter", "Jesse", "Pilot"]
Outlined in orange: compiled to SQL. SQLite is drivine4j’s for now.
  • Reads and writes. A statement that writes is applied whole or not at all.
  • Variable-length relationships and quantified path patterns compile too.
  • Cypher the compiler doesn’t take yet is refused by name, before the store is sent anything.
  • One Cypher spec, run by all three libraries, says what each statement gives on each store.

PERFORMANCE

One statement, one round trip.

Drivine is built to be fast. There's no lazy loading, no session cache to keep warm and no query for each relationship. A view, with everything related to it, loads in one statement, and a save is one statement too.

Graph Bench uses drivine4j to compare Neo4j, FalkorDB, Oracle and Postgres on a graph of 1.1 million nodes and 4.5 million relationships, with the same Kotlin on each.

Postgres and Oracle have no index-free adjacency: a relationship is a row, found through a B-tree. Out to 275,000 paths, that walk cost them less for each path than Neo4j’s pointers did. What costs them is reading the nodes on the way. The numbers.

1.2–3.5 ms
to load an issue with its repo, author, assignees, topics and comments, on every engine
1
statement for each load or save, however many relationships it has
0.14 µs
for each path walked on Postgres and on Oracle, through an index, against 0.24 µs on Neo4j

DRIVINE STUDIO

See what a view loads.

Each library writes your model to .drivine/model.json:

  • drivine4j as it builds
  • Python from its stubs step
  • TypeScript with npx drivine model

Drivine Studio reads it: drag a view to the canvas and it loads as loadAll does, shows the Cypher, and draws what came back. Or type Cypher and see a graph or rows. It reads and never writes, in a browser or as a desktop app.

Drivine Studio: the catalogue of a model's views and fragments beside a canvas drawing issues, the people assigned to and raising them, and where they work
One view on the canvas: each issue, who it’s assigned to, who raised it and where they work, loaded in one statement.

Early days: run it from source.

Sponsored by Jasper Blues, creator of Typhoon, and Rod Johnson, creator of the Spring Framework and of Embabel, the AI agent framework for the JVM, which uses Drivine as its graph client.

ENGINES

Neo4jMemgraphFalkorDBOracle · SQL/PGQPostgres · SQLSQLite · SQLAmazon Neptune

Dashed: through the persistence manager. SQLite: drivine4j.

Up and running in five minutes.

Configure a connection, inject the object manager, load your first view.