Interstellar
Early Development Notice
Interstellar is in early development and is not recommended for production use. APIs may change without notice, and the project has not been audited for security or performance at scale.
A high-performance Rust graph database with dual query APIs: Gremlin-style fluent traversals and GQL (Graph Query Language).
Features
- Dual Query APIs: Gremlin-style fluent API and SQL-like GQL syntax
- Gremlin Text Parser: Execute TinkerPop-compatible Gremlin query strings
- GQL Schema & DDL: Define vertex/edge types with validation (
CREATE NODE TYPE,CREATE EDGE TYPE) - Dual Storage Backends: In-memory (HashMap-based) and memory-mapped (persistent)
- Anonymous Traversals: Composable fragments via the
__factory module - Zero-cost Abstractions: Monomorphized traversal pipelines
- Rich Predicate System:
eq,neq,gt,lt,within,containing,regex, and more - Path Tracking: Full path history with
as_()labels andselect() - Thread-Safe: All backends support concurrent read access
- Formal Verification: Critical code paths verified with Kani
Quick Start
Add to your Cargo.toml:
[]
= "0.1"
# With Gremlin text parser:
# interstellar = { version = "0.1", features = ["gremlin"] }
# With GQL query language:
# interstellar = { version = "0.1", features = ["gql"] }
# For persistent storage (memory-mapped files):
# interstellar = { version = "0.1", features = ["mmap"] }
# Everything enabled:
# interstellar = { version = "0.1", features = ["full"] }
Basic Usage
use *;
Storage Backends
Graph (Default)
In-memory graph with copy-on-write snapshots. Uses interior mutability for thread-safe mutations:
use *;
let graph = new; // No mut needed - interior mutability
let id = graph.add_vertex;
let snapshot = graph.snapshot; // Immutable point-in-time view
MmapGraph (Persistent Storage)
Memory-mapped persistent storage with write-ahead logging. Enable with the mmap feature:
[]
= { = "0.1", = ["mmap"] }
use *;
use MmapGraph;
// Open or create a database
let graph = open.unwrap;
// Add data (each operation is durable by default)
let alice = graph.add_vertex.unwrap;
Batch Mode (High-Performance Writes)
For bulk loading, use batch mode to defer fsync until commit (~500x faster):
use *;
use MmapGraph;
let graph = open.unwrap;
// Start batch mode
graph.begin_batch.unwrap;
// Add many vertices (no fsync per operation)
for i in 0..100_000
// Single fsync commits all operations atomically
graph.commit_batch.unwrap;
MmapGraph Features
- Durability: Write-ahead logging ensures crash recovery
- Efficient Reads: Memory-mapped access with zero-copy reads
- Slot Reuse: Deleted vertex/edge slots are reused
- String Interning: Compact storage for repeated strings
- Label Indexes: RoaringBitmap indexes for fast label filtering
Traversal API
Source Steps
g.v // All vertices
g.e // All edges
g.v_ids // Specific vertices by ID
g.e_ids // Specific edges by ID
g.inject // Inject arbitrary values
Navigation Steps
.out // Outgoing edges (optional label filter)
.in_ // Incoming edges
.both // Both directions
.out_e // Outgoing edge objects
.in_e // Incoming edge objects
.both_e // Both edge objects
.out_v // Source vertex of edge
.in_v // Target vertex of edge
.other_v // Opposite vertex of edge
Filter Steps
.has // Has property
.has_not // Missing property
.has_value // Property equals value
.has_where // Property matches predicate
.has_label // Filter by label
.has_id // Filter by ID
.is_ // Filter values by predicate
.is_eq // Filter values equal to
.dedup // Remove duplicates
.limit // Take first N
.skip // Skip first N
.range // Take elements 5-9
.filter // Custom filter function
.simple_path // Only non-cyclic paths
.cyclic_path // Only cyclic paths
Transform Steps
.values // Extract property value
.values_multi // Extract multiple properties
.label // Get element label
.id // Get element ID
.constant // Replace with constant
.map // Transform values
.flat_map // Transform and flatten
.path // Get traversal path
.value_map // All properties as map
.value_map_selected // Selected properties as map
.element_map // Properties + id + label
.unfold // Flatten collections
Branch Steps
.union // Merge multiple traversals
.coalesce // First non-empty traversal
.choose // Conditional branching
.optional // Include if exists
.and_ // All conditions must match
.or_ // Any condition matches
.not_ // Negation
.where_ // Filter by sub-traversal
.local // Apply per-element
Repeat Steps
.repeat
.times // Fixed iterations
.until // Stop condition
.emit // Emit intermediates
.emit_if // Conditional emit
Aggregation Steps
.group // Group by identity
.group_by_label // Group by element label
.group_count // Count per group
.group_count_by // Count by property
Path and Labels
.as_ // Label current position
.select // Retrieve labeled value
.select_multi // Multiple labels
.path // Full traversal path
Terminal Steps
.to_list // Collect all results
.to_set // Collect unique results
.count // Count results
.sum // Sum numeric values
.min // Find minimum
.max // Find maximum
.fold // Collect into single list
.next // First result (Option)
.one // Exactly one result (Result)
.has_next // Check if results exist
.iterate // Consume without collecting
Predicates
The p module provides rich predicates for filtering:
use p;
eq // Equals
neq // Not equals
gt // Greater than
gte // Greater than or equal
lt // Less than
lte // Less than or equal
between // In range [20, 40)
inside // In range (20, 40)
outside // Outside range
within // In set
without // Not in set
containing // String contains
starting_with // String prefix
ending_with // String suffix
regex // Regex match
and_ // Logical AND
or_ // Logical OR
not_ // Logical NOT
Anonymous Traversals
The __ module provides anonymous traversal fragments for composition:
use __;
// Use in branch steps
g.v.union;
// Use in predicates
g.v.where_;
// Use in repeat
g.v.repeat.until;
GQL (Graph Query Language)
Interstellar includes a full GQL implementation with SQL-like syntax for querying and mutating graphs. Enable with the gql feature:
[]
= { = "0.1", = ["gql"] }
Basic Queries
use *;
let snapshot = graph.snapshot;
// Pattern matching with MATCH
let results = snapshot.gql.unwrap;
// Aggregation
let results = snapshot.gql.unwrap;
Mutations
use execute_mutation;
// Create vertices and edges
execute_mutation.unwrap;
// Update properties
execute_mutation.unwrap;
// MERGE (create if not exists)
execute_mutation.unwrap;
Schema Definition (DDL)
Define vertex and edge types with validation:
-- Create vertex type with property constraints
CREATE NODE TYPE Person (
name STRING NOT NULL,
age INT,
email STRING,
active BOOL DEFAULT true
)
-- Create edge type with endpoint constraints
CREATE EDGE TYPE WORKS_AT (
since INT,
role STRING
) FROM Person TO Company
-- Set validation mode
SET SCHEMA VALIDATION STRICT
Advanced Features
- UNION / UNION ALL: Combine query results
- OPTIONAL MATCH: Left outer join semantics
- EXISTS / NOT EXISTS: Subquery predicates
- CASE expressions: Conditional logic
- List comprehensions:
[x IN list WHERE x > 0 | x * 2] - Pattern comprehension:
[(p)-[:FRIEND]->(f) | f.name] - FOREACH: Iterate and mutate:
FOREACH (n IN nodes(path) | SET n.visited = true) - REDUCE: Fold over lists:
REDUCE(sum = 0, x IN list | sum + x) - CALL subqueries: Correlated and uncorrelated subqueries
Gremlin Text Parser
Interstellar includes a TinkerPop-compatible Gremlin query string parser. Enable with the gremlin feature:
[]
= { = "0.1", = ["gremlin"] }
Basic Queries
use *;
let graph = new;
// ... add vertices and edges ...
// Execute Gremlin query strings directly
let result = graph.query.unwrap;
// Or use a snapshot
let snapshot = graph.snapshot;
let result = snapshot.query.unwrap;
Supported Steps
The parser supports the full range of Gremlin steps:
// Navigation
g.V().out('knows').in('created').both('uses')
g.V().outE('knows').inV().otherV()
// Filtering
g.V().hasLabel('person').has('age', P.gt(30))
g.V().has('name', P.within('Alice', 'Bob'))
g.V().has('email', TextP.containing('@gmail'))
g.V().where(__.out('knows').count().is(P.gt(3)))
// Transform
g.V().values('name', 'age')
g.V().valueMap(true) // Include id and label
g.V().project('name', 'friends').by('name').by(__.out('knows').count())
// Branching
g.V().union(__.out('knows'), __.out('created'))
g.V().choose(__.hasLabel('person'), __.out('knows'), __.out('uses'))
g.V().coalesce(__.out('preferred'), __.out('default'))
// Repeat
g.V().repeat(__.out('parent')).times(3)
g.V().repeat(__.out()).until(__.hasLabel('root')).emit()
// Aggregation
g.V().hasLabel('person').order().by('age', desc).limit(10)
g.V().group().by('city')
g.V().groupCount().by('label')
// Side effects
g.V().as('a').out().as('b').select('a', 'b')
g.V().aggregate('x').out().where(P.within('x'))
// Terminal
g.V().toList()
g.V().next()
g.V().count()
g.V().hasNext()
Math Expressions
When both gremlin and gql features are enabled, mathematical expressions are supported:
// Requires features = ["gremlin", "gql"]
let result = graph.query.unwrap;
Predicates
Full predicate support including:
- Comparison:
P.eq(),P.neq(),P.gt(),P.gte(),P.lt(),P.lte() - Range:
P.between(),P.inside(),P.outside() - Membership:
P.within(),P.without() - Text:
TextP.containing(),TextP.startingWith(),TextP.endingWith(),TextP.regex() - Logical:
P.and(),P.or(),P.not()
Mutations
Note: The
graph.query()andsnapshot.query()methods are read-only. Mutation queries (addV, addE, property, drop) will parse and compile but return placeholder values instead of executing.
For mutations, use the Rust fluent API:
use *;
use Arc;
let graph = new;
// Use gremlin() with Arc for write access
let g = graph.gremlin;
// Mutations execute immediately
let alice = g.add_v.property.next;
let bob = g.add_v.property.next;
// Create edge between them
if let =
// Read queries work normally
let count = g.v.count; // 2
Examples
Run the included examples:
# Gremlin-style traversals
# GQL queries (requires gql feature)
# Persistence (requires mmap feature)
Features
| Feature | Description | Default |
|---|---|---|
graphson |
GraphSON import/export | Yes |
mmap |
Memory-mapped persistent storage | No |
gql |
GQL query language | No |
gremlin |
Gremlin text query parser | No |
full-text |
Full-text search (Tantivy) | No |
wasm |
WebAssembly/JavaScript bindings | No |
full |
Enable all features (except wasm) | No |
In-memory graph storage is always available as core functionality.
WebAssembly Support
Enable the wasm feature to compile Interstellar for browsers and Node.js:
[]
= { = "0.1", = ["wasm"] }
Build with wasm-pack:
Usage in JavaScript/TypeScript:
import init, { Graph, P, __ } from 'interstellar-graph';
await init();
const graph = new Graph();
const alice = graph.addVertex('person', { name: 'Alice', age: 30n });
const bob = graph.addVertex('person', { name: 'Bob', age: 25n });
graph.addEdge(alice, bob, 'knows', { since: 2020n });
// Gremlin-style traversal
const friends = graph.V([alice])
.outLabels(['knows'])
.values('name')
.toList();
console.log(friends); // ['Bob']
// With predicates
const adults = graph.V()
.hasLabel('person')
.hasWhere('age', P.gte(18n))
.values('name')
.toList();
// With anonymous traversals
const withFriends = graph.V()
.hasLabel('person')
.where_(__.out()) // Has at least one outgoing edge
.toList();
See spec-45-wasm-bindgen.md for full API documentation.
Building
Testing
Note: The
full-textfeature (tantivy) has a known upstream dependency conflict and is excluded from the full test suite.
WASM Testing
The wasm feature requires browser or Node.js testing via wasm-pack:
# Install wasm-pack (one-time setup)
|
# Run tests in headless browser
# Run tests in Node.js
Benchmarks
Formal Verification
Interstellar uses Kani for formal verification of critical code paths. Kani exhaustively checks all possible inputs within defined bounds, providing mathematical proofs of correctness.
Verified Properties
- Packed struct layout: All
#[repr(C, packed)]structs match their size constants - Serialization roundtrips: FileHeader, NodeRecord, EdgeRecord serialize/deserialize correctly
- Type conversions: Value type conversions preserve data within safe ranges
- Offset calculations: File offset arithmetic cannot overflow for reasonable capacities
- Data structure invariants: FreeList and ArenaAllocator maintain their contracts
Running Verification
# Install Kani (one-time setup)
# Run all proofs
# Run specific proof
# Run with verbose output
Proof Harnesses
| Category | Proof | Description |
|---|---|---|
| Records | verify_struct_sizes_match_constants |
All packed struct sizes match constants |
| Records | verify_file_header_roundtrip |
FileHeader serialization roundtrip |
| Records | verify_node_record_roundtrip |
NodeRecord serialization roundtrip |
| Records | verify_edge_record_roundtrip |
EdgeRecord serialization roundtrip |
| Value | verify_u64_to_value_safe_range |
Safe u64 to Value conversion |
| Value | verify_i64_to_value |
i64 to Value conversion |
| Value | verify_bool_to_value |
bool to Value conversion |
| Offset | verify_node_offset_no_overflow |
Node offset calculation safety |
| Offset | verify_edge_offset_no_overflow |
Edge offset calculation safety |
| FreeList | verify_freelist_push_pop |
FreeList LIFO behavior |
| Arena | verify_arena_allocation_bounded |
Arena bounds checking |
Documentation
See the docs/ directory for comprehensive documentation:
- Getting Started - Installation, quick start, and examples
- API Reference - Gremlin, GQL, and predicate reference
- Concepts - Architecture, storage, traversal model
- Guides - Graph modeling, querying, mutations, performance
- Reference - Value types, error handling, feature flags, glossary
License
MIT
Development Approach
This project uses spec-driven development with AI assistance. Most code is generated or reviewed by LLMs (primarily Claude Opus 4.5). While we aim for high quality and test coverage, this approach is experimental.