Go Driver¶
Velr is an embedded property-graph database from Velr.ai, written in Rust, built on top of SQLite, and queried using the openCypher language.
It runs in-process and is designed for local, embedded, and edge use cases.
This module provides the Go bindings for Velr. It wraps a bundled native runtime with a C ABI and exposes an idiomatic Go API for executing Cypher queries, streaming result tables, working with transactions, decoding graph values, and importing or exporting Arrow data.
This page follows the public Go module README. For the latest release-specific notes, see the Go driver README and the Velr Go module on pkg.go.dev.
For the main Velr public entry point, see velr-ai/velr. For the Velr website, see velr.ai.
Community¶
- Community and questions: GitHub Discussions
- Bug reports and feature requests: GitHub Issues
- Go examples: velr-go-examples
We'd love to have you join the Velr community.
Release Status¶
Velr is currently in public alpha.
- The Go API is still evolving.
- Velr supports openCypher and passes all positive openCypher TCK tests. Exact error semantics are not guaranteed to match other openCypher implementations.
Schema Version 7 Compatibility¶
This release's current on-disk schema is version 7. Supported older databases
can be opened with velr.Open or velr.OpenReadonly without changing the file.
Reads continue to work on those databases, but writes (CREATE, MERGE, SET,
DELETE, DETACH DELETE, and other mutating queries) are only available after
migrating to the current schema version. This is intentional: migration is an
explicit maintenance operation, not a side effect of opening a database.
Velr is already usable for real workflows and representative use cases, but rough edges remain and the API is not yet stable.
BM25 fulltext search and vector/ANN search are available today through Cypher
DDL and CALL syntax. API details may still evolve while Velr remains alpha.
Installation¶
Install the Go module:
The module ships the supported Velr native runtime binaries. Applications do not need a Velr source checkout, Rust toolchain, or separate runtime package.
Supported bundled platforms:
darwin-universallinux-x64-gnulinux-arm64-gnuwin32-x64-msvc
Licensing In Simple Terms¶
- The Go binding source code in this module is licensed under the project license.
- The bundled native runtime binaries may be used and freely redistributed
in unmodified form under the terms of
LICENSE.runtime.
Quick Start¶
package main
import (
"fmt"
"log"
velr "github.com/velr-ai/velr-go-driver"
)
func main() {
db, err := velr.OpenInMemory()
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.RunWithParams("CREATE (:Person {name: $name, born: $born})", velr.Params{
"name": "Keanu Reeves",
"born": 1964,
})
if err != nil {
log.Fatal(err)
}
rows, err := db.Query("MATCH (p:Person) RETURN p.name AS name, p.born AS born")
if err != nil {
log.Fatal(err)
}
fmt.Println(rows)
}
Open a file-backed database instead of an in-memory database:
Open an existing database for reads only:
OpenReadonly never creates, initializes, migrates, or repairs a database. The
file must already exist and have a supported Velr schema version. Older
supported databases, such as schema version 3, 4, 5, or 6 databases opened by a
schema version 7 runtime, remain available for reads. Writes and features that
require the current schema fail with a normal query error until the database is
explicitly migrated.
Connections and active handles are not safe for concurrent use. Use one connection per goroutine when you need parallelism.
Schema Migration¶
Velr does not migrate supported older databases automatically on open. Use the
driver migration API, or run MIGRATE DATABASE, from maintenance code when you
intend to update the on-disk schema. See the release-status note above for the
schema version 7 read/write compatibility behavior.
db, err := velr.Open("mygraph.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
needs, err := db.NeedsMigration()
if err != nil {
log.Fatal(err)
}
if needs {
report, err := db.Migrate()
if err != nil {
log.Fatal(err)
}
fmt.Println(report.Status, report.FromVersion, report.ToVersion, report.Steps)
}
The equivalent Cypher command is useful for scripts and tools that already work through query execution:
Query Execution¶
Velr exposes four main ways to run Cypher:
Runexecutes Cypher and drains all result tables.Execreturns a stream when a statement can produce multiple result tables.ExecOnereturns exactly one table.Queryconverts one table into[]map[string]any.
Convenience helpers such as RunWithParams, ExecOneWithParams, and
QueryWithParams bind named Cypher parameters:
rows, err := db.QueryWithParams(
"MATCH (p:Person {name:$name}) RETURN p.born AS born",
velr.Params{"name": "Keanu Reeves"},
)
Query text uses $name; map keys omit the leading $. Parameters are values,
not Cypher string interpolation.
Use MaxResultRows when you want a per-result-table row cap:
rows, err := db.Query(
"MATCH (p:Person) RETURN p.name AS name ORDER BY name",
velr.MaxResultRows(100),
)
Supported parameter values are nil, bool, signed and unsigned integers that
fit int64, finite floats, string, json.RawMessage, lists, arrays, and maps
with string keys. Convert structs to maps or explicit json.RawMessage before
binding them.
Reading Results¶
For typed row handling, use Rows.NextInto or velr.Scan:
table, err := db.ExecOneWithParams(
"MATCH (p:Person {name:$name}) RETURN p.name, p.born",
velr.Params{"name": "Keanu Reeves"},
)
if err != nil {
log.Fatal(err)
}
defer table.Close()
rows, err := table.Rows()
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var name string
var born int64
ok, err := rows.NextInto(&name, &born)
if err != nil {
log.Fatal(err)
}
fmt.Println(ok, name, born)
Result helpers:
Table.ColumnCountandTable.ColumnNamesinspect result metadata.Table.Rowsopens a row cursor.Table.ForEachRow,Table.Collect, andTable.ToObjectscollect results.Rows.Nextreturns[]Cell;Rows.NextIntoscans into pointers.Cell.Valuereturns plain Go values.Cell.AsBool,AsInt64,AsFloat64,AsString,DecodeJSON, andAssignToprovide stricter conversions.Cell.AsPropertyValuereturns a typedvelr.PropertyValuefor scalar property values and lists.
Cell.AsProperty adds richer decoding for Velr graph and property values.
Nodes become velr.Node, relationships become velr.Relationship, paths
become velr.Path, and spatial GeoJSON values become velr.GeoJSON.
Node.Properties and Relationship.Properties are map[string]velr.PropertyValue.
Use PropertyValue.Type or PropertyValue.Kind() for the Velr kind, and
PropertyValue.GoValue() when you want ordinary Go values.
Transactions And Savepoints¶
Use Transaction for commit-on-success and rollback-on-error:
err := db.Transaction(func(tx *velr.Tx) error {
if err := tx.Run("CREATE (:Event {name:'start'})"); err != nil {
return err
}
sp, err := tx.Savepoint()
if err != nil {
return err
}
if err := tx.Run("CREATE (:Event {name:'discard'})"); err != nil {
_ = sp.Close()
return err
}
return sp.Rollback()
})
Explicit transactions are available with BeginTx, Commit, Rollback, and
Close. Closing an uncommitted transaction rolls it back.
Named savepoints use SavepointNamed, RollbackTo, and ReleaseSavepoint.
Named savepoints are stack-like: release the most recently created active named
savepoint first.
Savepoint.Release keeps changes after the savepoint, Savepoint.Rollback
discards them, and Savepoint.Close closes the native handle without an
explicit release or rollback.
Introspection¶
Use SHOW CURRENT GRAPH SHAPE to inspect the observed schema of the graph. It
reports the shape present in stored data: node labels, relationship types,
properties, observed value types, and counts. It is an observed shape surface,
not a declared GQL graph type.
rows, err := db.Query(`
SHOW CURRENT GRAPH SHAPE
YIELD element_kind, element_name, property_name, observed_type, owner_count
WHERE element_kind = 'node_property'
RETURN element_name, property_name, observed_type, owner_count
`)
Use YIELD to compose the command with WHERE and RETURN. Plain
SHOW CURRENT GRAPH SHAPE returns the default projection; YIELD * exposes the
full current row shape.
OpenCypher Functions¶
The bundled runtime supports the following openCypher functions and constructors:
Graph and path: id, type, labels, keys, properties, length,
nodes, and relationships.
Lists and predicates: size, head, last, tail, reverse, range,
all, any, none, and single.
Strings and conversion: coalesce, toInteger, toString, toLower, trim,
substring, and split.
Numeric: abs, ceil, rand, sign, and sqrt.
Temporal constructors and clocks: date, time, localtime, datetime,
localdatetime, duration, datetime.fromepoch,
datetime.fromepochmillis, and the .realtime, .transaction, and
.statement variants for date, time, localtime, datetime, and
localdatetime.
Aggregates: count, sum, avg, min, max, collect,
percentileDisc, and percentileCont.
Fulltext Search¶
BM25 fulltext search is available through normal Cypher execution. Define
indexes with CREATE FULLTEXT INDEX and query them with
CALL db.index.fulltext.queryNodes(...).
db, err := velr.Open("mygraph.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Run(`
CREATE FULLTEXT INDEX paperText
FOR (n:Paper) ON EACH [n.title, n.abstract]
`)
if err != nil {
log.Fatal(err)
}
rows, err := db.QueryWithParams(`
CALL db.index.fulltext.queryNodes('paperText', $query)
YIELD node, score
RETURN node, score
`, velr.Params{"query": `abstract:vector OR title:"query planning"`})
Fulltext indexes use a sidecar next to file-backed databases. The sidecar is kept up to date by writes and rebuilt on open if it is missing or corrupt.
Vector Search¶
Register an embedding callback, then reference it from CREATE VECTOR INDEX.
Velr invokes the callback for index maintenance when indexed source values
change and for text queries passed to CALL db.index.vector.queryNodes(...).
err := db.RegisterVectorEmbedder("toy", func(inputs []velr.VectorEmbeddingInput) ([][]float32, error) {
out := make([][]float32, len(inputs))
for i, input := range inputs {
out[i] = embed(input.Text(), input.Dimensions)
}
return out, nil
})
The callback must return one finite []float32 embedding per input, with the
exact dimension count requested by the index.
VectorEmbeddingInput includes index name, dimensions, purpose, entity kind,
entity id, and selected fields. Each VectorEmbeddingField carries a typed
PropertyValue; use field.Value.Type or field.Value.Kind() to inspect the
Velr kind, field.Value.GoValue() for ordinary Go values, and
VectorEmbeddingInput.Text() to join display strings for toy or local
embedders.
Vector indexes use a sidecar next to file-backed databases.
Arrow¶
Table.ToArrowIPC exports a result table as Arrow IPC file bytes.
BindArrowIPC imports Arrow IPC file bytes under a logical table name:
table, err := db.ExecOne("MATCH (m:Movie) RETURN m.title AS title")
if err != nil {
log.Fatal(err)
}
defer table.Close()
ipc, err := table.ToArrowIPC()
if err != nil {
log.Fatal(err)
}
if err := db.BindArrowIPC("_movies", ipc); err != nil {
log.Fatal(err)
}
BindArrow and BindArrowChunks bind Arrow C Data Interface columns from
whichever Go Arrow implementation your application uses. The ArrowArray
pointers passed to BindArrow or BindArrowChunks are transferred to Velr by
the ABI call. Do not release or reuse those arrays after calling; schemas and
column names are borrowed only during the call.
After binding a logical name, read the rows from Cypher with
UNWIND BIND(...):
if err := db.BindArrow("_people", columns); err != nil {
log.Fatal(err)
}
if err := db.Run(`
UNWIND BIND('_people') AS row
CREATE (:Person {name: row.name, age: row.age})
`); err != nil {
log.Fatal(err)
}
The
arrow_columns example
shows a complete Apache Arrow Go export using arrow/cdata and malloc-backed
Arrow buffers.
Transactions also expose Tx.BindArrowIPC, Tx.BindArrow, and
Tx.BindArrowChunks.
Explain¶
Use Explain or ExplainAnalyze on DB or Tx to inspect a query plan:
trace, err := db.Explain("MATCH (n) RETURN count(n)")
if err != nil {
log.Fatal(err)
}
defer trace.Close()
compact, err := trace.CompactString()
For structured access, use PlanCount, PlanMeta, StepCount, StepMeta,
StatementCount, StatementMeta, SQLitePlanCount, SQLitePlanDetail,
SQLitePlanDetails, or Snapshot. For compact rendering use CompactLen,
CompactBytes, CompactString, or WriteCompact.
Errors And Lifecycle¶
Runtime failures return *velr.Error with a native error code and message.
Driver-side validation uses ordinary Go errors where no native call was made.
Explicitly close DB, Tx, Stream, TxStream, Table, Rows,
Savepoint, and ExplainTrace values. Close is idempotent on all closeable
handle types. Finalizers are present as a leak safety net, but explicit close
keeps native resources deterministic.
Examples¶
Runnable examples live in github.com/velr-ai/velr-go-examples.
The examples cover basic queries, transactions and savepoints, schema migration, read-only open, BM25 fulltext search, vector/ANN search, Arrow IPC, and Arrow C Data column binding. They use the published Go module; you do not need a Velr source checkout.
License¶
See LICENSE and LICENSE.runtime.