levelgraph

package module
v0.0.0-...-fc2b521 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 28, 2025 License: MIT Imports: 18 Imported by: 0

README

LevelGraph

Logo

LevelGraph is a Graph Database built on the ultra-fast key-value store LevelDB. This is a Go port of the original JavaScript LevelGraph.

LevelGraph uses the Hexastore approach as presented in the article: Hexastore: sextuple indexing for semantic web data management (C. Weiss, P. Karras, A. Bernstein - Proceedings of the VLDB Endowment, 2008). Following this approach, LevelGraph uses six indices for every triple, enabling extremely fast pattern matching queries.

Design Trade-offs

Hexastore Indexing: LevelGraph creates 6 index entries per triple (SPO, SOP, POS, PSO, OPS, OSP). This 6x write amplification is a deliberate trade-off:

Aspect Hexastore Approach
Write Speed Slower (6 writes per triple)
Storage Size Larger (6x index overhead)
Read Speed Very fast - O(1) lookups by any S/P/O combination
Query Flexibility Query by any combination without full scans

This makes LevelGraph ideal for read-heavy workloads where you need flexible querying patterns.

Binary Data ([]byte): All triple components (subject, predicate, object) are stored as []byte:

  • Supports arbitrary binary data, not just strings
  • Avoids encoding/decoding overhead
  • Consistent with LevelDB's native key/value types
  • Use NewTripleFromStrings() for convenient string handling

Installation

go get github.com/benbenbenbenbenben/levelgraph

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/benbenbenbenbenben/levelgraph"
)

func main() {
    ctx := context.Background()

    // Open a database
    db, err := levelgraph.Open("./mydb")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Insert a triple
    triple := levelgraph.NewTripleFromStrings("alice", "knows", "bob")
    if err := db.Put(ctx, triple); err != nil {
        log.Fatal(err)
    }

    // Query by subject using NewPattern (nil = wildcard)
    results, err := db.Get(ctx, levelgraph.NewPattern("alice", nil, nil))
    if err != nil {
        log.Fatal(err)
    }

    for _, t := range results {
        fmt.Printf("%s %s %s\n", t.Subject, t.Predicate, t.Object)
    }
}

Features

  • Hexastore Indexing: Six indexes for every triple enable fast lookups by any combination of subject, predicate, and object
  • Pattern Matching: Query triples using flexible patterns with variables
  • Search/Join: Multi-pattern joins for complex graph queries
  • Navigator API: Fluent API for graph traversal
  • Journalling: Record all write operations for audit trails and replication
  • Facets: Attach properties to subjects, predicates, objects, or entire triples
  • Binary Data Support: Store arbitrary []byte data in triples
  • Vector Search: Semantic similarity search using vector embeddings (HNSW)
  • Hybrid Search: Combine graph traversal with vector similarity

API Reference

Opening a Database
// Basic open
db, err := levelgraph.Open("/path/to/db")

// With options
db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithJournal(),   // Enable journalling
    levelgraph.WithFacets(),    // Enable facets
)

// With structured logging
import "log/slog"

logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithLogger(logger),
)
Triples

Triples are the fundamental unit of data:

// Create from byte slices
triple := levelgraph.NewTriple([]byte("subject"), []byte("predicate"), []byte("object"))

// Create from strings (convenience)
triple := levelgraph.NewTripleFromStrings("alice", "knows", "bob")
Put and Delete
ctx := context.Background()

// Insert single triple
err := db.Put(ctx, triple)

// Insert multiple triples
err := db.Put(ctx, t1, t2, t3)

// Delete triple
err := db.Del(ctx, triple)
Get (Query)

Query triples using patterns. Use NewPattern(subject, predicate, object) where:

  • nil or "" = wildcard (match any value)
  • string or []byte = exact match
  • levelgraph.V("name") = variable binding
ctx := context.Background()

// Get by subject (nil = wildcard)
results, err := db.Get(ctx, levelgraph.NewPattern("alice", nil, nil))

// Get by predicate and object
results, err := db.Get(ctx, levelgraph.NewPattern(nil, "knows", "bob"))

// With limit and offset
pattern := levelgraph.NewPattern("alice", nil, nil)
pattern.Limit = 10
pattern.Offset = 5
results, err := db.Get(ctx, pattern)

// With filter
pattern := levelgraph.NewPattern("alice", nil, nil)
pattern.Filter = func(t *levelgraph.Triple) bool {
    return string(t.Object) != "eve"
}
results, err := db.Get(ctx, pattern)

// Reverse order
pattern := levelgraph.NewPattern("alice", nil, nil)
pattern.Reverse = true
results, err := db.Get(ctx, pattern)
Search (Join)

Perform multi-pattern joins using variables. Use levelgraph.V("name") to create variables that capture matched values:

ctx := context.Background()

// Find friends of friends
results, err := db.Search(ctx, []*levelgraph.Pattern{
    levelgraph.NewPattern("alice", "knows", levelgraph.V("x")),
    levelgraph.NewPattern(levelgraph.V("x"), "knows", levelgraph.V("y")),
}, nil)

// Each result is a Solution map[string][]byte
for _, sol := range results {
    fmt.Printf("x=%s, y=%s\n", sol["x"], sol["y"])
}

// With options
results, err := db.Search(ctx, patterns, &levelgraph.SearchOptions{
    Limit:  10,
    Offset: 0,
    Filter: func(s levelgraph.Solution) bool {
        return string(s["x"]) != "eve"
    },
})
Navigator API

Fluent API for graph traversal:

// Find friends of alice
values, err := db.Nav("alice").
    ArchOut("knows").
    Values()

// Find who knows alice
values, err := db.Nav("alice").
    ArchIn("knows").
    Values()

// Chain multiple traversals
values, err := db.Nav("alice").
    ArchOut("knows").       // alice -> knows -> ?
    ArchOut("knows").       // ? -> knows -> ?
    Values()

// Name intermediate vertices
solutions, err := db.Nav("alice").
    ArchOut("knows").
    As("friend").
    ArchOut("likes").
    As("liked").
    Solutions()

// Bind to specific value
values, err := db.Nav("alice").
    ArchOut("knows").
    Bind("bob").            // Must match "bob"
    ArchOut("knows").
    Values()

// Check existence
exists, err := db.Nav("alice").ArchOut("knows").Exists()

// Count results
count, err := db.Nav("alice").ArchOut("knows").Count()

// Clone navigator for branching
nav := db.Nav("alice").ArchOut("knows")
nav1 := nav.Clone().ArchOut("likes")
nav2 := nav.Clone().ArchOut("follows")
Iterators

For large result sets, use iterators:

ctx := context.Background()

// Triple iterator
iter, err := db.GetIterator(ctx, levelgraph.NewPattern("alice", nil, nil))
if err != nil {
    log.Fatal(err)
}
defer iter.Release()

for iter.Next() {
    triple, err := iter.Triple()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(triple)
}

// Search iterator
iter, err := db.SearchIterator(ctx, patterns, nil)
if err != nil {
    log.Fatal(err)
}
defer iter.Close()

for iter.Next() {
    solution := iter.Solution()
    fmt.Println(solution)
}
Journalling

When enabled, all write operations are recorded:

// Open with journalling
db, err := levelgraph.Open("/path/to/db", levelgraph.WithJournal())

// Get journal entries
entries, err := db.GetJournalEntries(time.Time{})  // All entries
entries, err := db.GetJournalEntries(since)        // Entries after timestamp

// Count entries
count, err := db.JournalCount(time.Time{})

// Trim old entries
trimmed, err := db.Trim(before)

// Export and trim
exported, err := db.TrimAndExport(before, archiveDB)

// Replay journal to another database
replayed, err := db.ReplayJournal(after, targetDB)
Facets

Attach properties to graph components:

// Open with facets
db, err := levelgraph.Open("/path/to/db", levelgraph.WithFacets())

// Component facets (on subjects, predicates, or objects)
err = db.SetFacet(levelgraph.FacetSubject, []byte("alice"), []byte("age"), []byte("30"))
value, err := db.GetFacet(levelgraph.FacetSubject, []byte("alice"), []byte("age"))
facets, err := db.GetFacets(levelgraph.FacetSubject, []byte("alice"))
err = db.DelFacet(levelgraph.FacetSubject, []byte("alice"), []byte("age"))

// Triple facets (on entire triples)
triple := levelgraph.NewTripleFromStrings("alice", "knows", "bob")
err = db.SetTripleFacet(triple, []byte("since"), []byte("2020"))
value, err := db.GetTripleFacet(triple, []byte("since"))
facets, err := db.GetTripleFacets(triple)
err = db.DelTripleFacet(triple, []byte("since"))
err = db.DelAllTripleFacets(triple)

LevelGraph supports semantic similarity search using vector embeddings. This enables "fuzzy" queries based on meaning rather than exact matches.

Basic Setup
import (
    "github.com/benbenbenbenbenben/levelgraph"
    "github.com/benbenbenbenbenben/levelgraph/vector"
    "github.com/benbenbenbenbenben/levelgraph/vector/luxical"
)

// Load a text embedding model (Luxical produces 192-dim embeddings)
embedder, err := luxical.NewEmbedder("./models/luxical")
if err != nil {
    log.Fatal(err)
}
defer embedder.Close()

// Create a vector index matching the embedder dimensions
index := vector.NewHNSWIndex(embedder.Dimensions())

// Open database with vector support and auto-embedding
db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithVectors(index),
    levelgraph.WithAutoEmbed(embedder, levelgraph.AutoEmbedObjects),
)
Manual Vector Operations
ctx := context.Background()

// Set a vector manually
vec := []float32{0.1, 0.2, 0.3, ...} // 192 dimensions
id := vector.MakeID(vector.IDTypeObject, []byte("tennis"))
db.SetVector(ctx, id, vec)

// Get a vector
vec, err := db.GetVector(ctx, id)

// Search for similar vectors
results, err := db.SearchVectors(ctx, queryVec, 10)
for _, match := range results {
    fmt.Printf("ID: %s, Score: %.3f\n", match.ID, match.Score)
}

// Search by text (requires embedder)
results, err := db.SearchVectorsByText(ctx, "racket sports", 10)
Hybrid Search (Graph + Vectors)

Combine graph pattern matching with vector similarity:

// Find people who like topics similar to "machine learning"
solutions, err := db.Search(ctx, []*levelgraph.Pattern{
    levelgraph.NewPattern(levelgraph.V("person"), "likes", levelgraph.V("topic")),
}, &levelgraph.SearchOptions{
    VectorFilter: &levelgraph.VectorFilter{
        Variable:  "topic",
        QueryText: "machine learning",  // Will be embedded
        TopK:      10,                   // Top 10 similar topics
        MinScore:  0.7,                  // Filter low similarity
        IDType:    vector.IDTypeObject,
    },
})

for _, sol := range solutions {
    score := levelgraph.GetVectorScore(sol)
    fmt.Printf("%s likes %s (score: %.3f)\n", sol["person"], sol["topic"], score)
}
Async Auto-Embedding

For better performance with real embedding models, enable async embedding:

db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithVectors(index),
    levelgraph.WithAutoEmbed(embedder, levelgraph.AutoEmbedObjects),
    levelgraph.WithAsyncAutoEmbed(100),  // Buffer size
)

// Add triples (embedding happens in background)
for _, triple := range triples {
    db.Put(ctx, triple)
}

// Wait for all embeddings before searching
err = db.WaitForEmbeddings(ctx)
HNSW Parameter Tuning
// High-speed, lower recall (~95%)
index := vector.NewHNSWIndex(192,
    vector.WithM(12),
    vector.WithEfConstruction(100),
    vector.WithEfSearch(30),
)

// Balanced (default, ~98% recall)
index := vector.NewHNSWIndex(192,
    vector.WithM(16),
    vector.WithEfConstruction(200),
    vector.WithEfSearch(50),
)

// High-recall (~99.5%)
index := vector.NewHNSWIndex(192,
    vector.WithM(32),
    vector.WithEfConstruction(400),
    vector.WithEfSearch(200),
)
Score Interpretation
  • 1.0: Identical vectors (perfect match)
  • 0.7-0.9: Highly similar (typically good matches)
  • 0.5-0.7: Moderately similar
  • 0.0-0.5: Dissimilar

Web Playground (WASM)

LevelGraph can be compiled to WebAssembly and run directly in the browser. A playground is included for interactive experimentation.

Building the Playground
# Build WASM module and start local server (standard Go, 3.8MB)
make serve

# Build with TinyGo for smaller binary (1.5MB, ~60% smaller)
make serve-tinygo

Then open http://localhost:8080 in your browser.

Makefile Targets
make wasm             # Build WASM module (standard Go)
make wasm-tinygo      # Build WASM module (TinyGo, smaller)
make playground       # Build WASM + copy wasm_exec.js
make playground-tinygo # Build TinyGo WASM + copy wasm_exec_tinygo.js
make serve            # Build and start local server
make serve-tinygo     # Build TinyGo version and start server

The playground UI allows switching between builds via a dropdown menu.

WASM API

When loaded in a browser, the following JavaScript API is available:

// Insert triples
levelgraph.put([
    { subject: "alice", predicate: "knows", object: "bob" },
    { subject: "bob", predicate: "knows", object: "charlie" }
]);

// Delete triples
levelgraph.del([
    { subject: "alice", predicate: "knows", object: "bob" }
]);

// Query by pattern (use null for wildcards)
const results = levelgraph.get({ subject: "alice", predicate: null, object: null });

// Search with variables (prefix with ?)
const friends = levelgraph.search([
    { subject: "alice", predicate: "knows", object: "?friend" },
    { subject: "?friend", predicate: "knows", object: "?fof" }
]);

// Search with filters
const results = levelgraph.search([
    { subject: "?person", predicate: "knows", object: "?other" }
], {
    notEqual: [{ var: "person", var2: "other" }]  // person != other
});

// Navigation API
const nav = levelgraph.nav({
    start: "alice",
    steps: [
        { direction: "out", predicate: "knows", as: "friend" },
        { direction: "out", predicate: "likes", as: "liked" }
    ]
});

// Reset database
levelgraph.reset();

// Check if ready
if (levelgraph.isReady()) { /* ... */ }

The playground includes several example presets demonstrating these features.

Binary Data Support

LevelGraph stores all data as []byte, supporting arbitrary binary data:

// Binary data in triples
subject := []byte{0x00, 0x01, 0x02}
predicate := []byte("hasData")
object := []byte{0xAA, 0xBB, 0xCC}

triple := levelgraph.NewTriple(subject, predicate, object)
db.Put(triple)

Benchmarks

Run benchmarks:

go test -bench=. -benchmem

Example results (AMD Ryzen Threadripper 3960X):

BenchmarkPut-24               271693    11143 ns/op    3346 B/op    29 allocs/op
BenchmarkGet-24               431667     5337 ns/op    3344 B/op    81 allocs/op
BenchmarkSearch-24            479586     4628 ns/op    4520 B/op    70 allocs/op
BenchmarkSearchJoin-24         81771    29709 ns/op   26032 B/op   386 allocs/op
BenchmarkNavigator-24          72498    33393 ns/op   27549 B/op   424 allocs/op

Testing

go test ./...

Credits

This Go port builds on the excellent work of the original JavaScript LevelGraph by Matteo Collina and contributors.

LevelGraph builds on LevelDB from Google, accessed via goleveldb.

License

MIT License - see LICENSE file.

Copyright (c) 2013-2025 Matteo Collina and LevelGraph Contributors Copyright (c) 2025 Benjamin Babik and LevelGraph Go Contributors

Documentation

Overview

Package levelgraph provides a graph database built on top of LevelDB.

LevelGraph uses the Hexastore approach with six indexes for every triple, enabling fast pattern matching queries on subject, predicate, and object.

Basic usage:

db, err := levelgraph.Open("/path/to/db")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Insert a triple
err = db.Put(levelgraph.NewTripleFromStrings("alice", "knows", "bob"))

// Query triples
triples, err := db.Get(&levelgraph.Pattern{
    Subject: []byte("alice"),
})

With features enabled:

db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithJournal(),
    levelgraph.WithFacets(),
)

For WebAssembly builds, use OpenWithStore with NewMemStore:

store := levelgraph.NewMemStore()
db := levelgraph.OpenWithStore(store)
Example

Example demonstrates basic LevelGraph usage: opening a database, storing triples, and querying them.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
)

func main() {
	// Create a temporary directory for the database
	dir, err := os.MkdirTemp("", "levelgraph-example")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	// Open the database
	db, err := levelgraph.Open(filepath.Join(dir, "example.db"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	// Insert triples
	err = db.Put(context.Background(),
		graph.NewTripleFromStrings("alice", "knows", "bob"),
		graph.NewTripleFromStrings("bob", "knows", "charlie"),
	)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Query by subject
	triples, err := db.Get(context.Background(), &graph.Pattern{Subject: graph.ExactString("alice")})
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	for _, t := range triples {
		fmt.Printf("%s %s %s\n", t.Subject, t.Predicate, t.Object)
	}
}
Output:
alice knows bob
Example (Facets)

Example_facets demonstrates attaching metadata to triples and their components.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-facets")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	// Open with facets enabled
	db, err := levelgraph.Open(filepath.Join(dir, "facets.db"), levelgraph.WithFacets())
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	ctx := context.Background()

	// Add a triple
	triple := graph.NewTripleFromStrings("alice", "knows", "bob")
	db.Put(ctx, triple)

	// Add facet to the triple itself (relationship metadata)
	err = db.SetTripleFacet(ctx, triple, []byte("since"), []byte("2020"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Add facet to a subject (entity metadata)
	err = db.SetFacet(ctx, levelgraph.FacetSubject, []byte("alice"), []byte("age"), []byte("30"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Retrieve the facets
	since, err := db.GetTripleFacet(ctx, triple, []byte("since"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	age, err := db.GetFacet(ctx, levelgraph.FacetSubject, []byte("alice"), []byte("age"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Printf("Alice (age %s) knows Bob since %s\n", age, since)
}
Output:
Alice (age 30) knows Bob since 2020
Example (HybridSearch)

Example_hybridSearch demonstrates combining graph pattern matching with vector similarity search (hybrid search). This enables finding related graph entities based on semantic similarity.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
	"github.com/benbenbenbenbenben/levelgraph/vector"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-example-hybrid")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	// Create a vector index (3 dimensions for this simple example)
	vectorIndex := vector.NewFlatIndex(3)

	db, err := levelgraph.Open(filepath.Join(dir, "hybrid.db"),
		levelgraph.WithVectors(vectorIndex),
	)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	ctx := context.Background()

	// Build a graph of people and their favorite sports
	db.Put(ctx,
		graph.NewTripleFromStrings("alice", "likes", "tennis"),
		graph.NewTripleFromStrings("alice", "likes", "badminton"),
		graph.NewTripleFromStrings("bob", "likes", "football"),
		graph.NewTripleFromStrings("charlie", "likes", "tennis"),
	)

	// Associate vectors with sports (simulated embeddings)
	// Racket sports (tennis, badminton) are similar
	db.SetObjectVector(ctx, []byte("tennis"), []float32{0.9, 0.1, 0.0})
	db.SetObjectVector(ctx, []byte("badminton"), []float32{0.85, 0.15, 0.0})
	db.SetObjectVector(ctx, []byte("football"), []float32{0.1, 0.9, 0.0})

	// Hybrid search: Find people who like sports similar to tennis
	// Combines graph pattern matching with vector similarity
	results, err := db.Search(ctx, []*graph.Pattern{
		{
			Subject:   graph.Binding("person"),
			Predicate: graph.ExactString("likes"),
			Object:    graph.Binding("sport"),
		},
	}, &levelgraph.SearchOptions{
		VectorFilter: &levelgraph.VectorFilter{
			Variable: "sport",
			Query:    []float32{0.9, 0.1, 0.0}, // Query vector for "tennis-like"
			TopK:     10,
			MinScore: 0.8, // Only high similarity matches
			IDType:   vector.IDTypeObject,
		},
	})
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("People who like tennis-like sports:")
	for _, sol := range results {
		score := levelgraph.GetVectorScore(sol)
		fmt.Printf("  %s likes %s (similarity: %.2f)\n",
			sol["person"], sol["sport"], score)
	}
}
Output:
People who like tennis-like sports:
  alice likes tennis (similarity: 1.00)
  charlie likes tennis (similarity: 1.00)
  alice likes badminton (similarity: 1.00)
Example (Iterator)

Example_iterator demonstrates using iterators for memory-efficient streaming of large result sets instead of loading all results into memory.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-example-iter")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	db, err := levelgraph.Open(filepath.Join(dir, "iter.db"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	ctx := context.Background()

	// Add some triples
	db.Put(ctx,
		graph.NewTripleFromStrings("alice", "knows", "bob"),
		graph.NewTripleFromStrings("alice", "knows", "charlie"),
		graph.NewTripleFromStrings("alice", "knows", "david"),
	)

	// Use iterator instead of Get() for large result sets
	// This streams results one at a time instead of loading all into memory
	pattern := &graph.Pattern{
		Subject:   graph.ExactString("alice"),
		Predicate: graph.ExactString("knows"),
		Object:    graph.Wildcard(),
	}

	iter, err := db.GetIterator(ctx, pattern)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer iter.Release()

	fmt.Println("Alice knows:")
	for iter.Next() {
		triple, err := iter.Triple()
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		fmt.Printf("  %s\n", triple.Object)
	}

	if err := iter.Error(); err != nil {
		fmt.Println("Error:", err)
	}
}
Output:
Alice knows:
  bob
  charlie
  david
Example (Journal)

Example_journal demonstrates journaling for audit trails.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-journal")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	// Open with journaling enabled
	db, err := levelgraph.Open(filepath.Join(dir, "journal.db"), levelgraph.WithJournal())
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	ctx := context.Background()

	// Perform some operations
	db.Put(ctx, graph.NewTripleFromStrings("alice", "knows", "bob"))
	db.Put(ctx, graph.NewTripleFromStrings("bob", "knows", "charlie"))
	db.Del(ctx, graph.NewTripleFromStrings("alice", "knows", "bob"))

	// Get all journal entries (use zero time to get all)
	count, err := db.JournalCount(ctx, time.Time{})
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Printf("Journal has %d entries\n", count)
}
Output:
Journal has 3 entries
Example (Navigator)

Example_navigator demonstrates the fluent Navigator API for graph traversal.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-nav")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	db, err := levelgraph.Open(filepath.Join(dir, "nav.db"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	// Build a graph
	db.Put(context.Background(),
		graph.NewTripleFromStrings("alice", "knows", "bob"),
		graph.NewTripleFromStrings("bob", "knows", "charlie"),
	)

	// Navigate: find friends of friends of alice
	solutions, err := db.Nav(context.Background(), "alice").
		ArchOut("knows").As("friend").
		ArchOut("knows").As("fof").
		Solutions()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	if len(solutions) > 0 {
		fmt.Printf("Friend of friend: %s\n", solutions[0]["fof"])
	}
}
Output:
Friend of friend: charlie
Example (SearchJoin)

Example_searchJoin demonstrates multi-pattern joins to find complex relationships.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-join")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	db, err := levelgraph.Open(filepath.Join(dir, "join.db"))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	ctx := context.Background()

	// Build a graph of people and their interests
	db.Put(ctx,
		graph.NewTripleFromStrings("alice", "likes", "tennis"),
		graph.NewTripleFromStrings("alice", "likes", "programming"),
		graph.NewTripleFromStrings("bob", "likes", "tennis"),
		graph.NewTripleFromStrings("bob", "likes", "chess"),
		graph.NewTripleFromStrings("charlie", "likes", "programming"),
	)

	// Find what alice and bob have in common (join on shared interest)
	results, err := db.Search(ctx, []*graph.Pattern{
		{
			Subject:   graph.ExactString("alice"),
			Predicate: graph.ExactString("likes"),
			Object:    graph.Binding("interest"),
		},
		{
			Subject:   graph.ExactString("bob"),
			Predicate: graph.ExactString("likes"),
			Object:    graph.Binding("interest"), // Same variable binds shared value
		},
	}, nil)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	if len(results) > 0 {
		fmt.Printf("Alice and Bob both like: %s\n", results[0]["interest"])
	}
}
Output:
Alice and Bob both like: tennis
Example (VectorSearch)

Example_vectorSearch demonstrates semantic similarity search using vector embeddings. This enables "fuzzy" queries that find results based on meaning rather than exact matches.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/benbenbenbenbenben/levelgraph"
	"github.com/benbenbenbenbenben/levelgraph/pkg/graph"
	"github.com/benbenbenbenbenben/levelgraph/vector"
)

func main() {
	dir, err := os.MkdirTemp("", "levelgraph-example-vector")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.RemoveAll(dir)

	// Create a vector index (3 dimensions for this simple example)
	vectorIndex := vector.NewFlatIndex(3)

	db, err := levelgraph.Open(filepath.Join(dir, "vector.db"),
		levelgraph.WithVectors(vectorIndex),
	)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()

	ctx := context.Background()

	// Add triples about sports
	db.Put(ctx,
		graph.NewTripleFromStrings("alice", "likes", "tennis"),
		graph.NewTripleFromStrings("bob", "likes", "badminton"),
		graph.NewTripleFromStrings("charlie", "likes", "football"),
	)

	// Associate vectors with sport objects (simulated embeddings)
	// In practice, these would come from an embedding model
	// Tennis and badminton are similar (both racket sports)
	db.SetObjectVector(ctx, []byte("tennis"), []float32{0.9, 0.1, 0.0})
	db.SetObjectVector(ctx, []byte("badminton"), []float32{0.85, 0.15, 0.0})
	db.SetObjectVector(ctx, []byte("football"), []float32{0.1, 0.9, 0.0})

	// Search for sports similar to tennis
	results, err := db.SearchSimilarObjects(ctx, []float32{0.9, 0.1, 0.0}, 3)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("Sports similar to tennis:")
	for _, match := range results {
		fmt.Printf("  %s (score: %.2f)\n", match.Parts[0], match.Score)
	}
}
Output:
Sports similar to tennis:
  tennis (score: 1.00)
  badminton (score: 1.00)
  football (score: 0.61)

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// NewTriple refers to graph.NewTriple
	NewTriple = graph.NewTriple
	// NewTripleFromStrings refers to graph.NewTripleFromStrings
	NewTripleFromStrings = graph.NewTripleFromStrings
	// NewPattern refers to graph.NewPattern
	NewPattern = graph.NewPattern
	// V refers to graph.V
	V = graph.V
	// Wildcard refers to graph.Wildcard
	Wildcard = graph.Wildcard
	// Exact refers to graph.Exact
	Exact = graph.Exact
	// ExactString refers to graph.ExactString
	ExactString = graph.ExactString
	// Binding refers to graph.Binding
	Binding = graph.Binding
)
View Source
var (
	// ErrClosed is returned when operating on a closed database.
	ErrClosed = errors.New("levelgraph: database is closed")
	// ErrInvalidTriple is returned when a triple is invalid.
	ErrInvalidTriple = errors.New("levelgraph: invalid triple - subject, predicate, and object are required")
	// ErrDimensionMismatch is returned when Embedder and VectorIndex have different dimensions.
	ErrDimensionMismatch = errors.New("levelgraph: embedder and vector index dimension mismatch")
)
View Source
var (
	// ErrVectorsDisabled is returned when vector operations are called without
	// a configured vector index.
	ErrVectorsDisabled = errors.New("levelgraph: vectors not enabled - use WithVectors option")

	// ErrEmbedderRequired is returned when auto-embedding requires an embedder
	// but none was configured.
	ErrEmbedderRequired = errors.New("levelgraph: embedder required for this operation")

	// ErrVectorDimensionMismatch is returned when loading a persisted vector
	// whose dimensions don't match the configured index dimensions.
	ErrVectorDimensionMismatch = errors.New("levelgraph: persisted vector dimensions do not match index dimensions")
)
View Source
var (

	// ErrFacetsDisabled is returned when facets operations are called but facets are not enabled.
	ErrFacetsDisabled = errors.New("levelgraph: facets are not enabled")
)
View Source
var ErrNotFound = leveldb.ErrNotFound

ErrNotFound is returned when a key is not found.

Functions

func GetVectorScore

func GetVectorScore(sol graph.Solution) float32

GetVectorScore extracts the vector similarity score from a solution. Returns 0 if no score was set (e.g., if VectorFilter wasn't used).

Types

type AutoEmbedTarget

type AutoEmbedTarget int

AutoEmbedTarget specifies which parts of triples should be automatically embedded.

const (
	// AutoEmbedNone disables automatic embedding.
	AutoEmbedNone AutoEmbedTarget = 0
	// AutoEmbedSubjects enables automatic embedding of subject values.
	AutoEmbedSubjects AutoEmbedTarget = 1 << iota
	// AutoEmbedPredicates enables automatic embedding of predicate values.
	AutoEmbedPredicates
	// AutoEmbedObjects enables automatic embedding of object values.
	AutoEmbedObjects
	// AutoEmbedAll enables automatic embedding of all triple components.
	AutoEmbedAll = AutoEmbedSubjects | AutoEmbedPredicates | AutoEmbedObjects
)

type Batch

type Batch = leveldb.Batch

Batch is an alias for leveldb.Batch.

func NewBatch

func NewBatch() *Batch

NewBatch creates a new batch.

type BatchOp

type BatchOp struct {
	Type  string `json:"type"` // "put" or "del"
	Key   []byte `json:"key"`
	Value []byte `json:"value"`
}

BatchOp represents a single batch operation.

type DB

type DB struct {
	// contains filtered or unexported fields
}

DB represents a LevelGraph database.

func Open

func Open(path string, opts ...Option) (*DB, error)

Open opens or creates a LevelGraph database at the specified path. For WebAssembly builds, use OpenWithStore with NewMemStore instead.

func OpenWithDB

func OpenWithDB(store KVStore, opts ...Option) (*DB, error)

OpenWithDB wraps an existing KVStore instance with LevelGraph. This is useful for using custom configurations or in-memory databases.

func (*DB) Close

func (db *DB) Close() error

Close closes the database. If async embedding is enabled, Close waits for all pending embeddings to complete.

func (*DB) CloseGracefully

func (db *DB) CloseGracefully(ctx context.Context) error

CloseGracefully closes the database gracefully, waiting for the context to be cancelled or for a clean shutdown. This allows pending read operations and async embeddings to complete before closing.

func (*DB) Del

func (db *DB) Del(ctx context.Context, triples ...*graph.Triple) error

Del deletes one or more triples from the database.

func (*DB) DelAllTripleFacets

func (db *DB) DelAllTripleFacets(ctx context.Context, triple *graph.Triple) error

DelAllTripleFacets deletes all facets from a triple.

func (*DB) DelFacet

func (db *DB) DelFacet(ctx context.Context, facetType FacetType, value []byte, key []byte) error

DelFacet deletes a facet from a component.

func (*DB) DelTripleFacet

func (db *DB) DelTripleFacet(ctx context.Context, triple *graph.Triple, key []byte) error

DelTripleFacet deletes a facet from a triple.

func (*DB) DeleteVector

func (db *DB) DeleteVector(ctx context.Context, id []byte) error

DeleteVector removes a vector embedding by ID.

func (*DB) EmbedAndSetVector

func (db *DB) EmbedAndSetVector(ctx context.Context, id []byte, text string) error

EmbedAndSetVector embeds text and stores the resulting vector. Requires an Embedder to be configured.

Example:

id := vector.MakeID(vector.IDTypeObject, []byte("tennis"))
db.EmbedAndSetVector(ctx, id, "tennis is a racket sport")

func (*DB) GenerateBatch

func (db *DB) GenerateBatch(triple *graph.Triple, action string) ([]BatchOp, error)

GenerateBatch generates batch operations for a triple. This is useful for external batch management.

func (*DB) Get

func (db *DB) Get(ctx context.Context, pattern *graph.Pattern) ([]*graph.Triple, error)

Get retrieves triples matching the given pattern.

func (*DB) GetFacet

func (db *DB) GetFacet(ctx context.Context, facetType FacetType, value []byte, key []byte) ([]byte, error)

GetFacet retrieves a facet from a component.

func (*DB) GetFacetIterator

func (db *DB) GetFacetIterator(ctx context.Context, facetType FacetType, value []byte) (*FacetIterator, error)

GetFacetIterator returns an iterator over facets on a component.

func (*DB) GetFacets

func (db *DB) GetFacets(ctx context.Context, facetType FacetType, value []byte) (map[string][]byte, error)

GetFacets retrieves all facets from a component. Returns a map of facet keys to values.

func (*DB) GetIterator

func (db *DB) GetIterator(ctx context.Context, pattern *graph.Pattern) (*TripleIterator, error)

GetIterator returns an iterator for triples matching the pattern.

func (*DB) GetJournalEntries

func (db *DB) GetJournalEntries(ctx context.Context, before time.Time) ([]*JournalEntry, error)

GetJournalEntries returns all journal entries, optionally filtered by time.

func (*DB) GetJournalIterator

func (db *DB) GetJournalIterator(ctx context.Context, before time.Time) (*JournalIterator, error)

GetJournalIterator returns an iterator over all journal entries. If before is non-zero, only entries before that time are returned.

func (*DB) GetTripleFacet

func (db *DB) GetTripleFacet(ctx context.Context, triple *graph.Triple, key []byte) ([]byte, error)

GetTripleFacet retrieves a facet from a triple.

func (*DB) GetTripleFacetIterator

func (db *DB) GetTripleFacetIterator(ctx context.Context, triple *graph.Triple) (*FacetIterator, error)

GetTripleFacetIterator returns an iterator over facets on a triple.

func (*DB) GetTripleFacets

func (db *DB) GetTripleFacets(ctx context.Context, triple *graph.Triple) (map[string][]byte, error)

GetTripleFacets retrieves all facets from a triple.

func (*DB) GetVector

func (db *DB) GetVector(ctx context.Context, id []byte) ([]float32, error)

GetVector retrieves a vector embedding by ID.

func (*DB) IsOpen

func (db *DB) IsOpen() bool

IsOpen returns true if the database is open.

func (*DB) JournalCount

func (db *DB) JournalCount(ctx context.Context, before time.Time) (int, error)

JournalCount returns the number of journal entries, optionally filtered by time.

func (*DB) LoadVectors

func (db *DB) LoadVectors(ctx context.Context) error

LoadVectors loads all persisted vectors from KVStore into the index. This should be called after opening a database with vectors enabled to restore the index state.

func (*DB) Nav

func (db *DB) Nav(ctx context.Context, start any) *Navigator

Nav creates a new Navigator starting from the given vertex. If start is nil, a new variable is created as the starting point.

func (*DB) PendingEmbeddings

func (db *DB) PendingEmbeddings() int

PendingEmbeddings returns the number of pending async embedding operations. Returns 0 if async embedding is not enabled.

func (*DB) Put

func (db *DB) Put(ctx context.Context, triples ...*graph.Triple) error

Put inserts one or more triples into the database. If auto-embedding is enabled (via WithAutoEmbed), vectors will be automatically generated for the configured triple components.

func (*DB) ReplayJournal

func (db *DB) ReplayJournal(ctx context.Context, after time.Time, targetDB *DB) (int, error)

ReplayJournal replays all journal entries from a given time onwards. If after is zero, replays all entries from the beginning. This can be used to restore the database state or replay operations.

func (*DB) Search

func (db *DB) Search(ctx context.Context, patterns []*Pattern, opts *SearchOptions) ([]Solution, error)

Search executes a search query with one or more patterns. It performs joins across patterns, binding variables as it matches triples.

func (*DB) SearchIterator

func (db *DB) SearchIterator(ctx context.Context, patterns []*graph.Pattern, opts *SearchOptions) (*SolutionIterator, error)

SearchIterator returns an iterator for search results.

Note: VectorFilter is not supported with SearchIterator. If you need vector-filtered search results, use Search() instead which returns all results at once after applying vector filtering and sorting.

func (*DB) SearchSimilarObjects

func (db *DB) SearchSimilarObjects(ctx context.Context, query []float32, k int) ([]VectorMatch, error)

SearchSimilarObjects searches for objects similar to a query vector. Only returns matches with IDTypeObject.

func (*DB) SearchSimilarSubjects

func (db *DB) SearchSimilarSubjects(ctx context.Context, query []float32, k int) ([]VectorMatch, error)

SearchSimilarSubjects searches for subjects similar to a query vector. Only returns matches with IDTypeSubject.

func (*DB) SearchVectors

func (db *DB) SearchVectors(ctx context.Context, query []float32, k int) ([]VectorMatch, error)

SearchVectors finds the k most similar vectors to the query. Results are sorted by similarity (highest first).

Example:

// Find objects similar to "racket sports"
queryVec, _ := embedder.Embed("racket sports")
results, _ := db.SearchVectors(ctx, queryVec, 10)
for _, r := range results {
    fmt.Printf("%s: %.3f\n", r.Parts[0], r.Score)
}

func (*DB) SearchVectorsByText

func (db *DB) SearchVectorsByText(ctx context.Context, text string, k int) ([]VectorMatch, error)

SearchVectorsByText searches for similar vectors using text input. Requires an Embedder to be configured (via WithAutoEmbed).

Example:

results, _ := db.SearchVectorsByText(ctx, "racket sports", 10)

func (*DB) SetFacet

func (db *DB) SetFacet(ctx context.Context, facetType FacetType, value []byte, key []byte, facetValue []byte) error

SetFacet sets a facet on a component (subject, predicate, or object value). The facet is a key-value pair attached to the component.

func (*DB) SetObjectVector

func (db *DB) SetObjectVector(ctx context.Context, object []byte, vec []float32) error

SetObjectVector is a convenience method to set a vector for an object value.

func (*DB) SetSubjectVector

func (db *DB) SetSubjectVector(ctx context.Context, subject []byte, vec []float32) error

SetSubjectVector is a convenience method to set a vector for a subject value.

func (*DB) SetTripleFacet

func (db *DB) SetTripleFacet(ctx context.Context, triple *graph.Triple, key []byte, value []byte) error

SetTripleFacet sets a facet on an entire triple relationship.

func (*DB) SetTripleVector

func (db *DB) SetTripleVector(ctx context.Context, triple *graph.Triple, vec []float32) error

SetTripleVector is a convenience method to set a vector for a triple.

func (*DB) SetVector

func (db *DB) SetVector(ctx context.Context, id []byte, vec []float32) error

SetVector associates a vector embedding with an ID. The ID can be created using vector.MakeID to associate vectors with graph elements (subjects, objects, predicates, triples, or facets).

Example:

// Associate a vector with an object value
id := vector.MakeID(vector.IDTypeObject, []byte("tennis"))
db.SetVector(ctx, id, tennisEmbedding)

// Associate a vector with a custom ID
db.SetVector(ctx, []byte("doc:123"), docEmbedding)

func (*DB) Trim

func (db *DB) Trim(ctx context.Context, before time.Time) (int, error)

Trim removes all journal entries before the given time.

func (*DB) TrimAndExport

func (db *DB) TrimAndExport(ctx context.Context, before time.Time, targetDB *DB) (int, error)

TrimAndExport removes journal entries before the given time and exports them to another database. This is useful for archiving old journal entries while keeping the main database lean.

func (*DB) V

func (db *DB) V(name string) *graph.Variable

V creates a new Variable for use in queries. This is a convenience method that calls the package-level V function.

func (*DB) VectorCount

func (db *DB) VectorCount() int

VectorCount returns the number of vectors in the index.

func (*DB) VectorDimensions

func (db *DB) VectorDimensions() int

VectorDimensions returns the dimensionality of the vector index. Returns 0 if vectors are not enabled.

func (*DB) VectorsEnabled

func (db *DB) VectorsEnabled() bool

VectorsEnabled returns true if vector operations are available.

func (*DB) WaitForEmbeddings

func (db *DB) WaitForEmbeddings(ctx context.Context) error

WaitForEmbeddings blocks until all pending async embedding operations are complete. Returns immediately if async embedding is not enabled. Returns an error if the context is cancelled before all embeddings complete.

Use this method after a batch of Put operations to ensure all vectors are indexed before performing searches:

// Add triples with async embedding
for _, triple := range triples {
    db.Put(ctx, triple)
}

// Wait for all embeddings to complete before searching
if err := db.WaitForEmbeddings(ctx); err != nil {
    log.Printf("embedding error: %v", err)
}

// Now search will include all vectors
results, _ := db.SearchVectorsByText(ctx, "query", 10)

type Embedder

type Embedder interface {
	// Embed converts a single text string to a vector embedding.
	Embed(text string) ([]float32, error)

	// EmbedBatch converts multiple texts to vector embeddings.
	// Implementations may optimize batch processing.
	EmbedBatch(texts []string) ([][]float32, error)

	// Dimensions returns the dimensionality of the embeddings.
	Dimensions() int
}

Embedder is an interface for text embedding models. Implementations convert text to vector representations for semantic search.

type FacetIterator

type FacetIterator struct {
	// contains filtered or unexported fields
}

FacetIterator iterates over facets on a component or triple.

func (*FacetIterator) Close

func (fi *FacetIterator) Close()

Close releases the iterator.

func (*FacetIterator) Error

func (fi *FacetIterator) Error() error

Error returns any error from the iterator.

func (*FacetIterator) Key

func (fi *FacetIterator) Key() []byte

Key returns the current facet key.

func (*FacetIterator) Next

func (fi *FacetIterator) Next() bool

Next advances the iterator.

func (*FacetIterator) Value

func (fi *FacetIterator) Value() []byte

Value returns the current facet value.

type FacetType

type FacetType string

FacetType represents the type of component a facet is attached to.

const (
	// FacetSubject is a facet on a subject value
	FacetSubject FacetType = "subject"
	// FacetPredicate is a facet on a predicate value
	FacetPredicate FacetType = "predicate"
	// FacetObject is a facet on an object value
	FacetObject FacetType = "object"
)

type Iterator

type Iterator = iterator.Iterator

Iterator is an alias for the leveldb iterator interface.

type JoinAlgorithm

type JoinAlgorithm string

JoinAlgorithm represents the algorithm used for joining patterns in searches.

const (
	// JoinAlgorithmBasic uses nested loop join.
	JoinAlgorithmBasic JoinAlgorithm = "basic"
	// JoinAlgorithmSort uses sort-merge join for better performance.
	JoinAlgorithmSort JoinAlgorithm = "sort"
)

type JournalEntry

type JournalEntry struct {
	// Operation is either "put" or "del"
	Operation string `json:"op"`
	// Triple is the triple that was written or deleted
	Triple *Triple `json:"triple"`
	// Timestamp is when the operation occurred
	Timestamp time.Time `json:"ts"`
}

JournalEntry represents a recorded operation in the journal.

func (*JournalEntry) MarshalBinary

func (e *JournalEntry) MarshalBinary() ([]byte, error)

MarshalBinary implements encoding.BinaryMarshaler for JournalEntry. Format: [OpByte][Timestamp (8 bytes)][Triple Binary]

func (*JournalEntry) UnmarshalBinary

func (e *JournalEntry) UnmarshalBinary(data []byte) error

UnmarshalBinary implements encoding.BinaryUnmarshaler for JournalEntry.

type JournalIterator

type JournalIterator struct {
	// contains filtered or unexported fields
}

JournalIterator iterates over journal entries.

func (*JournalIterator) Close

func (ji *JournalIterator) Close()

Close releases the iterator.

func (*JournalIterator) Entry

func (ji *JournalIterator) Entry() (*JournalEntry, error)

Entry returns the current journal entry.

func (*JournalIterator) Error

func (ji *JournalIterator) Error() error

Error returns any error from the iterator.

func (*JournalIterator) Key

func (ji *JournalIterator) Key() []byte

Key returns the current key.

func (*JournalIterator) Next

func (ji *JournalIterator) Next() bool

Next advances to the next journal entry.

type KVStore

type KVStore interface {
	Get(key []byte, ro *ReadOptions) (value []byte, err error)
	Put(key, value []byte, wo *WriteOptions) error
	Delete(key []byte, wo *WriteOptions) error
	Write(batch *Batch, wo *WriteOptions) error
	NewIterator(slice *Range, ro *ReadOptions) Iterator
	Close() error
}

KVStore defines the interface for the underlying key-value store.

type Navigator struct {
	// contains filtered or unexported fields
}

Navigator provides a fluent API for traversing the graph. It allows building queries by following edges in and out.

Example usage:

nav := db.Nav(ctx, []byte("alice"))
solutions, err := nav.ArchOut("knows").ArchOut("likes").Solutions()

This finds all things liked by people that alice knows.

func (nav *Navigator) ArchIn(predicate any) *Navigator

ArchIn follows an incoming edge with the given predicate. The current position becomes the object, and navigates to the subject.

func (nav *Navigator) ArchOut(predicate any) *Navigator

ArchOut follows an outgoing edge with the given predicate. The current position becomes the subject, and navigates to the object.

func (nav *Navigator) As(name string) *Navigator

As names the current position with the given variable name. This allows referencing the position later in the query.

func (nav *Navigator) Bind(value any) *Navigator

Bind binds the current position's variable to a concrete value. This is used to constrain the search.

func (nav *Navigator) Clone() *Navigator

Clone creates a copy of this navigator that can be modified independently.

func (nav *Navigator) Count() (int, error)

Count returns the number of solutions without materializing all of them.

func (nav *Navigator) Exists() (bool, error)

Exists returns true if at least one solution exists.

func (nav *Navigator) Filter(fn func(*graph.Triple) bool) *Navigator

Filter adds a filter function to the last condition. The filter is applied to each matching triple.

func (nav *Navigator) First() (graph.Solution, error)

First returns the first solution, or nil if none found.

func (nav *Navigator) Go(vertex any) *Navigator

Go moves the navigator to a new vertex. If vertex is nil, a new variable is created. vertex can be []byte, string (converted to []byte), or *graph.Variable.

func (nav *Navigator) Solutions() ([]graph.Solution, error)

Solutions executes the navigation query and returns all solutions. Each solution is a map of variable names to their bound values.

func (nav *Navigator) Triples(pattern *graph.Pattern) ([]*graph.Triple, error)

Triples executes the query and materializes results into triples. The pattern specifies how to construct the result triples from solutions.

func (nav *Navigator) Values() ([][]byte, error)

Values returns unique values for the last navigated position. This is useful for getting distinct nodes at the end of a traversal.

func (nav *Navigator) Where(pattern *graph.Pattern) *Navigator

Where adds a custom pattern condition to the navigator.

type Option

type Option func(*Options)

Option is a function that configures Options.

func WithAsyncAutoEmbed

func WithAsyncAutoEmbed(bufferSize int) Option

WithAsyncAutoEmbed enables non-blocking auto-embedding with the specified buffer size. When enabled, embedding is performed in a background goroutine instead of blocking the Put() call. This is useful when using real embedding models that have latency.

Use WaitForEmbeddings() to block until all pending embeddings are complete. The buffer size determines how many embedding requests can be queued before Put() blocks waiting for the queue to drain.

Example:

db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithVectors(vector.NewHNSWIndex(192)),
    levelgraph.WithAutoEmbed(myEmbedder, levelgraph.AutoEmbedObjects),
    levelgraph.WithAsyncAutoEmbed(100),
)
// ... add triples ...
db.WaitForEmbeddings(ctx) // Wait for all embeddings to complete

func WithAutoEmbed

func WithAutoEmbed(embedder Embedder, targets AutoEmbedTarget) Option

WithAutoEmbed enables automatic vector embedding when triples are added. Requires both an Embedder and a VectorIndex to be configured.

Example:

db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithVectors(vector.NewHNSWIndex(192)),
    levelgraph.WithAutoEmbed(myEmbedder, levelgraph.AutoEmbedObjects),
)

func WithBasicJoin

func WithBasicJoin() Option

WithBasicJoin is a convenience option for using the basic (nested loop) join algorithm.

func WithDefaultLimit

func WithDefaultLimit(limit int) Option

WithDefaultLimit sets the default maximum result limit for Get/Search operations. When set to a positive value, this limit is applied if no explicit limit is provided in the query. This is useful for preventing unbounded result sets that could exhaust memory or cause performance issues. 0 means no default limit (the default for backward compatibility).

func WithFacets

func WithFacets() Option

WithFacets enables the facets/properties feature. When enabled, additional properties can be attached to triple components or entire triples.

func WithJoinAlgorithm

func WithJoinAlgorithm(algo JoinAlgorithm) Option

WithJoinAlgorithm sets the join algorithm for searches.

func WithJournal

func WithJournal() Option

WithJournal enables the journalling feature. When enabled, all Put and Del operations are recorded in a journal that can be trimmed or exported.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets an optional structured logger for debug output. Pass nil to disable logging (the default).

func WithSortJoin

func WithSortJoin() Option

WithSortJoin is a convenience option for using the sort-merge join algorithm.

func WithVectors

func WithVectors(index vector.Index) Option

WithVectors enables vector similarity search with the provided index. Use vector.NewFlatIndex for exact search or vector.NewHNSWIndex for approximate nearest neighbor search.

Example:

db, err := levelgraph.Open("/path/to/db",
    levelgraph.WithVectors(vector.NewHNSWIndex(192)),
)

type Options

type Options struct {
	// JournalEnabled enables the journalling feature for write operations.
	JournalEnabled bool

	// FacetsEnabled enables the facets/properties feature.
	FacetsEnabled bool

	// VectorIndex is an optional vector similarity index for semantic search.
	// When set, vector operations (SetVector, GetVector, SearchVectors) are enabled.
	VectorIndex vector.Index

	// JoinAlgorithm specifies which join algorithm to use for searches.
	// Defaults to JoinAlgorithmSort.
	JoinAlgorithm JoinAlgorithm

	// Logger is an optional structured logger for debug output.
	// When nil, no logging is performed.
	Logger *slog.Logger

	// DefaultLimit is the default maximum number of results for Get/Search operations.
	// When set to a positive value, this limit is applied if no explicit limit is provided.
	// 0 means no default limit (unbounded, the default for backward compatibility).
	DefaultLimit int

	// Embedder is an optional text embedder for automatic vector generation.
	// When set along with AutoEmbedTargets, vectors are automatically created
	// when triples are added.
	Embedder Embedder

	// AutoEmbedTargets specifies which triple components should be auto-embedded.
	// Only used when Embedder is set.
	AutoEmbedTargets AutoEmbedTarget

	// AsyncAutoEmbed enables non-blocking auto-embedding.
	// When enabled, embedding is performed in a background goroutine instead of
	// blocking the Put() call. Use WaitForEmbeddings() to wait for pending work.
	AsyncAutoEmbed bool

	// AsyncEmbedBufferSize sets the buffer size for the async embed queue.
	// Defaults to 100 if not set. Only used when AsyncAutoEmbed is true.
	AsyncEmbedBufferSize int
}

Options configures a LevelGraph database.

type Pattern

type Pattern = graph.Pattern

Pattern is an alias for graph.Pattern representing a query pattern.

type Range

type Range = util.Range

Range is an alias for util.Range.

type ReadOptions

type ReadOptions = opt.ReadOptions

ReadOptions is an alias for opt.ReadOptions.

type SearchOptions

type SearchOptions struct {
	// Limit restricts the number of results (0 means no limit)
	Limit int
	// Offset skips the first N results
	Offset int
	// Filter is an optional function to filter solutions
	Filter func(Solution) bool
	// AsyncFilter is an optional async filter (returns solution or nil)
	AsyncFilter func(Solution, func(Solution, error))
	// Materialized is a pattern to transform solutions into triples
	Materialized *Pattern
	// InitialSolution is an optional starting solution with pre-bound variables
	InitialSolution Solution
	// VectorFilter enables hybrid search by filtering/ranking solutions based
	// on vector similarity of a bound variable.
	VectorFilter *VectorFilter
}

SearchOptions configures search behavior.

type Solution

type Solution = graph.Solution

Solution is an alias for graph.Solution representing query result bindings.

type SolutionIterator

type SolutionIterator struct {
	// contains filtered or unexported fields
}

SolutionIterator iterates over search solutions.

func (*SolutionIterator) Close

func (si *SolutionIterator) Close()

Close releases iterator resources.

func (*SolutionIterator) Error

func (si *SolutionIterator) Error() error

Error returns any error encountered during iteration.

func (*SolutionIterator) Next

func (si *SolutionIterator) Next() bool

Next advances to the next solution.

func (*SolutionIterator) Solution

func (si *SolutionIterator) Solution() graph.Solution

Solution returns the current solution.

type Triple

type Triple = graph.Triple

Triple is an alias for graph.Triple representing a subject-predicate-object triple.

type TripleIterator

type TripleIterator struct {
	// contains filtered or unexported fields
}

TripleIterator iterates over triples from a query.

func (*TripleIterator) Error

func (ti *TripleIterator) Error() error

Error returns any error from the iterator.

func (*TripleIterator) Next

func (ti *TripleIterator) Next() bool

Next advances the iterator to the next triple.

func (*TripleIterator) Release

func (ti *TripleIterator) Release()

Release releases the iterator resources.

func (*TripleIterator) Triple

func (ti *TripleIterator) Triple() (*graph.Triple, error)

Triple returns the current triple.

type Variable

type Variable = graph.Variable

Variable is an alias for graph.Variable representing a variable binding in patterns.

type VectorFilter

type VectorFilter struct {
	// Variable is the name of the variable to filter by vector similarity.
	// The variable's value will be used to look up vectors in the index.
	Variable string

	// Query is the query vector to compare against.
	Query []float32

	// QueryText is an optional text query that will be embedded using the
	// configured Embedder. Either Query or QueryText should be set, not both.
	QueryText string

	// TopK limits results to the K most similar values for the variable.
	// If 0, all solutions are kept but scored/sorted.
	TopK int

	// MinScore filters out solutions where the similarity score is below this threshold.
	// Score is in range [0, 1] for cosine similarity (after normalization).
	MinScore float32

	// IDType specifies the type of vector ID to look up (e.g., IDTypeObject).
	// If empty, defaults to IDTypeObject.
	IDType vector.IDType
}

VectorFilter specifies how to filter search results using vector similarity. It allows hybrid queries that combine graph traversal with semantic search.

How Hybrid Search Works

Hybrid search executes in two phases:

  1. Graph phase: Executes patterns to find matching solutions (like regular Search)
  2. Vector phase: Scores and filters solutions based on vector similarity

The Variable field specifies which solution variable to look up in the vector index. For example, if your pattern binds ?topic, and you have vectors stored for topics, set Variable: "topic" to score solutions by topic similarity.

Score Interpretation

Scores are normalized to [0, 1] range:

  • 1.0: Identical vectors (perfect match)
  • 0.7-0.9: Highly similar (typically good matches)
  • 0.5-0.7: Moderately similar
  • 0.0-0.5: Dissimilar

Use MinScore to filter out low-quality matches.

Example: Find People Who Like Similar Topics

solutions, err := db.Search(ctx, []*Pattern{
    {Subject: V("person"), Predicate: []byte("likes"), Object: V("topic")},
}, &SearchOptions{
    VectorFilter: &VectorFilter{
        Variable:  "topic",
        QueryText: "machine learning",  // Requires configured Embedder
        TopK:      10,                   // Limit to top 10 similar topics
        MinScore:  0.7,                  // Filter out scores below 0.7
        IDType:    vector.IDTypeObject, // Look up topic as object vector
    },
})

Example: Vector Search with Precomputed Query

queryVec := embedder.Embed("artificial intelligence")
solutions, err := db.Search(ctx, patterns, &SearchOptions{
    VectorFilter: &VectorFilter{
        Variable: "topic",
        Query:    queryVec,  // Use precomputed vector
        TopK:     10,
    },
})

type VectorMatch

type VectorMatch struct {
	// ID is the vector identifier (e.g., "object:tennis").
	ID []byte
	// Score is the similarity score (higher is more similar).
	Score float32
	// Distance is the distance metric (lower is more similar).
	Distance float32
	// IDType indicates what kind of graph element this ID refers to.
	IDType vector.IDType
	// Parts contains the parsed ID components.
	Parts [][]byte
}

VectorMatch represents a vector search result with graph context.

type WriteOptions

type WriteOptions = opt.WriteOptions

WriteOptions is an alias for opt.WriteOptions.

Directories

Path Synopsis
cmd
levelgraph command
wasm command
example
nolij command
Package main implements nolij, a knowledge graph CLI built on LevelGraph.
Package main implements nolij, a knowledge graph CLI built on LevelGraph.
simple command
Package main demonstrates basic LevelGraph features.
Package main demonstrates basic LevelGraph features.
Package memstore provides an in-memory key-value store implementation that is compatible with the levelgraph KVStore interface.
Package memstore provides an in-memory key-value store implementation that is compatible with the levelgraph KVStore interface.
pkg
graph
Package graph provides core types for graph database operations including triples, patterns, variables, and solutions for pattern matching queries.
Package graph provides core types for graph database operations including triples, patterns, variables, and solutions for pattern matching queries.
index
Package index provides hexastore index key generation and management for efficient triple pattern matching using six different key orderings.
Package index provides hexastore index key generation and management for efficient triple pattern matching using six different key orderings.
playground
wasm command
Package vector provides vector similarity search capabilities for LevelGraph.
Package vector provides vector similarity search capabilities for LevelGraph.
luxical
Package luxical provides an adapter for the Luxical text embedding model to work with LevelGraph's vector search capabilities.
Package luxical provides an adapter for the Luxical text embedding model to work with LevelGraph's vector search capabilities.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL