Cypher Manual
Jetgraph implements a custom subset of the openCypher query language optimised for high-throughput, low-latency graph analytics on fraud detection workloads. This manual is the authoritative reference for every query feature the engine supports. Where Jetgraph behaviour differs from standard Neo4j Cypher, the difference is called out explicitly.
1. Connecting & Sending Queries
HTTP API (primary interface)
POST http://<host>:8080/cypher
Content-Type: application/json
Request body:
{
"query": "MATCH (c:CARD {external_id: $id}) RETURN c.external_id",
"parameters": { "id": "card-001" }
}Response:
{
"columns": ["c.external_id"],
"rows": [["card-001"]],
"elapsed_us": 142
}Bolt (port 7687)
The engine also accepts queries over the Bolt protocol (Neo4j driver
compatible). Connect with any Neo4j driver pointing to
bolt://<host>:7687.
Comments
Single-line comments are supported. Use // β everything
after // until the end of the line is ignored by the
parser.
// Retrieve all merchants a card transacts at
MATCH (c:CARD {external_id: $card_id})-[:TRANSACTS_AT]->(m:MERCHANT)
RETURN m.external_id
2. Query Structure
A query is a sequence of one or more clauses. Supported clauses, in the order they would typically appear:
| Clause | Category |
|---|---|
MATCH / OPTIONAL MATCH |
Read |
WHERE |
Filter |
WITH |
Projection / pipeline |
UNWIND |
List expansion |
CALL |
Procedure invocation |
CREATE |
Write |
MERGE |
Conditional write |
SET |
Property update |
DELETE / DETACH DELETE |
Node / edge removal |
RETURN |
Output |
Multiple clauses in one query are supported and
executed in the order they appear. A single query may only contain one
statement β UNION / UNION ALL as a single
request is not supported (see Β§23).
3. Data Types & Literals
Scalar literals
| Type | Example |
|---|---|
null |
null |
| Boolean | true, false |
| Integer | 42, -7, 1_000_000 |
| Float | 3.14, -0.5, 1.2e10 |
| String | 'hello', "world",
'it\'s fine' |
Collection literals
// List
[1, 2, 3]
['a', 'b', 'c']
// Map
{name: 'Alice', score: 0.95}
Value types returned in results
Null, Bool, Integer,
Float, String, List,
Map, Node, Relationship,
Path.
4. Parameters
Parameters are strongly recommended over embedded literals. Using parameters produces a single cached query plan that is reused for every invocation. Hardcoding literal values creates a new cache entry per unique value.
Named parameters
MATCH (c:CARD {external_id: $card_id})-[:TRANSACTS_AT]->(m:MERCHANT)
RETURN m.external_id
Supply parameters in the parameters field of the request
body:
{ "card_id": "9792487647826207" }Positional parameters
MATCH (c:CARD {external_id: $0}) RETURN c
["9792487647826207"]Parameter types supported
String, Integer, Float, Boolean, Null, List, Map.
5. MATCH
MATCH is the primary read clause. It describes a graph
pattern to find.
Basic node match
MATCH (c:CARD)
RETURN c.external_id
LIMIT 25
Node match with inline property filter
MATCH (c:CARD {external_id: $card_id})
RETURN c
Node-relationship-node pattern
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id, r.tx_count, r.approx_sum
Relationship direction
| Syntax | Meaning |
|---|---|
(a)-[:TYPE]->(b) |
Directed β a is the source |
(a)<-[:TYPE]-(b) |
Directed β b is the source (same as above, reversed
variable binding) |
(a)-[:TYPE]-(b) |
Undirected β matches either direction |
OPTIONAL MATCH
Returns null for the optional side when no match is
found (left-outer join semantics). All bound variables from the
OPTIONAL MATCH will be null if the pattern
does not exist.
MATCH (c:CARD {external_id: $card_id})
OPTIONAL MATCH (c)-[:FLAGGED_BY]->(alert:ALERT)
RETURN c.external_id, alert.external_id AS alert_id
Multiple comma-separated patterns (Cartesian product)
MATCH (c:CARD {external_id: $c}), (m:MERCHANT {external_id: $m})
RETURN c, m
Path variable assignment
Assign the entire matched path to a variable for use in functions
like length().
MATCH p = (c:CARD)-[:TRANSACTS_AT*1..3]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN length(p) AS hops, m.external_id
Labels
Node labels are case-insensitive in the query text
but are stored and matched in uppercase. Write
CARD or card β both resolve to the same
type.
Only one label per node is supported. Multi-label
patterns like (n:A:B) are not supported.
6. WHERE
WHERE filters rows using a boolean expression. It can
follow MATCH, OPTIONAL MATCH, or
WITH.
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
AND r.tx_count > 5
AND m.name CONTAINS 'Airlines'
RETURN m.external_id, r.tx_count
Inline WHERE on variable-length relationships
MATCH (c:CARD)-[r:TRANSACTS_AT*1..3 WHERE r.last_seen > $cutoff]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN DISTINCT m.external_id
Supported inline relationship filter fields: last_seen,
tx_count, approx_sum / amount,
bool_flag.
7. RETURN
RETURN projects results into the response columns.
Basic return
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id AS merchant, r.tx_count AS transactions
RETURN DISTINCT
Deduplicates result rows after all other processing.
MATCH (c:CARD)-[:TRANSACTS_AT*1..2]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN DISTINCT m.external_id AS merchant
ORDER BY
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id, r.tx_count
ORDER BY r.tx_count DESC
Multiple sort keys:
RETURN m.external_id, r.tx_count, r.approx_sum
ORDER BY r.tx_count DESC, r.approx_sum DESC
Default sort direction is ASC. Use DESC for
descending.
LIMIT and SKIP
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id, r.tx_count
ORDER BY r.tx_count DESC
SKIP 10
LIMIT 20
LIMIT is pushed down into node scans and BFS expansion β
always add LIMIT to exploratory queries to prevent full
graph materialisation.
8. WITH
WITH is an intermediate projection step. It passes a
subset of variables (and computed values) to the next clause, and can
also filter, sort, and paginate mid-query.
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WITH c, count(m) AS merchant_count
WHERE merchant_count > 3
RETURN c.external_id AS card, merchant_count
ORDER BY merchant_count DESC
LIMIT 50
WITH DISTINCT
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WITH DISTINCT c, m
MATCH (m)-[:LOCATED_IN]->(city:CITY)
RETURN c.external_id, city.external_id
WITH β¦ ORDER BY β¦ SKIP β¦ LIMIT
WITH accepts the same ordering and pagination modifiers
as RETURN.
9. CREATE
CREATE adds new nodes and relationships to the
graph.
Create a node
CREATE (c:CARD {external_id: $card_id})
Create a node with multiple properties
CREATE (m:MERCHANT {external_id: $merchant_id, name: $name, category: $category})
Create a relationship between existing nodes
MATCH (c:CARD {external_id: $card_id}), (m:MERCHANT {external_id: $merchant_id})
CREATE (c)-[:TRANSACTS_AT]->(m)
Create nodes and a relationship in one pattern
CREATE (c:CARD {external_id: $card_id})-[:USES_DEVICE]->(d:DEVICE {external_id: $device_id})
Tip: For bulk creation use
CALL graph.ingest()β it processes multiple nodes and edges in a single round-trip without per-statement planning overhead.
10. MERGE
MERGE is an upsert. It finds a matching pattern in the
graph; if none exists it creates it. The ON CREATE SET and
ON MATCH SET sub-clauses let you apply different updates
depending on whether the node was just created or already existed.
Basic MERGE
MERGE (c:CARD {external_id: $card_id})
RETURN c
MERGE with ON CREATE SET and ON MATCH SET
MERGE (c:CARD {external_id: $card_id})
ON CREATE SET c.created_at = $now, c.risk_score = 0.0
ON MATCH SET c.last_seen = $now
RETURN c
MERGE a relationship
MATCH (c:CARD {external_id: $card_id}), (m:MERCHANT {external_id: $merchant_id})
MERGE (c)-[:TRANSACTS_AT]->(m)
Tip: For high-frequency edge upserts (recording payment events) use
CALL graph.upsertEdge()instead ofMERGE. It is an O(1) atomic counter update with no planner involvement.
11. SET
SET updates properties on existing nodes or
relationships.
Set a single property
MATCH (c:CARD {external_id: $card_id})
SET c.risk_score = $score
Set multiple properties
MATCH (c:CARD {external_id: $card_id})
SET c.risk_score = $score, c.reviewed_at = $ts
Merge properties (+=)
+= merges the supplied map into the nodeβs existing
properties without removing unmentioned properties.
MATCH (c:CARD {external_id: $card_id})
SET c += {risk_score: $score, reviewed_at: $ts}
Replace all properties (=)
= replaces the entire property map.
MATCH (c:CARD {external_id: $card_id})
SET c = {external_id: $card_id, risk_score: $score}
12. DELETE & DETACH DELETE
DELETE a node
DELETE tombstones the node β it becomes invisible to
subsequent scans and traversals.
MATCH (c:CARD {external_id: $card_id})
DELETE c
DELETE a relationship
MATCH (c:CARD {external_id: $card_id})-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE m.external_id = $merchant_id
DELETE r
DETACH DELETE
DETACH DELETE removes all edges of every type connected
to the node, then removes the node itself.
MATCH (c:CARD {external_id: $card_id})
DETACH DELETE c
For procedure-based node deletion see
graph.deleteNode()in Β§14.
13. UNWIND
UNWIND expands a list into individual rows. Each element
of the list becomes a separate row in the result stream.
UNWIND $card_ids AS card_id
MATCH (c:CARD {external_id: card_id})-[:TRANSACTS_AT]->(m:MERCHANT)
RETURN card_id, m.external_id AS merchant
{ "card_ids": ["card-001", "card-002", "card-003"] }UNWIND with a literal list
UNWIND [1, 2, 3] AS n
RETURN n * 2 AS doubled
14. CALL Procedures
Procedures extend the query engine with domain-specific operations.
Invoke them with CALL, passing arguments in parentheses,
and project their output columns with YIELD.
CALL procedure.name(arg1, arg2)
YIELD column1, column2
RETURN column1, column2
YIELD is optional for most procedures. When omitted, all
output columns are available to subsequent clauses.
graph.* β Data & Analytics
graph.upsertEdge
Atomically increments the edge counter between two nodes. The preferred method for recording transaction events.
CALL graph.upsertEdge($edge_type, $src_ref, $dst_ref)
YIELD created_new, tx_count, approx_sum
RETURN created_new, tx_count, approx_sum
With optional numeric value and timestamp:
CALL graph.upsertEdge(
$edge_type,
$src_ref,
$dst_ref,
$amount,
$event_ts_secs
)
YIELD created_new, tx_count, approx_sum
| Parameter | Type | Required | Description |
|---|---|---|---|
edge_type |
String | Yes | Edge type name, e.g.Β 'TRANSACTS_AT' |
src_ref |
String | Yes | 'NodeType:external_id' or node variable |
dst_ref |
String | Yes | 'NodeType:external_id' or node variable |
amount |
Float | No | Numeric value added to approx_sum |
event_ts_secs |
Integer | No | Event Unix timestamp (seconds) |
| YIELD column | Type | Description |
|---|---|---|
created_new |
Bool | true if the edge was created, false if
updated |
tx_count |
Integer | Running transaction count after this operation |
approx_sum |
Float | Approximate sum of all recorded amounts |
graph.ingest
Batch-creates nodes and edges in a single round-trip. The preferred method for bulk loading.
CALL graph.ingest($nodes, $edges)
YIELD ok, nodes_created, nodes_existing, edges_created, edges_updated
RETURN ok, nodes_created, edges_created
Node map fields:
| Field | Required | Description |
|---|---|---|
node_type (or nodeType) |
Yes | Node type name |
externalId (or external_id) |
No | External identifier string |
key |
No | Within-batch reference key for edge
src/dst |
properties |
No | Map of property name β value |
Edge map fields:
| Field | Required | Description |
|---|---|---|
edge_type (or edgeType) |
Yes | Edge type name |
src |
Yes | Within-batch key, bare externalId, or
'Type:ext_id' |
dst |
Yes | Same formats as src |
numeric_value (or numericValue) |
No | Amount to record |
event_ts_secs (or eventTsSecs) |
No | Event Unix timestamp |
Example:
CALL graph.ingest(
[
{node_type: 'CARD', externalId: $card_id},
{node_type: 'MERCHANT', externalId: $merchant_id}
],
[
{edge_type: 'TRANSACTS_AT', src: $card_id, dst: $merchant_id,
numeric_value: $amount, event_ts_secs: $ts}
]
) YIELD ok, nodes_created, edges_created
RETURN ok, nodes_created, edges_created
graph.edgeState
Reads the current aggregate state of an edge between two nodes.
CALL graph.edgeState($src_ref, $dst_ref, $edge_type)
YIELD tx_count, approx_sum, last_seen, bool_flag, activity_counts
RETURN tx_count, approx_sum, last_seen
With custom activity windows (list of tick counts):
CALL graph.edgeState($src_ref, $dst_ref, $edge_type, [1, 7, 30])
YIELD tx_count, approx_sum, last_seen, bool_flag, activity_counts
| YIELD column | Type | Description |
|---|---|---|
tx_count |
Integer | Total transaction count |
approx_sum |
Float | Approximate cumulative sum |
last_seen |
Integer | Last event Unix timestamp (seconds) |
bool_flag |
Bool | Boolean flag from the compact payload |
activity_counts |
List<Integer> | Transaction counts per activity window tick |
Returns zero rows if no edge exists between the two nodes.
graph.lastNeighbor
Returns the most recently seen neighbor of a node on a given edge type.
CALL graph.lastNeighbor($node_ref, $edge_type)
YIELD found, neighbor_node_id, last_seen_secs
RETURN found, neighbor_node_id, last_seen_secs
Optionally exclude a specific neighbor:
CALL graph.lastNeighbor($node_ref, $edge_type, $exclude_ref)
YIELD found, neighbor_node_id, last_seen_secs
| YIELD column | Type | Description |
|---|---|---|
found |
Bool | true if a neighbor was found |
neighbor_node_id |
Integer | Internal numeric node ID of the neighbor |
last_seen_secs |
Integer | Unix timestamp of the most recent event |
graph.fraudContext
Returns all fraud cases that include a given node.
CALL graph.fraudContext($node_ref)
YIELD flagged, cases, case_count, max_risk_score
RETURN flagged, case_count, max_risk_score, cases
| YIELD column | Type | Description |
|---|---|---|
flagged |
Bool | true if the node is in any fraud case |
cases |
List<Map> | List of case maps with case_id,
case_node_id, fraud_score,
reason |
case_count |
Integer | Number of cases |
max_risk_score |
Float | Highest fraud score across all associated cases |
graph.createFraudCase
Creates a new fraud case and links participant nodes to it.
CALL graph.createFraudCase($case_id, $participants, $score, $reason)
YIELD ok, case_node_id, case_id
RETURN ok, case_node_id, case_id
| Parameter | Type | Description |
|---|---|---|
case_id |
String | Unique identifier for the case |
participants |
List<String> | List of 'Type:external_id' node refs |
score |
Float | Risk score (default 1.0) |
reason |
String | Free-text reason (default 'manual_case') |
graph.addFraudCaseNodes
Adds additional nodes to an existing fraud case.
CALL graph.addFraudCaseNodes($case_id, $participants, $score)
YIELD ok
graph.removeFraudCaseNode
Removes a node from a fraud case.
CALL graph.removeFraudCaseNode($case_id, $participant_ref)
YIELD ok
graph.deleteNode
Deletes a node by reference.
CALL graph.deleteNode($node_ref)
YIELD ok
$node_ref can be a 'Type:external_id'
string or a numeric node ID.
graph.histogram
Returns a time-windowed transaction amount histogram for a node.
CALL graph.histogram($node_ref, $edge_type, $window_hours, $window_days)
YIELD buckets, counts
RETURN buckets, counts
| Parameter | Type | Description |
|---|---|---|
node_ref |
String / Node | Source node |
edge_type |
String | Edge type |
window_hours |
Integer | Hour-level window size (takes priority over
window_days) |
window_days |
Integer | Day-level window size |
| YIELD column | Type | Description |
|---|---|---|
buckets |
List<Integer> | Bucket indices |
counts |
List<Integer> | Transaction counts per bucket |
graph.featureVector
Returns a numeric feature vector for a node across multiple edge types. Useful as input for downstream ML models.
CALL graph.featureVector($node_ref, $edge_types, $window_hours, $window_days)
YIELD vector
RETURN vector
{
"node_ref": "CARD:card-001",
"edge_types": ["TRANSACTS_AT", "USES_DEVICE", "USES_IP"]
}| YIELD column | Type | Description |
|---|---|---|
vector |
List<Float> | Feature values (outbound count + approx sum per edge type) |
graph.findSimilar
Finds the top-k most similar nodes to a given node using Jaccard similarity over shared neighbors on an edge type.
CALL graph.findSimilar($node_ref, $edge_type, $k)
YIELD similar, score
WHERE score > 0.3
RETURN similar, score
ORDER BY score DESC
| Parameter | Type | Default | Description |
|---|---|---|---|
node_ref |
String | β | 'Type:external_id' |
edge_type |
String | β | Edge type to compute similarity over |
k |
Integer | 10 |
Number of top results to return |
| YIELD column | Type | Description |
|---|---|---|
similar |
Integer | Internal node ID of the similar node |
score |
Float | Jaccard similarity score (0β1) |
graph.buildSimilarityGraph
Builds a similarity graph over a node type in bulk. For each pair of nodes that exceed a minimum Jaccard threshold, an edge is created or updated in the graph.
CALL graph.buildSimilarityGraph({
node_type: 'CARD',
edge_types: ['TRANSACTS_AT', 'USES_DEVICE'],
similar_to_edge_type: 'SIMILAR_TO',
k: 20,
min_similarity: 0.2
})
YIELD edges_created, edges_updated, nodes_processed, elapsed_ms
RETURN edges_created, nodes_processed, elapsed_ms
Spec map fields:
| Field | Type | Default | Description |
|---|---|---|---|
node_type |
String | β | Node type to process (required) |
edge_types |
List<String> | β | Edge types used in similarity (at least one required) |
weighted_edge_types |
List<Map> | β | [{edge_type: 'X', weight: 2.0}] β overrides plain
edge_types weights |
required_edge_types |
List<String> | [] |
Both nodes must share these types |
bool_property_weights |
List<Map> | [] |
[{property_name: 'is_high_value', weight: 1.5}] |
required_bool_properties |
List<String> | [] |
Both nodes must have these bool properties set |
similar_to_edge_type |
String | β | Edge type to write similarity edges into |
k |
Integer | 10 |
Max similar nodes per source |
min_similarity |
Float | 0.0 |
Minimum Jaccard score threshold |
graph.clearEdgeTypeData
Removes all edge pairs of a given edge type from the compact store. Used for data management and TTL-style cleanup.
CALL graph.clearEdgeTypeData($edge_type)
YIELD edge_type, pairs_removed
RETURN edge_type, pairs_removed
graph.saveSnapshot
Triggers an immediate full persistence snapshot to the configured snapshot directory. Call this after schema changes to ensure they survive a restart without waiting for the next scheduled snapshot.
CALL graph.saveSnapshot()
YIELD ok, path, elapsed_ms
RETURN ok, path, elapsed_ms
db.* β Schema & Administration
db.schema
Returns the full schema: node types, edge types, and registered properties.
CALL db.schema()
YIELD nodeTypes, edgeTypes, properties
RETURN nodeTypes, edgeTypes, properties
| YIELD column | Type | Description |
|---|---|---|
nodeTypes |
List<Map> | {id, name, ext_id_kind} per node type |
edgeTypes |
List<Map> | {id, name, from, to, symmetric, state_ttl_secs, minimal_payload, tick_size_secs, β¦}
per edge type |
properties |
List<Map> | {id, name, owner, is_node, value_type} per
property |
db.nodeStats
Returns the live node count per type. Prefer this over
MATCH (n) RETURN count(n).
CALL db.nodeStats()
YIELD type, count
RETURN type, count
ORDER BY count DESC
Filter to a specific type:
CALL db.nodeStats()
YIELD type, count
WHERE type = 'CARD'
RETURN count
db.memoryUsage
Returns a detailed memory breakdown of the engine process.
CALL db.memoryUsage()
YIELD total_bytes, payload_bytes, edge_pair_count, process_rss_bytes,
jemalloc_allocated_bytes, jemalloc_resident_bytes, breakdown
RETURN total_bytes, payload_bytes, process_rss_bytes
| YIELD column | Type | Description |
|---|---|---|
total_bytes |
Integer | Full in-process memory model (payload + overhead) |
payload_bytes |
Integer | Pure graph data (lower bound) |
edge_pair_count |
Integer | Total stored directed edge pairs |
process_rss_bytes |
Integer | OS-reported resident set size |
jemalloc_allocated_bytes |
Integer / Null | Live heap if jemalloc is active |
jemalloc_resident_bytes |
Integer / Null | Jemalloc resident pages |
jemalloc_is_global_allocator |
Bool | Whether jemalloc is the allocator |
jemalloc_fragmentation_bytes |
Integer | resident β allocated |
breakdown |
Map | Per-component byte estimates |
note |
String | Explanatory annotation |
db.registerNodeType
Registers a new node type in the schema (schema setup, run once).
CALL db.registerNodeType('CARD', 'string')
YIELD node_type_id
RETURN node_type_id
| Parameter | Type | Description |
|---|---|---|
name |
String | Node type name (will be stored in uppercase) |
ext_id_kind |
String | 'string' or 'numeric' (default
'numeric') |
db.registerEdgeType
Registers a new edge type. Accepts either a spec map or positional arguments.
Map form (recommended):
CALL db.registerEdgeType({
name: 'TRANSACTS_AT',
from_node_type: 'CARD',
to_node_type: 'MERCHANT',
state_ttl_secs: 7776000,
symmetric: false,
minimal_payload: false,
bin_boundaries: [10.0, 50.0, 100.0, 250.0, 500.0, 1000.0],
tracked_property: 'amount',
node_histogram: {
enabled_for_src: true,
enabled_for_dst: false,
hourly_slots: 48,
daily_slots: 30
},
activity_bitmap: {tick_size_secs: 3600},
bool_property: 'is_chargeback'
})
YIELD edge_type_id
Positional form:
CALL db.registerEdgeType('TRANSACTS_AT', 'CARD', 'MERCHANT', 7776000, false, false)
YIELD edge_type_id
| Field | Type | Default | Description |
|---|---|---|---|
name |
String | β | Edge type name (required) |
from_node_type |
String | β | Source node type (required) |
to_node_type |
String | β | Destination node type (required) |
state_ttl_secs |
Integer | 0 |
TTL in seconds (0 = never expire) |
symmetric |
Bool | false |
Store as undirected (both directions share one record) |
minimal_payload |
Bool | false |
Store only existence flag, no counters |
bin_boundaries |
List<Float> | β | Up to 7 amount histogram bucket boundaries |
tracked_property |
String | 'amount' |
Property name for histogram tracking |
node_histogram |
Map | β | Time-series histogram configuration |
activity_bitmap |
Map | β | Activity bitmap tick size (tick_size_secs, default
3600) |
bool_property |
String | β | Name of the edgeβs boolean property |
db.registerProperty
Registers a node or edge property in the schema.
CALL db.registerProperty('risk_score', 'CARD', true, 'float')
YIELD property_id
| Parameter | Type | Description |
|---|---|---|
name |
String | Property name |
owner |
String | Owner node type or edge type name |
is_node |
Bool | true for node property, false for edge
property |
value_type |
String | 'string', 'int', 'float',
'bool', 'timestamp' |
db.finalizeSchema
Locks the schema so it can no longer be modified. Call this after all types and properties have been registered.
CALL db.finalizeSchema()
YIELD schema_version
RETURN schema_version
db.resetGraph
Wipes all graph data. Requires enable_admin_reset = true
in the engine config or the ENABLE_ADMIN_RESET=true
environment variable.
CALL db.resetGraph()
YIELD ok, message
RETURN ok, message
15. Aggregation Functions
Aggregation functions collapse multiple rows into a single value.
They are typically used in RETURN or WITH
clauses. Any non-aggregate expressions in the same clause become
implicit grouping keys.
| Function | Description |
|---|---|
count(*) |
Count all rows |
count(expr) |
Count rows where expr is not null |
count(DISTINCT expr) |
Parsed and accepted; see note below |
sum(expr) |
Sum of numeric values |
avg(expr) |
Average of numeric values |
min(expr) |
Minimum value |
max(expr) |
Maximum value |
collect(expr) |
Collect all values into a list |
Examples
// Count all cards
MATCH (c:CARD)
RETURN count(c) AS total_cards
// Merchants per card, filter to active ones
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WITH c, count(m) AS merchant_count
WHERE merchant_count > 2
RETURN c.external_id, merchant_count
ORDER BY merchant_count DESC
// Top spend amounts
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id, sum(r.approx_sum) AS total_spend
ORDER BY total_spend DESC
LIMIT 10
// Collect all merchant IDs for a card
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN collect(m.external_id) AS merchants
Note on
count(DISTINCT expr): The syntax is accepted by the parser but the executor does not deduplicate the values β it behaves identically tocount(expr). To count distinct values, useRETURN DISTINCTwith aWITHgrouping step before aggregating, or filter withRETURN DISTINCTfirst.
16. Scalar Functions
Graph topology functions
| Function | Arguments | Returns | Description |
|---|---|---|---|
id(node) |
Node variable | Integer | Internal numeric node ID |
labels(node) |
Node variable | List<String> | Type labels of the node |
type(rel) |
Relationship variable | String | Relationship type name |
degree(node, edgeType, direction) |
Node, String, String | Integer | Number of edges; direction = 'out'
(default), 'in', 'both' |
Path functions
| Function | Arguments | Returns | Description |
|---|---|---|---|
length(path) |
Path variable | Integer | Number of hops in the path |
size(list) |
List | Integer | Number of elements |
Type conversion
| Function | Description |
|---|---|
toString(expr) |
Convert to string |
toInteger(expr) / toInt(expr) |
Convert to integer (truncates floats) |
toFloat(expr) |
Convert to float |
Math
| Function | Description |
|---|---|
abs(n) |
Absolute value |
round(n) / round(n, precision) |
Round to nearest integer or decimal places |
ceil(n) / ceiling(n) |
Ceiling (round up) |
floor(n) |
Floor (round down) |
sqrt(n) |
Square root |
Examples
// Node degree
MATCH (c:CARD {external_id: $card_id})
RETURN degree(c, 'TRANSACTS_AT', 'out') AS merchant_count
// Path length
MATCH p = (c:CARD)-[:TRANSACTS_AT*1..3]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN length(p) AS hops, m.external_id
// Type conversions
RETURN toFloat('3.14'), toInteger(9.9), toString(42)
// Math
RETURN abs(-5), round(3.7), ceil(2.1), floor(2.9), sqrt(16.0)
17. Operators
Comparison
| Operator | Meaning |
|---|---|
= |
Equal |
<> or != |
Not equal |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
Logical
| Operator | Meaning |
|---|---|
AND |
Both conditions must be true |
OR |
At least one condition must be true |
NOT |
Negation |
String predicates
| Operator | Meaning |
|---|---|
CONTAINS |
Substring match |
STARTS WITH |
Prefix match |
ENDS WITH |
Suffix match |
Membership
WHERE c.status IN ['active', 'flagged']
WHERE m.category IN $categories
Null checks
WHERE c.risk_score IS NULL
WHERE c.risk_score IS NOT NULL
Arithmetic
| Operator | Meaning |
|---|---|
+ |
Addition (also string concatenation) |
- |
Subtraction (also unary negation) |
* |
Multiplication |
/ |
Division |
% |
Modulo |
List subscript
RETURN $features[0]
RETURN collect(m.external_id)[-1] // last element (negative indices supported)
18. Node & Relationship Properties
Accessing node properties
MATCH (c:CARD {external_id: $card_id})
RETURN c.external_id, c.name, c.risk_score
The external_id, id, and
node_id fields are always available without schema
registration. All other properties must be registered with
db.registerProperty.
Accessing relationship properties
Compact edge properties are read through a relationship variable:
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id, r.tx_count, r.approx_sum, r.last_seen, r.bool_flag
| Edge property | Type | Description |
|---|---|---|
tx_count |
Integer | Number of recorded transactions |
approx_sum / amount |
Float | Approximate cumulative transaction amount |
last_seen |
Integer | Unix timestamp of the most recent event (seconds) |
bool_flag |
Bool | Boolean flag from the compact payload |
bins |
List<Integer> | Amount histogram bucket counts |
activity_bitmap_lo |
Integer | Lower 32 bits of the raw activity bitmap |
activity_bitmap_hi |
Integer | Upper 32 bits of the raw activity bitmap |
19. EXISTS Subqueries
EXISTS { MATCH β¦ } tests whether a correlated pattern
exists in the graph for the current outer row. It returns
true or false and is evaluated once per outer
row.
Check that a relationship exists
MATCH (c:CARD)
WHERE EXISTS { MATCH (c)-[:TRANSACTS_AT]->(:MERCHANT) }
RETURN c.external_id
Check that a relationship does NOT exist
MATCH (c:CARD)
WHERE NOT EXISTS { MATCH (c)-[:TRANSACTS_AT]->(:MERCHANT) }
RETURN c.external_id AS dormant_card
Correlated subquery with properties
MATCH (c:CARD)
WHERE EXISTS {
MATCH (c)-[:TRANSACTS_AT]->(m:MERCHANT)
WHERE m.category = 'HIGH_RISK'
}
RETURN c.external_id
20. Variable-Length Paths
Variable-length patterns traverse multiple hops in a single clause.
Syntax
(a)-[:TYPE*min..max]->(b)
(a)-[:TYPE*min..max]-(b) // undirected
| Element | Description |
|---|---|
min |
Minimum number of hops (positive integer) |
max |
Maximum number of hops (positive integer, β€ max_hops
config) |
Examples
// Exactly 2 hops
MATCH (c:CARD)-[:TRANSACTS_AT*2..2]->(x)
WHERE c.external_id = $card_id
RETURN DISTINCT x.external_id
// 1 to 3 hops
MATCH (c:CARD)-[:TRANSACTS_AT*1..3]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN DISTINCT m.external_id
LIMIT 100
// Undirected, up to 4 hops
MATCH (c:CARD)-[:CONNECTED*1..4]-(neighbor)
WHERE c.external_id = $card_id
RETURN DISTINCT labels(neighbor) AS type, neighbor.external_id AS id
LIMIT 50
Inline WHERE on traversed relationships
Filter relationships during traversal (not after):
MATCH (c:CARD)-[r:TRANSACTS_AT*1..3 WHERE r.tx_count > 1]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN DISTINCT m.external_id
Relationship variable in variable-length patterns
When using [r:TYPE*min..max], r holds a
list of relationships (one per hop in the path). Access
individual properties with list subscript:
MATCH (c:CARD)-[rels:TRANSACTS_AT*1..2]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id, size(rels) AS hops
Default hop limit: The maximum allowed value of
maxis controlled by themax_hopsconfiguration setting (default: 6).
21. Engine Limits
These limits protect the engine from runaway queries. They are
configurable in config.toml.
| Setting | Default | Effect |
|---|---|---|
max_hops |
6 |
Maximum hop count for variable-length patterns |
max_expand_nodes |
100,000 |
BFS circuit breaker β halts traversal after this many nodes |
max_result_rows |
1,000,000 |
Maximum rows emitted from a scan or expand node |
max_sort_rows |
1,000,000 |
Maximum rows processed by an ORDER BY without
LIMIT |
max_distinct_cardinality |
1,000,000 |
Budget for distinct-aggregation tracking |
Queries that exceed these limits return a partial result or an error,
not a crash. Always add LIMIT to exploratory queries to
avoid hitting these boundaries.
22. Performance Guide
Label every node
Without a label the engine performs a full scan across every node for every registered edge type.
// BAD β full graph scan
MATCH (n)-[r]->(m) WHERE n.external_id = $id RETURN n, r, m
// GOOD β indexed lookup
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT) WHERE c.external_id = $id RETURN c, r, m
Type every relationship
An untyped [r] causes the engine to fan out over every
edge type in the schema, executing one expand per type.
// BAD β N expands (one per edge type)
MATCH (c:CARD)-[r]->(n) WHERE c.external_id = $id RETURN type(r), n.external_id
// GOOD β single targeted expand
MATCH (c:CARD)-[r:TRANSACTS_AT]->(m:MERCHANT) WHERE c.external_id = $id RETURN r, m
Always use parameters
Literal values in the query string create a new cache entry per unique value. Parameters produce a single cached plan reused for all invocations.
// BAD β unique cache entry per card number
MATCH (c:CARD {external_id: '9792487647826207'})-[:TRANSACTS_AT]->(m:MERCHANT)
// GOOD β one cached plan for all cards
MATCH (c:CARD {external_id: $card_id})-[:TRANSACTS_AT]->(m:MERCHANT)
Filter on
external_id of the source node
The optimizer pushes WHERE node.external_id = $value
into the node scan, converting it to an O(1) index lookup. This only
fires for the source node of a traversal. Filters on
destination nodes or other properties remain as post-expansion
filters.
// GOOD β pushdown converts CARD scan to IndexLookup
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id AND m.name CONTAINS 'Airline'
RETURN m.external_id
// If you need to start from the destination, flip the traversal
MATCH (m:MERCHANT)<-[:TRANSACTS_AT]-(c:CARD)
WHERE m.external_id = $merchant_id
RETURN c.external_id
Always add LIMIT to exploratory queries
LIMIT is pushed down into node scans and BFS β traversal
stops as soon as the limit is reached rather than materialising the full
result set.
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN m.external_id LIMIT 50
Use
db.nodeStats() instead of count(n)
// BAD β materialises all 1M nodes
MATCH (n:CARD) RETURN count(n)
// GOOD β O(1) counter lookup
CALL db.nodeStats() YIELD type, count WHERE type = 'CARD' RETURN count
Use
graph.ingest() for batch writes
// GOOD β single round-trip, no per-statement planning
CALL graph.ingest($nodes, $edges)
YIELD ok, nodes_created, edges_created
Use
graph.upsertEdge() for transaction recording
// GOOD β O(1) atomic counter update
CALL graph.upsertEdge($edge_type, $src, $dst, $amount, $ts)
YIELD created_new, tx_count, approx_sum
Multi-hop fraud context β run as separate queries
UNION is not supported in a single request. For
retrieving multiple edge types run one query per type:
// Query 1 β merchants
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WHERE c.external_id = $card_id
RETURN 'merchant' AS kind, m.external_id AS id
// Query 2 β devices
MATCH (c:CARD)-[:USES_DEVICE]->(d:DEVICE)
WHERE c.external_id = $card_id
RETURN 'device' AS kind, d.external_id AS id
// Query 3 β IPs
MATCH (c:CARD)-[:USES_IP]->(ip:IP)
WHERE c.external_id = $card_id
RETURN 'ip' AS kind, ip.external_id AS id
23. Unsupported Neo4j Features
The following features are present in standard openCypher or Neo4j but are not supported in Jetgraph:
| Feature | Status |
|---|---|
UNION / UNION ALL |
Not in the parser. Run as separate HTTP requests. |
FOREACH |
Not supported |
REMOVE |
Not supported |
LOAD CSV |
Not supported |
CALL { ... } subqueries |
Not supported |
Multi-label nodes (n:A:B) |
Not supported β each node has exactly one type |
Multi-type relationships [:A\|B] |
Grammar accepts the pipe, but only the first type is used |
Untyped relationships [r] as multi-type fan-out |
Returns empty β use separate typed queries |
count(DISTINCT expr) deduplication |
Syntax accepted; deduplication is not applied |
XOR operator |
Parsed in the AST but not in the grammar |
| Semicolon-separated batches in one request | Not supported β one statement per request |
24. Query Cookbook
Schema setup
// 1. Register node types
CALL db.registerNodeType('CARD', 'string') YIELD node_type_id
CALL db.registerNodeType('MERCHANT', 'string') YIELD node_type_id
CALL db.registerNodeType('DEVICE', 'string') YIELD node_type_id
CALL db.registerNodeType('IP', 'string') YIELD node_type_id
// 2. Register edge types
CALL db.registerEdgeType({
name: 'TRANSACTS_AT', from_node_type: 'CARD', to_node_type: 'MERCHANT',
state_ttl_secs: 7776000,
bin_boundaries: [10.0, 50.0, 100.0, 250.0, 500.0, 1000.0],
node_histogram: {enabled_for_src: true, enabled_for_dst: false,
hourly_slots: 48, daily_slots: 30}
}) YIELD edge_type_id
CALL db.registerEdgeType({
name: 'USES_DEVICE', from_node_type: 'CARD', to_node_type: 'DEVICE',
state_ttl_secs: 0, minimal_payload: true
}) YIELD edge_type_id
CALL db.registerEdgeType({
name: 'USES_IP', from_node_type: 'CARD', to_node_type: 'IP',
state_ttl_secs: 0
}) YIELD edge_type_id
// 3. Register properties
CALL db.registerProperty('risk_score', 'CARD', true, 'float') YIELD property_id
CALL db.registerProperty('name', 'MERCHANT', true, 'string') YIELD property_id
CALL db.registerProperty('category', 'MERCHANT', true, 'string') YIELD property_id
// 4. Lock schema
CALL db.finalizeSchema() YIELD schema_version RETURN schema_version
// 5. Persist immediately
CALL graph.saveSnapshot() YIELD ok, elapsed_ms RETURN ok, elapsed_ms
CRUD β basic operations
// Create a card
CREATE (c:CARD {external_id: $card_id})
// Create a card and merchant together
CREATE (c:CARD {external_id: $card_id})-[:TRANSACTS_AT]->(m:MERCHANT {external_id: $merchant_id})
// Find a card by ID
MATCH (c:CARD {external_id: $card_id}) RETURN c
// Update a property
MATCH (c:CARD {external_id: $card_id})
SET c.risk_score = $score
// Upsert a card
MERGE (c:CARD {external_id: $card_id})
ON CREATE SET c.risk_score = 0.0
ON MATCH SET c.last_seen = $now
RETURN c
// Delete a node
MATCH (c:CARD {external_id: $card_id}) DETACH DELETE c
Ingest β bulk load
CALL graph.ingest(
[
{node_type: 'CARD', externalId: 'card-001'},
{node_type: 'MERCHANT', externalId: 'merchant-A'},
{node_type: 'DEVICE', externalId: 'device-X'}
],
[
{edge_type: 'TRANSACTS_AT', src: 'card-001', dst: 'merchant-A',
numeric_value: 49.99, event_ts_secs: 1718000000},
{edge_type: 'USES_DEVICE', src: 'card-001', dst: 'device-X'}
]
) YIELD ok, nodes_created, nodes_existing, edges_created, edges_updated
RETURN ok, nodes_created, edges_created, edges_updated
Recording a transaction event
CALL graph.upsertEdge(
$edge_type,
$src_typed_id,
$dst_typed_id,
$amount,
$event_ts_secs
) YIELD created_new, tx_count, approx_sum
RETURN created_new, tx_count, approx_sum
{
"edge_type": "TRANSACTS_AT",
"src_typed_id": "CARD:9792487647826207",
"dst_typed_id": "MERCHANT:M2_0000803",
"amount": 149.00,
"event_ts_secs": 1718000000
}Traversal β outbound neighbors
MATCH (c:CARD {external_id: $card_id})-[r:TRANSACTS_AT]->(m:MERCHANT)
RETURN m.external_id AS merchant, r.tx_count AS txs, r.approx_sum AS spend
ORDER BY spend DESC
LIMIT 20
Traversal β multi-hop BFS
MATCH (c:CARD {external_id: $card_id})-[:TRANSACTS_AT*1..3]->(m:MERCHANT)
RETURN DISTINCT m.external_id AS merchant
LIMIT 100
Ring / shared entity β cards sharing a device
MATCH (c1:CARD)-[:USES_DEVICE]->(d:DEVICE)<-[:USES_DEVICE]-(c2:CARD)
WHERE c1.external_id = $card_id
AND c2.external_id <> $card_id
RETURN DISTINCT c2.external_id AS linked_card, d.external_id AS shared_device
LIMIT 50
Ring / shared entity β cards sharing a merchant
MATCH (c1:CARD {external_id: $card_id})-[:TRANSACTS_AT]->(m:MERCHANT)<-[:TRANSACTS_AT]-(c2:CARD)
WHERE c2.external_id <> $card_id
RETURN DISTINCT c2.external_id AS sibling_card, m.external_id AS shared_merchant
LIMIT 25
Aggregation β merchant count per card
MATCH (c:CARD)-[:TRANSACTS_AT]->(m:MERCHANT)
WITH c, count(m) AS merchant_count
WHERE merchant_count > 1
RETURN c.external_id AS card, merchant_count
ORDER BY merchant_count DESC
LIMIT 50
Aggregation β top spend merchants for a card
MATCH (c:CARD {external_id: $card_id})-[r:TRANSACTS_AT]->(m:MERCHANT)
RETURN m.external_id AS merchant, r.tx_count AS transactions, r.approx_sum AS spend
ORDER BY spend DESC
LIMIT 10
EXISTS β cards that have used a high-risk merchant
MATCH (c:CARD)
WHERE EXISTS {
MATCH (c)-[:TRANSACTS_AT]->(m:MERCHANT)
WHERE m.category = 'HIGH_RISK'
}
RETURN c.external_id
LIMIT 100
Fraud context β full picture for a card
// Edge state
CALL graph.edgeState(
'CARD:' + $card_id,
'MERCHANT:' + $merchant_id,
'TRANSACTS_AT'
) YIELD tx_count, approx_sum, last_seen, bool_flag, activity_counts
RETURN tx_count, approx_sum, last_seen, bool_flag, activity_counts
// Fraud case membership
CALL graph.fraudContext('CARD:' + $card_id)
YIELD flagged, case_count, max_risk_score, cases
RETURN flagged, case_count, max_risk_score
// Transaction velocity histogram
CALL graph.histogram('CARD:' + $card_id, 'TRANSACTS_AT', 24, null)
YIELD buckets, counts
RETURN buckets, counts
Fraud case management
// Create a new fraud case
CALL graph.createFraudCase(
$case_id,
['CARD:card-001', 'CARD:card-002', 'MERCHANT:merchant-X'],
0.92,
'velocity_anomaly'
) YIELD ok, case_node_id, case_id
RETURN ok, case_node_id
// Add more nodes later
CALL graph.addFraudCaseNodes($case_id, ['CARD:card-003'], 0.85) YIELD ok
// Remove a node from a case
CALL graph.removeFraudCaseNode($case_id, 'CARD:card-001') YIELD ok
Similarity search
// Find similar cards by shared merchants
CALL graph.findSimilar('CARD:' + $card_id, 'TRANSACTS_AT', 10)
YIELD similar, score
WHERE score > 0.25
RETURN similar, score
ORDER BY score DESC
// Build a full SIMILAR_TO graph for all cards
CALL graph.buildSimilarityGraph({
node_type: 'CARD',
edge_types: ['TRANSACTS_AT', 'USES_DEVICE'],
similar_to_edge_type: 'SIMILAR_TO',
k: 15,
min_similarity: 0.2
}) YIELD edges_created, edges_updated, nodes_processed, elapsed_ms
RETURN edges_created, nodes_processed, elapsed_ms
Admin β statistics and health
// Node counts per type
CALL db.nodeStats() YIELD type, count RETURN type, count ORDER BY count DESC
// Full schema
CALL db.schema() YIELD nodeTypes, edgeTypes, properties RETURN nodeTypes, edgeTypes
// Memory breakdown
CALL db.memoryUsage()
YIELD total_bytes, payload_bytes, process_rss_bytes, edge_pair_count
RETURN
total_bytes / 1024 / 1024 AS total_mb,
payload_bytes / 1024 / 1024 AS payload_mb,
process_rss_bytes / 1024 / 1024 AS rss_mb,
edge_pair_count
// Sample nodes
MATCH (n:CARD) RETURN n.external_id LIMIT 25
// Pattern mining β most common next merchants
MATCH (m1:MERCHANT)-[r:NEXT_MERCHANT]->(m2:MERCHANT)
RETURN m1.external_id AS from_merchant, m2.external_id AS to_merchant, r.tx_count AS count
ORDER BY count DESC
LIMIT 10