Skip to content

Query DSL

Crystal provides a fluent query builder for filtering, traversing, and paginating graph data.

Query Types

Vertex Queries

Start with query.Vertices() or db.QueryVertices().

q := query.Vertices().
    OfType("Person").
    WithLabel("active").
    PropertyEquals("city", "Berlin").
    HasProperty("email").
    Where(func(props core.Properties) bool {
        age, ok := props.GetFloat("age")
        return ok && age >= 18
    }).
    SortBy("name", false).
    Limit(20).
    Offset(0)

Edge Queries

Start with query.Edges() or db.QueryEdges().

q := query.Edges().
    OfType("knows").
    PropertyEquals("status", "confirmed").
    Limit(100)

Neighbor Queries

Start with query.From(vertexID) or db.QueryFrom(vertexID).

q := query.From(startID).
    Out().
    OfType("Server").
    Depth(3).
    Where(func(props core.Properties) bool {
        _, hasIP := props["ip"]
        return hasIP
    }).
    Limit(50)

Methods Reference

Type Filtering

Method Description
OfType(t string) Filter by vertex or edge type
WithLabel(l string) Require vertex to have this label

Property Filtering

Method Description
HasProperty(key string) Property key must exist
PropertyEquals(key string, val any) Property must equal value
Where(fn FilterFunc) Custom filter function

FilterFunc has the signature func(props core.Properties) bool.

Direction (Neighbor Queries)

Method Description
Out() Follow outgoing edges (default)
In() Follow incoming edges
Both() Follow edges in both directions

Traversal Depth

Method Description
Depth(d int) How many hops to traverse (default 1)

Pagination and Sorting

Method Description
Limit(n int) Maximum results to return
Offset(n int) Skip first N results
SortBy(field string, reverse bool) Sort by property value

Execution

Queries are executed through the Executor:

executor := query.NewExecutor(store)
result, err := executor.Execute(ctx, q)

Or through the DB wrapper:

result, err := db.Execute(ctx, q)

The context is honoured: a query is assembled from several storage calls, and a cancelled one stops between them rather than running to completion for a caller that has gone away.

db.Execute runs the query inside a consistent view, so a result set cannot mix states of the graph. To run several queries against one view, open it yourself:

err := db.View(ctx, func(view crystal.Reader) error {
    people, err := query.NewExecutor(view).Execute(ctx, query.Vertices().OfType("Person"))
    if err != nil {
        return err
    }
    servers, err := query.NewExecutor(view).Execute(ctx, query.Vertices().OfType("Server"))
    // people and servers are guaranteed to describe the same instant
    ...
})

Result

type Result struct {
    Vertices []*core.Vertex
    Edges    []*core.Edge
    Count    int
}
  • Vertex queries populate Vertices
  • Edge queries populate Edges
  • Neighbor queries populate Vertices
  • Count is always the number of returned items

REST API

Queries can also be executed over HTTP:

curl -X POST http://localhost:7540/api/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "type": "vertices",
    "vertex_type": "Person",
    "properties": {"city": "Berlin"},
    "limit": 10
  }'

See api.md for full details.