Language Reference

Cypher Manual

πŸ“–
This is the authoritative reference for every Cypher feature JetGraph supports. For a guided introduction with fraud-detection examples, see Querying the Graph in the main documentation.

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 of MERGE. 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 to count(expr). To count distinct values, use RETURN DISTINCT with a WITH grouping step before aggregating, or filter with RETURN DISTINCT first.


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 max is controlled by the max_hops configuration 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

// 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