Docs
View as markdown

Choose an output format

Pick the relate CLI output mode for the job, from JSON Lines for jq and CSV for spreadsheets to Markdown for notes, TOON for agents and speedscope for profiles.

cli

Before you start: Install relate

This guide shows you how to pick the relate output mode that fits the job, one step per job: rows for jq, results for a program, a spreadsheet, a notes file, an agent, and a query profile. The table names the mode for each, TOON Token-Oriented Object Notation: an output mode that prints the columns once, then one comma-separated line per row; the piped default in agent mode. Read more in the glossary for an agent among them. Pass the mode on every command whose output something else reads. Without one, relate prints a table on a terminal and JSON Lines into a pipe, so the same command prints differently in each place.

JobModeFlag
Read results at a terminaltablenone on a terminal, or --mode table
Filter or reshape rows with jqjsonl--jsonl
Parse results in a program, with types and an empty-result signalenvelope--mode envelope
Open the rows in a spreadsheetcsv--csv
Paste a table into notes or a pull requestmarkdown--markdown
Hand rows to an agent in few characterstoon--toon
Look at where a statement spends its timespeedscope--mode speedscope with PROFILE

Prerequisites

  • relate on your PATH (Install relate).
  • jq, for the JSON steps.
  • An empty working directory. The first step writes the team graph shared across the docs to team.rl there.

Build the example store

Write the seed to a file and run it into a new store. This step writes team.rl; every later step opens it --read-only:

cat > seed.cypher <<'EOF'
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace);
EOF
relate query --database ./team.rl < seed.cypher

Pipe rows to jq

Use --jsonl for a stream of rows: one JSON object per row, each on its own line, which jq reads one at a time. Each statement ends with one more line, its timings and plan-cache outcome; a filter on a row's key, such as .role, passes over it. This keeps the engineers' names:

relate --database ./team.rl --read-only --jsonl "MATCH (p:Person) RETURN p.name AS name, p.role AS role ORDER BY name" | jq -r 'select(.role == "engineer") | .name'
Ada
Grace

jsonl is also the default when stdout is a pipe, but pass it anyway: under a detected agent harness the piped default is toon, which jq can't read.

Parse results in a program

Use --mode envelope when a program reads the output. Each result set is one JSON object, the envelope One compact JSON object per result set, printed by --mode envelope, that keeps the column list even when there are no rows. Read more in the glossary, with the column order in columns, a type per column in columnTypes, the rows, and truncated, which says whether a row cap cut them:

relate --database ./team.rl --read-only --mode envelope "MATCH (p:Person)-[:WORKS_ON]->(proj:Project) RETURN proj.name AS project, count(p) AS people ORDER BY project" | jq -c '{columns, columnTypes, truncated, rows: (.rows | length)}'
{"columns":["project","people"],"columnTypes":["STRING","INTEGER"],"truncated":false,"rows":2}

A read with no rows still prints an envelope with its columns, where jsonl prints only its timing line. A failed statement prints an error object The JSON object a failed query run prints on stdout in the json, jsonl, envelope and speedscope modes: the class, the kind, the message, the exit code. Read more in the glossary on stdout in place of the envelope, so a program can tell no rows, no output, and a failure apart.

Export CSV for a spreadsheet

Use --csv and redirect to a file. The first line is the header:

relate --database ./team.rl --read-only --csv "MATCH (p:Person) OPTIONAL MATCH (p)-[:WORKS_ON]->(proj:Project) RETURN p.name AS name, p.role AS role, count(proj) AS projects ORDER BY name" > people.csv
cat people.csv
name,role,projects
Ada,engineer,1
Grace,engineer,2
Noor,maintainer,0

To leave the header out, for appending to an existing file, add --noheader. A field that starts with =, +, - or @ gets a ' in front, so a spreadsheet shows it as text instead of running it as a formula; a negative number prints as '-5.

Paste a Markdown table into notes

Use --markdown for a table that renders in a Markdown file, an issue or a pull request:

relate --database ./team.rl --read-only --markdown "MATCH (proj:Project)<-[:WORKS_ON]-(p:Person) RETURN proj.name AS project, collect(p.name) AS people ORDER BY project"
| project          | people           |
|------------------|------------------|
| "opencypher-tck" | ["Grace"]        |
| "relate"         | ["Grace", "Ada"] |

Strings keep their quotes, and a | inside a value is escaped, so a value can't add a column. Append to a notes file with >> notes.md.

Give an agent TOON

Use --toon for rows an agent reads. The header names the row count and the columns once, then each row is one line of values, and the timings: and planCache: entries close the block:

relate --database ./team.rl --read-only --toon "MATCH (p:Person)-[w:WORKS_ON]->(proj:Project) RETURN p.name AS person, proj.name AS project, w.since AS since ORDER BY since"
rows[3]{person,project,since}:
  Grace,opencypher-tck,2022
  Grace,relate,2023
  Ada,relate,2024
timings:
  {"elapsed_ms":0.428167,"execute_ms":0.247,"lex_parse_ms":0.031291,"lower_ms":0.0425,"parse_ms":0.111333,"plan_ms":0.061,"result_generation_ms":0.00029099999999999997,"rewrite_ms":0.008291999999999999}
planCache:
  miss

Under agent mode CLI defaults for a detected agent harness on a non-terminal stdout, or forced on: at most 100 rows printed per statement, the cut reported, TOON in a pipe. Read more in the glossary, toon is already the default when stdout is piped. It prints no error object: a failure shows only as the stderr message and the exit code. If the agent branches on failures in its output, pass --mode envelope instead.

Profile a statement in speedscope

Use --mode speedscope with a PROFILE statement and save the output to a file. It holds the statement's plan The tree of operators the planner chooses to run a statement; EXPLAIN shows it without running, PROFILE runs and adds rows and time per operator. Read more in the glossary, one frame per operator, with the time each one took:

relate --database ./team.rl --read-only --mode speedscope "PROFILE MATCH (p:Person)-[:WORKS_ON]->(proj:Project) RETURN p.name AS person, proj.name AS project" > profile.json
jq -r '.shared.frames[].name' profile.json
ProduceResults 0
Expand 1

Open profile.json in the speedscope viewer at speedscope.app. The times change from run to run; the frames and their nesting don't. To read the plan as a table instead, run the PROFILE statement with --mode table. Tune a query covers reading plans.

Verify

Check that each file holds what its consumer expects: a CSV header for the spreadsheet, and an evented profile for speedscope:

head -n 1 people.csv
jq -r '.profiles[0].type, .profiles[0].unit' profile.json
name,role,projects
evented
nanoseconds

If it goes wrong

What you seeCauseRemedy
jq reports a parse error on piped outputAgent mode is on (a detected agent harness, --agent-mode or RELATE_AGENT_MODE=1), so the piped default is toonPass --jsonl or --mode envelope explicitly
speedscope mode requires a PROFILE statement: per-operator times are what it exports (the statement carries no plan tree), exit code 1The statement has no PROFILE prefix. The statement still ran: its writes are committedStart the statement with PROFILE
speedscope mode requires a PROFILE statement: per-operator times are what it exports (the plan tree carries no PROFILE metrics), exit code 1The plan's top operator recorded no metrics; as of 0.1.2, a count() over an Expand, such as MATCH (p:Person)-[:WORKS_ON]->(proj:Project) RETURN count(p), does this. The statement still ran: its writes are committedProfile the statement without its aggregation, or read the plan with --mode table
A spreadsheet column shows '-5The CSV formula guard prefixes a field that starts with -Strip the leading ' on import, or export with --jsonl
Error: invalid mode '<name>' (expected one of: table, csv, json, jsonl, envelope, markdown, cypher, toon, speedscope), exit code 2A mode name relate doesn't haveUse one of the nine names the message lists
  • Output modes: every mode's exact output, its defaults, --pretty and --noheader
  • CLI flags: the output flags and --width
  • Agent mode: the piped toon default and the row cap
  • Script the CLI: exit codes and the envelope in shell scripts