Go Embedded

Run Traverse as an in-process graph database in Go applications.

Install

Download the Traverse Go package from truespar.com, then add a replace directive in your go.mod:

require github.com/truespar/traverse v0.6.2

replace github.com/truespar/traverse => ./traverse

Note: No CGo required. The Go bindings use syscall.LoadDLL on Windows and dlopen on Linux to load the native library at runtime. Set TRAVERSE_FFI_LIB to specify the library path, or place it alongside your executable.

Open a Database

package main

import (
    "fmt"
    "log"

    "github.com/truespar/traverse"
)

func main() {
    // Open or create a database
    db, err := traverse.Open("mydb.tvdb")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

Run Queries

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}
result, err := tx.Execute("MATCH (n:Person) RETURN n.name, n.age")
if err != nil {
    tx.Abort()
    log.Fatal(err)
}
for _, row := range result.Rows {
    fmt.Println(row["n.name"], row["n.age"])
}
tx.Abort() // read-only, no changes to commit

Write Data

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}
_, err = tx.Execute("CREATE (p:Person {name: 'Alice', age: 30})")
if err != nil {
    tx.Abort()
    log.Fatal(err)
}
_, err = tx.Execute("CREATE (p:Person {name: 'Bob', age: 25})")
if err != nil {
    tx.Abort()
    log.Fatal(err)
}
if err := tx.Commit(); err != nil {
    log.Fatal(err)
}

Note: You must call Commit() to persist changes. Call Abort() to discard them. Once committed or aborted, the transaction cannot be reused.

QueryResult

tx, _ := db.Begin()
result, _ := tx.Execute("MATCH (n:Person) RETURN n.name, n.age")

fmt.Println(result.Columns)  // [n.name n.age]
fmt.Println(result.RowCount) // number of rows

// Rows are []map[string]interface{}
for _, row := range result.Rows {
    name := row["n.name"].(string)
    age := row["n.age"].(int64)
    fmt.Printf("%s is %d years old\n", name, age)
}

// Node values
result, _ = tx.Execute("MATCH (n:Person) RETURN n LIMIT 1")
node := result.Rows[0]["n"].(traverse.Node)
fmt.Println(node.ID, node.Labels, node.Properties)

// Edge values
result, _ = tx.Execute("MATCH ()-[r:KNOWS]->() RETURN r LIMIT 1")
edge := result.Rows[0]["r"].(traverse.Edge)
fmt.Println(edge.Type, edge.Source, edge.Target)

tx.Abort()

Bulk Writer

The BulkWriter bypasses MVCC for high-throughput imports (2–5M rows/s).

writer, err := db.BulkWriter()
if err != nil {
    log.Fatal(err)
}

nid1, err := writer.CreateNode(
    []string{"Person"},
    map[string]string{"name": "Alice", "age": "30"},
)
if err != nil {
    writer.Abort()
    log.Fatal(err)
}

nid2, err := writer.CreateNode(
    []string{"Person"},
    map[string]string{"name": "Bob", "age": "25"},
)
if err != nil {
    writer.Abort()
    log.Fatal(err)
}

_, err = writer.CreateEdge(nid1, nid2, "KNOWS", map[string]string{"since": "2024"})
if err != nil {
    writer.Abort()
    log.Fatal(err)
}

if err := writer.Commit(); err != nil {
    log.Fatal(err)
}

Note: The Go FFI bulk writer uses map[string]string for properties — all values are passed as strings through the C FFI boundary.

Indexes

Create indexes via the bulk writer or Cypher:

// Via BulkWriter
writer, _ := db.BulkWriter()
writer.CreateIndex("idx_person_name", "Person", "name")
writer.CreateEdgeIndex("idx_knows_since", "KNOWS", "since")
writer.CreateEdgeIndex("idx_knows", "KNOWS", "") // type-only index (empty property)
writer.Commit()

// Via Cypher
tx, _ := db.Begin()
tx.Execute("CREATE INDEX idx_person_age FOR (n:Person) ON (n.age)")
tx.Commit()

Flush and Close

// Flush dirty pages to disk
if err := db.Flush(); err != nil {
    log.Fatal(err)
}

// Close the database
if err := db.Close(); err != nil {
    log.Fatal(err)
}

Note: Traverse is an in-memory database. Call Flush() to persist data to the .tvdb file. Data written since the last flush is lost if the process crashes.

Error Handling

db, err := traverse.Open("mydb.tvdb")
if err != nil {
    // Check error type
    if tvErr, ok := err.(*traverse.TraverseError); ok {
        fmt.Println("Error code:", tvErr.Code)
        fmt.Println("Message:", tvErr.Message)
    }
}

Error codes include:

traverse.ErrIO               // I/O error
traverse.ErrSyntax           // Cypher syntax error
traverse.ErrType             // Type mismatch
traverse.ErrSemantic         // Semantic error
traverse.ErrGraph            // Graph constraint violation
traverse.ErrTransaction      // Transaction error
traverse.ErrInvalidArg       // Invalid argument