Skip to main content

Query Result Formats

Every query returns the same data; the engine can shape it into one of four wire formats. The choice is a trade-off between payload size, ease of access in host code, and whether you want a row view or a graph view.

The four formats

FormatShapeUse when…
rowsOne object per row, keyed by column.Most app code — each row reads like a record.
rowArraysColumns plus tuple rows.Wide result sets where keys are repetitive.
graphDe-duplicated nodes + relationships.Rendering a graph — visualisers, diffs.
combinedRows and a side graph.You want both per-row data and the underlying entities.

Choosing a format

If your consumer…Pick
Reads values by column namerows
Processes many rows with the same columnsrowArrays (smaller payload)
Draws a graph of matched entitiesgraph
Needs rows and the graph they touchedcombined

If you don't care: rows is the most ergonomic default in host code.

Setting the format

TransportHow
Rust / Node / Python / WASM / Go / RubyExecuteOptions { format } or per-call option
HTTP"format": "rows" in the POST /query body

If omitted, the engine-default is graph. Bindings may override that default for ergonomics — for example, lora-node and lora-python return rows unless you ask for another shape.

rows

The most natural shape for application code: one JSON object per row, keyed by the RETURN column name.

Example

For MATCH (p:Person) RETURN p.name AS name, p.born AS born:

{
"rows": [
{ "name": "Ada", "born": 1815 },
{ "name": "Grace", "born": 1906 }
]
}

When to prefer it

  • You want to iterate with row.name, row["name"], etc.
  • Result sets are small-to-medium; the per-row key repetition isn't a concern.

rowArrays

Columns are listed once; rows are arrays indexed by column position.

Example

{
"columns": ["name", "born"],
"rows": [
["Ada", 1815],
["Grace", 1906]
]
}

When to prefer it

  • Large result sets — avoids repeating the column keys per row.
  • You're streaming into a tabular UI that already knows column order.

Gotcha

You index rows by position. A schema change (e.g. reordering RETURN expressions) breaks downstream code. Prefer rows for code that's read outside the query author's head.

graph

Every matched node and relationship, de-duplicated, with labels, types, and properties hydrated.

Example

For MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a, r, b:

{
"graph": {
"nodes": [
{ "id": 1, "labels": ["Person"], "properties": { "name": "Ada" } },
{ "id": 2, "labels": ["Person"], "properties": { "name": "Grace" } }
],
"relationships": [
{ "id": 10, "startId": 1, "endId": 2, "type": "KNOWS", "properties": {} }
]
}
}

When to prefer it

  • You're rendering the graph in a visualiser.
  • You need every entity the query touched, exactly once, regardless of how many rows referenced it.

Gotcha

Scalar projections (RETURN p.name) don't contribute new entities to the graph — you'll get an empty nodes / relationships list unless the query returns nodes or relationships directly.

combined

Rows and a side graph in one payload. Useful when you want both per-row results and the backing entities (for example: rows for a table, and nodes+edges for a graph preview beside it).

Example

{
"columns": ["a", "r", "b"],
"data": [
{ "a": { "$node": 1 }, "r": { "$rel": 10 }, "b": { "$node": 2 } }
],
"graph": {
"nodes": [ /* nodes 1 and 2, as in the `graph` format */ ],
"relationships": [ /* relationship 10 */ ]
}
}

Entities in data appear as references (e.g. {"$node": 1}); their full hydrated form lives in graph. This keeps the payload compact when the same node appears in many rows.

When to prefer it

  • Dual views (table + graph) of the same query.
  • You've got a UI that links a table row to a node on a canvas.

Typed values in every format

Regardless of format, scalar and structured values round-trip through the same shared contract:

  • Primitives — Null, Boolean, Integer, Float, String.
  • Lists & maps.
  • Temporal values — tagged with {kind: "date" | "datetime" | …, iso: "…"}.
  • Spatial points — tagged with {kind: "point", srid, crs, x, y[, z]}.
  • Graph values — tagged with {kind: "node" | "relationship" | "path"}.

Each language binding provides narrow type guards to tell them apart (isNode, is_point, …).

See also