Running queries
db.Run, db.Query, db.Exec, iterating Result, the value model, parameters, and Summary.
The three entry points
gr provides three methods for running Cypher on a *gr.DB:
| Method | Use when |
|---|---|
db.Run(ctx, query, params) |
You do not know at compile time whether the query reads or writes |
db.Query(ctx, query, params) |
The query is a read; reject it at the API level if it writes |
db.Exec(ctx, query, params) |
The query writes or changes schema; return a Summary instead of a Result |
db.Query and db.Exec are convenience wrappers around db.Run.
For read-heavy code, use db.Query — it enforces at the API level that no write sneaks into a read path.
db.Run
result, err := db.Run(ctx, `
MATCH (p:Person {name:$name})-[:KNOWS]->(friend:Person)
RETURN friend.name AS name, friend.age AS age
`, map[string]any{"name": "Alice"})
if err != nil {
return err
}
defer result.Close()
db.Run runs inside an implicit auto-commit transaction.
For explicit transactions, use tx.Run instead.
db.Query
Same signature as db.Run.
Returns an error immediately if the query text contains a write clause.
res, err := db.Query(ctx, `MATCH (n:Person) RETURN n.name`, nil)
db.Exec
For write and schema queries.
Returns (*gr.Summary, error) instead of (*gr.Result, error).
summary, err := db.Exec(ctx, `CREATE (:Person {name:$name})`, map[string]any{"name": "Alice"})
if err != nil {
return err
}
fmt.Printf("created %d node(s)\n", summary.NodesCreated())
Iterating a Result
*gr.Result is a forward-only cursor.
Call Next() before reading each record:
for res.Next() {
rec := res.Record()
name, _ := rec.Get("name")
age, _ := rec.Get("age")
fmt.Printf("%v (age %v)\n", name, age)
}
if err := res.Err(); err != nil {
return err
}
Always check res.Err() after the loop — it returns any error that occurred during streaming.
Always call res.Close() when done (a defer is the right place).
Calling Next() after it returns false is safe and always returns false.
Record
rec.Get(key) returns the value for the named column as any.
rec.GetByIndex(i) returns the value for column i.
rec.Keys() returns the column names.
rec.Values() returns all values as []any.
rec := res.Record()
name := rec.Get("name").(string)
age := rec.Get("age").(int64)
The value model
gr maps Cypher types to Go types:
| Cypher type | Go type |
|---|---|
null |
nil |
Boolean |
bool |
Integer |
int64 |
Float |
float64 |
String |
string |
Bytes |
[]byte |
List |
[]any |
Map |
map[string]any |
Node |
gr.Node |
Relationship |
gr.Relationship |
Path |
gr.Path |
Use a type switch for safe extraction:
val := rec.Get("n")
switch v := val.(type) {
case nil:
fmt.Println("null")
case bool:
fmt.Println("bool:", v)
case int64:
fmt.Println("int:", v)
case float64:
fmt.Println("float:", v)
case string:
fmt.Println("string:", v)
case []any:
fmt.Println("list len:", len(v))
case map[string]any:
fmt.Println("map:", v)
case gr.Node:
fmt.Println("node id:", v.ElementId())
case gr.Relationship:
fmt.Println("rel type:", v.Type())
case gr.Path:
fmt.Println("path length:", v.Length())
}
Parameters
Pass parameters as map[string]any.
Reference them in Cypher with $name:
MATCH (p:Person {name:$name, age:$age}) RETURN p
params := map[string]any{"name": "Alice", "age": int64(30)}
res, err := db.Query(ctx, query, params)
Parameter values must be one of the Go types in the value model table.
Nested []any and map[string]any are supported.
You cannot pass a gr.Node or gr.Relationship as a parameter — use the node's ElementId() instead.
Summary
db.Exec returns a *gr.Summary:
summary, _ := db.Exec(ctx, `...`, params)
fmt.Println(summary.NodesCreated())
fmt.Println(summary.NodesDeleted())
fmt.Println(summary.RelationshipsCreated())
fmt.Println(summary.RelationshipsDeleted())
fmt.Println(summary.PropertiesSet())
fmt.Println(summary.LabelsAdded())
fmt.Println(summary.LabelsRemoved())
fmt.Println(summary.IndexesAdded())
fmt.Println(summary.IndexesRemoved())
fmt.Println(summary.ConstraintsAdded())
fmt.Println(summary.ConstraintsRemoved())
Error types
| Error type | Trigger |
|---|---|
*gr.ParseError |
The query has a syntax error |
*gr.BindError |
The query is syntactically valid but semantically wrong (e.g., undefined variable) |
*engine.ConstraintError |
A write violated a uniqueness or existence constraint |
*gr.ConflictError |
A write-write conflict with another concurrent transaction |
context.DeadlineExceeded |
The context deadline expired during execution |
context.Canceled |
The context was cancelled |
EXPLAIN and PROFILE
EXPLAIN returns the query plan without running the query.
PROFILE runs the query and annotates the plan with row counts and timing.
res, err := db.Run(ctx, `EXPLAIN MATCH (p:Person) WHERE p.age > 25 RETURN p.name`, nil)
if err != nil {
log.Fatal(err)
}
defer res.Close()
// The plan is in res.Summary().Plan()
Or from the CLI:
gr run graph.gr "PROFILE MATCH (p:Person) WHERE p.age > 25 RETURN p.name"