Skip to content

Query Execution Plans

Use the EXPLAIN command to understand how Quine executes your Cypher queries. This helps identify performance issues and optimize query patterns.

When to Use EXPLAIN

  • Ad-hoc queries: Before running expensive queries on production data
  • Ingest queries: When optimizing data loading performance
  • Standing queries: To understand pattern matching behavior (though standing queries compile differently)

Using EXPLAIN

Prefix any Cypher query with EXPLAIN to see the execution plan without running the query:

EXPLAIN MATCH (n:Person)-[:KNOWS]->(m:Person)
WHERE n.age > 30
RETURN m.name

This returns a query plan showing how Quine will execute the query. The query is compiled but not executed, making it safe to use on production systems.

Query Plan Structure

The execution plan is returned as a JSON tree structure:

{
  "operatorType": "Filter",
  "args": {
    "condition": "n.age > 30"
  },
  "identifiers": ["n", "m"],
  "children": [
    {
      "operatorType": "Expand",
      "args": {
        "edgeName": "KNOWS",
        "direction": "Outgoing"
      },
      "identifiers": ["n", "m"],
      "children": [...]
    }
  ],
  "isReadOnly": true,
  "isIdempotent": true,
  "canContainAllNodeScan": true
}

Plans form a tree where each operator processes results from its children. Read the plan from the innermost children outward to understand execution order.

Root-Level Flags

The root of the query plan includes metadata flags:

Flag Description
isReadOnly true if the query performs no writes to the graph
isIdempotent true if running the query multiple times produces the same result
canContainAllNodeScan true if the query may scan all nodes in the graph (performance warning)

Performance Warning

If canContainAllNodeScan is true, your query may be slow on large graphs. Consider anchoring your query by node ID using WHERE id(n) = idFrom(...). See Using IDs in a Query for details.

Query Plan Operators

The following operators appear as operatorType in query plans:

Data Scanning & Entry Points

Operator Description
AnchoredEntry Starts from a specific node ID or index lookup
ArgumentEntry Starts from an externally provided node argument

Graph Traversal

Operator Description
Expand Follows edges with optional length bounds and direction constraints
GetDegree Returns the count of edges matching specified constraints
LocalNode Checks node labels and properties, optionally binding to a variable

Data Flow & Combination

Operator Description
Apply Executes one query then another sequentially (flatMap)
Union Executes two queries and concatenates their results
Or Executes the first query; uses second only if first returns nothing
ValueHashJoin Joins two queries on matching property values
SemiApply Filters results based on whether a sub-query succeeds
Cross Calculates cross product of multiple query plan results

Filtering & Transformation

Operator Description
Filter Filters rows by a condition
FilterMap Projects and optionally filters data from nested query results
Optional Emits results, or input row if no results
AdjustContext Adds, removes, or renames columns
Unwind Expands a list into multiple rows

Aggregation & Ordering

Operator Description
EagerAggregation Groups and aggregates results
Return Applies ORDER BY, DISTINCT, SKIP, LIMIT in one operator
Skip Drops the first N results
Limit Keeps only the first N results
Sort Sorts results by expression(s)
Distinct Removes duplicate rows

Data Modification

Operator Description
SetProperty Sets a single node property
SetProperties Batch updates node properties
SetLabels Adds or removes labels from a node
SetEdge Creates or deletes an edge
Delete Removes a node, relationship, or path

Special Operations

Operator Description
ProcedureCall Calls a user-defined procedure
SubQuery Executes a sub-query and stitches results
LocalProperty Generates a row containing a property value from a node
LoadCSV Loads and iterates over CSV data
Empty Returns no results
Unit Returns input unchanged

Reading an Execution Plan

Here's how to interpret a query plan for the query:

EXPLAIN MATCH (n:Person)-[:KNOWS]->(m) WHERE n.age > 30 RETURN m.name
  1. Start at the innermost operator - This is typically AnchoredEntry or a scan operation
  2. Follow the tree outward - Each parent operator processes its children's output
  3. Look for performance issues:
    • AnchoredEntry with AllNodesScan indicates a full graph scan
    • Multiple Cross operators can indicate combinatorial explosion
    • Filter operators late in the plan may process many unnecessary rows

Query Type Considerations

Ad-hoc Queries

For interactive queries, use EXPLAIN before running expensive operations:

// Check the plan first
EXPLAIN MATCH (n:Person)-[:KNOWS*1..5]->(m:Person)
WHERE n.name = "Alice"
RETURN m

// If plan looks reasonable, run the actual query
MATCH (n:Person)-[:KNOWS*1..5]->(m:Person)
WHERE n.name = "Alice"
RETURN m

Ingest Queries

For ingest queries that run repeatedly, optimize the plan once during development. Run the query as an ad-hoc query first to test behavior and performance before deploying it as part of your ingest pipeline:

// Test as ad-hoc query with sample data
MATCH (n) WHERE id(n) = idFrom("user", "test-user-123")
SET n.lastSeen = datetime()
RETURN n

// Then verify the plan anchors properly
EXPLAIN MATCH (n) WHERE id(n) = idFrom("user", $that.userId)
SET n.lastSeen = $that.timestamp

Look for AnchoredEntry rather than scans, since ingest queries execute for every record.

Standing Queries

Standing queries compile into a different internal representation optimized for incremental matching. While EXPLAIN shows the logical plan, the actual execution differs.

Before registering a standing query, run the pattern as an ad-hoc query to verify it matches expected data:

// Test the pattern as an ad-hoc query first
MATCH (order:Order)-[:PLACED_BY]->(customer:Customer)
WHERE order.total > 1000
RETURN order, customer
LIMIT 10

// Once satisfied, register as a standing query with the pattern

For debugging registered standing queries, use debugging procedures to inspect runtime state.