Cypher
The read-only Cypher dialect at a glance: supported clauses, subqueries and functions, the five golden rules, and the full language reference pages.
On this page (5)
Cypher Documentation
WhisperGraph speaks a read-only dialect of Cypher, the graph query language, over HTTP. You POST a query to the Cypher API and get back columns and rows; there is no driver to install and no session to manage. If you have used Neo4j, you already know most of the language. Write clauses (CREATE, MERGE, SET, DELETE, REMOVE, FOREACH) are recognized by the parser and rejected. Parameters bind with $name through the request's parameters field, and several statements separated by ; run as one batch.
This section is the language reference. Syntax & Clauses covers every supported clause with verified examples, Functions documents the full function library, Best Practices collects the habits that separate an instant answer from a query that grinds, and the Cheat Sheet fits the whole language on one page.
The shape of a query
Almost every WhisperGraph query is anchor, traverse, return: pin a node by its indexed name, walk the edges you care about, project columns, cap the rows.
MATCH (h:HOSTNAME {name: "google.com"})-[:RESOLVES_TO]->(ip:IPV4)
RETURN ip.name AS ip
LIMIT 5
On a graph with 39.5B edges, the anchor is what makes this fast. The engine starts at one indexed node and touches only connected edges. Skip the anchor and you ask for a scan over billions of nodes, which is slow at best and rejected at worst.
For anything with more than one stage, narrow before you expand. WITH ... LIMIT bounds the intermediate set so the next stage starts from a handful of nodes:
MATCH (h:HOSTNAME {name: "google.com"})<-[:NAMESERVER_FOR]-(ns:HOSTNAME)
WITH ns LIMIT 3
MATCH (ns)-[:NAMESERVER_FOR]->(sibling:HOSTNAME)
RETURN ns.name AS nameserver, collect(DISTINCT sibling.name)[0..8] AS domains
LIMIT 3
The same building blocks shape results: UNWIND turns a list of indicators into one anchored lookup per element, and aggregation (count, collect) with ORDER BY ... LIMIT ranks a traversal. Both are covered with examples in Syntax & Clauses.
Labels and edge names must match the live schema exactly; the graph uses HOSTNAME (there is no Domain or FQDN label). An unknown name in an anchored pattern matches nothing, so when a query returns nothing, check CALL db.labels() and CALL db.relationshipTypes() first — both are cheap and both answer immediately. Labels carried over from other graph products (Domain, IpAddress, Certificate) are rejected with an error that names the label to use instead. The full model is in the Graph Schema.
Read
coveragebeforeband. Only known-clean — coverage: known-clean. In coverage, no malicious evidence. licenses the word "clean"; no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. means unknown, which is a different thing again; malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. and ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. mean there is evidence, whatever the band says.whisper.explaindoes not returncoverageat all. Full contract: Coverage — what we looked at.
The five golden rules
- Anchor on an indexed
name, with the label. Pin at least one node with{name: "..."}. Names are stored lowercase, so lowercase the value in your own code rather than wrapping the anchor intoLower(), which turns the lookup into a scan. Unanchored scans on billion-node labels likeHOSTNAMEandIPV4do not finish. - Always
LIMIT. On every query, includingCALL ... YIELD ... RETURN. For graph-wide totals, read the precomputed stats endpoint orCALL db.relationshipTypes() YIELD type, countinstead of counting edges. - Bound every branch. Narrow intermediates with
WITH ... LIMITbefore expanding, and put per-branch work in a boundedCALL { ... }subquery so one high-fan-out hop can't blow up the whole query. ALIMITat the end does not bound the traversal that feeds it, andcollect(DISTINCT x)[0..N]slices after collecting, so the bound goes before the fan-out. - Walk edges in their stored direction, and keep variable-length walks bounded. Mail and nameserver edges point server → domain, so a domain's MX is
(d)<-[:MAIL_FOR]-(mx). Edges computed at query time (ROUTES,ANNOUNCED_BY,LISTED_IN,BGP_NEIGHBOR) work inside[*1..N]when one endpoint is anchored; always write the upper bound, useBGP_NEIGHBOR(notPEERS_WITH) for peering, and filterWHERE n <> aso the walk does not report the origin as its own neighbour. - Procedures first.
explain(),whisper.assess(),whisper.enrich(),whisper.identify(),whisper.search(),whisper.variants(),whisper.history(), andwhisper.origins()answer the hardest questions in one call, without the wide traversal a hand-written equivalent needs. Quote every argument, andYIELDthe exact column names. Signatures are in Procedures.
Best Practices expands each rule into do-this-not-that pairs and the working rules behind them.
Clauses at a glance
Every clause below is documented with runnable examples in Syntax & Clauses.
| Clause | What it does |
|---|---|
MATCH / OPTIONAL MATCH | Find graph patterns; OPTIONAL MATCH keeps rows and fills null for sparse data like WHOIS contacts. |
WHERE | Filter with comparisons, AND/OR/NOT/XOR, IN, IS NULL, STARTS WITH / ENDS WITH / CONTAINS, and =~ full-match regex. |
RETURN | Project columns; AS aliases, DISTINCT deduplicates, RETURN * returns every bound variable. |
WITH | Pipe one stage into the next; aggregate, filter, or bound (WITH ... LIMIT) mid-query. |
ORDER BY / LIMIT / SKIP | Sort, cap, and page results with literal numbers. |
UNWIND | Expand a list into rows; the batch-lookup pattern, and the replacement for a long IN list. |
UNION / UNION ALL | Combine branches that return the same column names; each branch can carry its own LIMIT. |
CALL | Run a procedure with YIELD (once per incoming row after UNWIND), or scope a bounded CALL { ... } subquery. |
EXISTS { } / COUNT { } | Test or count a pattern as an expression without binding it. |
[x IN list WHERE ... | ...] / [(a)-->(b) | b.name] | List and pattern comprehensions; build lists inline, then slice them. |
CASE | Conditional expressions, simple and searched. |
$name | Parameters, bound through the request's parameters object. |
; | Multi-statement batching; the response becomes a results array with one outcome per statement. |
EXPLAIN / PROFILE | Return the query plan (PROFILE also runs it and reports rows and execution time); confirm the anchor hits the index (NodeLookup, never a label scan). |
shortestPath | Minimum-hop path between two anchored nodes; requires an explicit, tight path bound. |
Functions at a glance
Full signatures, examples, and return values are in Functions.
| Group | Functions |
|---|---|
| Aggregation | count, sum, avg, min, max, collect, each with DISTINCT; plus percentileCont, percentileDisc, stDev, stDevP |
| String | toUpper/upper, toLower/lower, trim, ltrim, rtrim, replace, substring, split, left, right, reverse, size/length, isEmpty, toString |
| Numeric & trig | abs, ceil/ceiling, floor, round, sign, sqrt, log, ln, log10, exp, e, pi, rand, sin, cos, tan, asin, acos, atan, atan2, degrees, radians |
| Collection | size, head, last, tail, range, reverse, keys, isEmpty |
| Node & relationship | id (a string), elementId, label, labels, type, properties, startNode, endNode, nodes, relationships, length |
| Type conversion | toInteger/toInt, toFloat, toBoolean, toIntegerList, toFloatList, toStringList, toBooleanList |
| Date & time | timestamp, date, datetime, localdatetime, time, localtime, duration, duration.between, duration.inDays, duration.inMonths, duration.inSeconds |
| Geo & misc | point, distance/point.distance, coalesce, randomUUID |
Go deeper
- Syntax & Clauses — every supported clause, subquery form, parameter binding, batching, and pattern and path syntax.
- Functions — the full library with signatures and example results.
- Best Practices — do this, not that; the working rules and how to design around them.
- Cheat Sheet — the language on one page, ready to copy.
- Cypher API — the endpoint, how to send your key in the
X-API-Keyheader, and the response envelope. - Cross-cutting recipes — the patterns that keep large or repetitive jobs fast.
- Use Cases — copy-paste recipes and runnable workflows organized by job.