xrayGraphDB  ›  Documentation

xrayGraphDB Documentation

Install the engine, connect a client, learn Cypher and GFQL, then explore the complete interactive reference of 395 functions and 125 procedures. Version 1, generated 2026-06-27.

Installation: Docker

Docker is the fastest way to start xrayGraphDB. The image ships with sensible defaults — storage path, ports, and encryption at rest. Pull it, bootstrap an admin on the first run, and you're up.

Before you run anything: raise the host's memory-map limit so the database can start: sudo sysctl -w vm.max_map_count=262144 (persist it in /etc/sysctl.d/).

First run — bootstrap an admin

The daemon refuses connections without an admin user. Pass --init-admin-user and --init-admin-password on the first start to create one. These flags are bootstrap-only — they only create a user when no users exist, and become silent no-ops on every subsequent boot once the admin record is on disk.

Shell — first run only
# Pull (or `docker load < xraygraphdb-4.9.4-docker.tar.gz` for the offline tarball)
docker pull xraygraphdb.emtailabs.com/xraygraphdb:v4.9.4

# First run — creates admin/<your-password> on initial boot
docker run -d \
  --name xraygraphdb \
  --restart unless-stopped \
  -p 7689:7689 \
  -v xraygraphdb-data:/var/lib/xraygraphdb \
  -v xraygraphdb-logs:/var/log/xraygraphdb \
  xraygraphdb.emtailabs.com/xraygraphdb:v4.9.4 \
  --license-acknowledge-saved=true \
  --init-admin-user=admin \
  --init-admin-password=YourStrongPassword!23

# Wait ~5s, then confirm the daemon is listening
docker logs xraygraphdb | grep "xrayProtocol listening"
# Expected: "xrayProtocol listening on 0.0.0.0:7689 with 4 workers"

Security: rotate the bootstrap flags off after the first boot. Once the daemon confirms it's listening, the admin record is persisted in /var/lib/xraygraphdb/auth/ and the --init-admin-* flags do nothing on restart. But they remain visible in three places — docker inspect xraygraphdb (full Args[]), /var/lib/docker/containers/<id>/config.v2.json on the host, and ps aux inside the container. Recreate the container without the bootstrap flags so the password isn't sitting in process args:

Shell — every run after the first
docker stop xraygraphdb && docker rm xraygraphdb
docker run -d \
  --name xraygraphdb \
  --restart unless-stopped \
  -p 7689:7689 \
  -v xraygraphdb-data:/var/lib/xraygraphdb \
  -v xraygraphdb-logs:/var/log/xraygraphdb \
  xraygraphdb.emtailabs.com/xraygraphdb:v4.9.4 \
  --license-acknowledge-saved=true

The named volumes xraygraphdb-data and xraygraphdb-logs persist the database state and logs across container recreations. The data path inside the image is /var/lib/xraygraphdb (matches the daemon's default --data-directory).

Enterprise license (optional)

If you have a license file, mount it read-only at /etc/xraygraphdb/license.xglicense. The daemon auto-detects and validates it on startup:

Shell
docker run -d \
  --name xraygraphdb \
  -p 7689:7689 \
  -v xraygraphdb-data:/var/lib/xraygraphdb \
  -v /path/to/license.xglicense:/etc/xraygraphdb/license.xglicense:ro \
  xraygraphdb.emtailabs.com/xraygraphdb:v4.9.4 \
  --license-acknowledge-saved=true

docker logs xraygraphdb | grep "License loaded"
# Expected: "License loaded: xg-ent-... tier=enterprise org=..."

Without a license, the community-tier graph algorithms (PageRank, BFS, triangle count, betweenness, community detection, and more) run unrestricted. An enterprise license unlocks the additional commercial procedure namespaces.

Docker Compose

YAML — docker-compose.yml
services:
  xraygraphdb:
    image: xraygraphdb.emtailabs.com/xraygraphdb:v4.9.4
    restart: unless-stopped
    ports:
      - "7689:7689"
    volumes:
      - xraygraphdb-data:/var/lib/xraygraphdb
      - xraygraphdb-logs:/var/log/xraygraphdb
      # Optional: Enterprise license
      # - ./license.xglicense:/etc/xraygraphdb/license.xglicense:ro
    command:
      - --license-acknowledge-saved=true
      # --- FIRST RUN ONLY: uncomment for the first `docker compose up`,
      # --- then comment back out and `docker compose up -d --force-recreate`.
      # - --init-admin-user=admin
      # - --init-admin-password=YourStrongPassword!23

volumes:
  xraygraphdb-data:
  xraygraphdb-logs:
Shell
docker compose up -d
docker compose logs -f xraygraphdb | grep "xrayProtocol listening"

Verify the Server

Shell
# Container status
docker ps --filter name=xraygraphdb

# Daemon ready check — xrayProtocol on 7689 (Bolt is OFF by default; opt-in via --bolt-server-name)
docker logs xraygraphdb | grep "xrayProtocol listening"
# Expected: "xrayProtocol listening on 0.0.0.0:7689 with 4 workers"

# Test connection (Python xray_protocol_client; HELLO must carry a database name)
python3 -c 'import xray_protocol_client as xg
conn = xg.connect(host="localhost", port=7689,
                  auth_token="admin:YourStrongPassword!23",
                  database="xraygraphdb")
print(conn.execute_query("MATCH (n) RETURN count(n) AS n_count"))'

Installation: Linux

Two install paths on Ubuntu 24.04 LTS: the .deb package (recommended — auto-resolves runtime deps via apt) or the portable .tar.gz (everything bundled, runs against the system glibc).

.deb — Ubuntu / Debian

Shell
# 1. Download
wget https://xraygraphdb.emtailabs.com/downloads/xraygraphdb_4.9.4_amd64.deb

# 2. Install — use `apt install -f` so apt auto-resolves the libgdal34t64 + python3 deps.
#    `dpkg -i` directly will FAIL the first time with "depends on libgdal34t64; however ...".
sudo apt install -f ./xraygraphdb_4.9.4_amd64.deb

.tar.gz — portable build (any glibc ≥ 2.39)

The tarball ships every C/C++ runtime we need (libstdc++, libLLVM, libsolclient, libssl, libcrypto, libxraygraphdb_module_support) under lib/. The install.sh wrapper drops them at /usr/lib/xraygraphdb/lib, registers the path with ldconfig, installs the systemd unit, and apt-installs the one external dep (libgdal34t64).

Shell
wget https://xraygraphdb.emtailabs.com/downloads/xraygraphdb-4.9.4-linux-x86_64.tar.gz
tar xzf xraygraphdb-4.9.4-linux-x86_64.tar.gz
cd xraygraphdb-4.9.4-linux-x86_64
sudo ./install.sh

Configure — required systemd drop-in

The default /lib/systemd/system/xraygraphdb.service ships an empty ExecStart sentinel so you can layer site-local flags via a drop-in without forking the unit file. Create the drop-in before the first systemctl start:

Shell
sudo mkdir -p /etc/systemd/system/xraygraphdb.service.d
sudo tee /etc/systemd/system/xraygraphdb.service.d/local.conf >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/lib/xraygraphdb/xraygraphdb \
    --data-directory=/var/lib/xraygraphdb \
    --bolt-port=7687 \
    --xray-port=7689 \
    --storage-properties-on-edges=true \
    --log-level=WARNING \
    --license-acknowledge-saved=true \
    --bolt_listen_mode=off
EOF

sudo systemctl daemon-reload
sudo systemctl start xraygraphdb
sudo systemctl status xraygraphdb
ss -tlnp | grep :7689     # xrayProtocol
Multi-tenant encryption at rest.

Enterprise multi-tenant deployments can encrypt each tenant's data at rest using your own key management system (HashiCorp Vault, AWS KMS, or a compatible KMS). Setup is covered in the licensed admin guide. Single-tenant and evaluation installs work out of the box with no additional configuration.

Critical: Always stop xrayGraphDB with SIGTERM (graceful shutdown). Never use kill -9. A forced kill skips the final snapshot and may result in data loss on next recovery.

Installation: macOS

macOS is supported for development only. For production workloads use Linux or Docker.

Shell
# Download macOS binary (Apple Silicon or Intel)
curl -LO https://releases.emtailabs.com/xraygraphdb/xraygraphdb-v4.9.4-macos-arm64.tar.gz

# Extract and run
tar xzf xraygraphdb-v4.9.4-macos-arm64.tar.gz
cd xraygraphdb-v4.9.4
./bin/xraygraphdb-wrapper

On macOS you may also use Docker Desktop, which is the recommended approach for local development.

First Boot: Bootstrap Admin

Fresh installs ship with no users in the auth store. The daemon will not let you create the first admin user over the wire (chicken-and-egg), so you must run the one-time bootstrap helper before the database is usable. This step does not apply to replica nodes — replicas inherit the admin user from the cluster's MAIN; see Cluster Setup.

Shell
sudo xraygraphdb-bootstrap-admin

The helper:

  1. Generates a 24-character random password.
  2. Displays it once on your terminal in red.
  3. Asks you to retype it to confirm you copied it.
  4. Writes the username/password to /run/xraygraphdb/bootstrap.env — this is a tmpfs file, never on disk — and starts the daemon.
  5. Schedules an ExecStartPost hook that wipes the env file ~15 seconds after the daemon comes up.
The password is shown exactly once. The script does not write it to any persistent file on this server. Copy it to your password manager when prompted. If you lose it, the only recovery is to wipe /var/lib/xraygraphdb and re-run the bootstrap — the existing user data is unrecoverable.

Loading datasets — where to put files

The default systemd unit ships with PrivateTmp=true for sandbox hardening. That gives the daemon its own private /tmp/ namespace, separate from the host's /tmp/. Anything you place in the host's /tmp/ is invisible to the daemon, and bulk-import calls against those paths fail-fast with 0 vertices / 0 edges in under a second — no error in the journal.

Do not put datasets in /tmp/. Even though the dataset file is world-readable, the daemon cannot see it. Use /var/lib/xraygraphdb/import/ instead — this path is in the unit's ReadWritePaths=, owned by the xraygraphdb user, and shares the daemon's namespace.
Shell
# Right way — daemon can read it:
sudo mkdir -p /var/lib/xraygraphdb/import
sudo mv ~/com-friendster.ungraph.txt /var/lib/xraygraphdb/import/
sudo chown -R xraygraphdb:xraygraphdb /var/lib/xraygraphdb/import

# Then in your bench/import script:
client.bulk_import_file("/var/lib/xraygraphdb/import/com-friendster.ungraph.txt")

# Wrong way — daemon's PrivateTmp namespace makes this invisible:
#   client.bulk_import_file("/tmp/com-friendster.ungraph.txt")    ← returns 0/0 in 1s

If you need a different dataset path (e.g. a large NVMe mount at /data/), add it to the systemd unit's ReadWritePaths= via a drop-in:

/etc/systemd/system/xraygraphdb.service.d/datasets.conf
[Service]
ReadWritePaths=/data

Then systemctl daemon-reload && systemctl restart xraygraphdb, and the daemon can read/write under /data/.

If the host is going to join an existing cluster as a replica, skip this section and use xraygraphdb-cluster-join instead.

Connect

First-party clients speak xrayProtocol on port 7689; the Bolt port 7689's companion 7687 is provided for triage compatibility. Pick your language below.

First Connection: Python

xrayGraphDB is compatible with the official Neo4j Python driver. Install it with pip and connect over the Bolt protocol.

Shell
pip install neo4j
Python
from neo4j import GraphDatabase

driver = GraphDatabase.driver(
    "bolt://localhost:7687",
    auth=("admin", "<your-password>")
)

with driver.session() as session:
    # Create a node
    session.run(
        "CREATE (n:Person {name: $name, age: $age})",
        name="Alice", age=30
    )

    # Read it back
    result = session.run(
        "MATCH (n:Person {name: $name}) RETURN n.name, n.age",
        name="Alice"
    )
    record = result.single()
    print(record["n.name"], record["n.age"])
    # Output: Alice 30

driver.close()

First Connection: JavaScript

Shell
npm install neo4j-driver
JavaScript
const neo4j = require('neo4j-driver');

const driver = neo4j.driver(
  'bolt://localhost:7687',
  neo4j.auth.basic('admin', '<your-password>')
);

const session = driver.session();

try {
  // Create a node
  await session.run(
    'CREATE (n:Person {name: $name, age: $age})',
    { name: 'Bob', age: 25 }
  );

  // Read it back
  const result = await session.run(
    'MATCH (n:Person {name: $name}) RETURN n',
    { name: 'Bob' }
  );

  console.log(result.records[0].get('n').properties);
} finally {
  await session.close();
  await driver.close();
}

First Connection: Java

Add the Neo4j Java driver to your Maven or Gradle project.

XML
<!-- Maven dependency -->
<dependency>
  <groupId>org.neo4j.driver</groupId>
  <artifactId>neo4j-java-driver</artifactId>
  <version>5.x</version>
</dependency>
Java
import org.neo4j.driver.*;

public class XRayExample {
    public static void main(String[] args) {
        var driver = GraphDatabase.driver(
            "bolt://localhost:7687",
            AuthTokens.basic("admin", "<your-password>")
        );

        try (var session = driver.session()) {
            session.run(
                "CREATE (n:Person {name: $name})",
                Values.parameters("name", "Carol")
            );

            var result = session.run(
                "MATCH (n:Person) RETURN n.name"
            );

            while (result.hasNext()) {
                System.out.println(result.next().get("n.name").asString());
            }
        }
        driver.close();
    }
}

First Connection: Go

Shell
go get github.com/neo4j/neo4j-go-driver/v5
Go
package main

import (
    "context"
    "fmt"
    "github.com/neo4j/neo4j-go-driver/v5/neo4j"
)

func main() {
    ctx := context.Background()

    driver, err := neo4j.NewDriverWithContext(
        "bolt://localhost:7687",
        neo4j.BasicAuth("admin", "<your-password>", ""),
    )
    if err != nil { panic(err) }
    defer driver.Close(ctx)

    session := driver.NewSession(ctx, neo4j.SessionConfig{})
    defer session.Close(ctx)

    _, err = session.Run(ctx,
        "CREATE (n:Person {name: $name})",
        map[string]any{"name": "Dave"},
    )
    if err != nil { panic(err) }

    result, err := session.Run(ctx,
        "MATCH (n:Person) RETURN n.name", nil,
    )
    if err != nil { panic(err) }

    for result.Next(ctx) {
        fmt.Println(result.Record().Values[0])
    }
}

First Connection: .NET

Shell
dotnet add package Neo4j.Driver
C#
using Neo4j.Driver;

var driver = GraphDatabase.Driver(
    "bolt://localhost:7687",
    AuthTokens.Basic("admin", "<your-password>")
);

await using var session = driver.AsyncSession();

await session.RunAsync(
    "CREATE (n:Person {name: $name})",
    new { name = "Eve" }
);

var result = await session.RunAsync(
    "MATCH (n:Person) RETURN n.name"
);

var records = await result.ToListAsync();
foreach (var record in records)
{
    Console.WriteLine(record["n.name"].As<string>());
}

await driver.DisposeAsync();

Quick Start Tutorial

This tutorial walks through creating a small graph, querying it, and cleaning up. It assumes you have a running xrayGraphDB instance and a Python driver installed.

Cypher
// Step 1: Create some nodes
CREATE (alice:Person {name: "Alice", age: 30})
CREATE (bob:Person {name: "Bob", age: 25})
CREATE (carol:Person {name: "Carol", age: 35})
CREATE (proj:Project {name: "xrayGraphDB"})
RETURN alice, bob, carol, proj;

// Step 2: Create relationships
MATCH (a:Person {name: "Alice"}), (b:Person {name: "Bob"})
CREATE (a)-[:KNOWS]->(b)
RETURN a, b;

MATCH (a:Person {name: "Alice"}), (p:Project {name: "xrayGraphDB"})
CREATE (a)-[:WORKS_ON {role: "lead"}]->(p)
RETURN a, p;

MATCH (b:Person {name: "Bob"}), (p:Project {name: "xrayGraphDB"})
CREATE (b)-[:WORKS_ON {role: "contributor"}]->(p)
RETURN b, p;

// Step 3: Query the graph
MATCH (p:Person)-[:WORKS_ON]->(proj:Project)
RETURN p.name, proj.name;

// Step 4: Update properties
MATCH (a:Person {name: "Alice"})
SET a.email = "alice@example.com"
RETURN a;

// Step 5: Clean up
MATCH (n) DETACH DELETE n;
Tip: Use parameterized queries in production code. Inline values in Cypher strings are shown here for readability but should be replaced with parameters ($name, $age, etc.) to prevent injection and improve plan cache hit rates.

MATCH

The MATCH clause is the primary read operation. It describes a pattern to find in the graph and binds matching subgraphs to variables.

Cypher
// Match all nodes with a specific label
MATCH (n:Person)
RETURN n;

// Match a relationship pattern
MATCH (a:Person)-[:KNOWS]->(b:Person)
RETURN a.name, b.name;

// Match with relationship variable
MATCH (a:Person)-[r:WORKS_ON]->(p:Project)
RETURN a.name, r.role, p.name;

// Match any direction
MATCH (a:Person)-[:KNOWS]-(b:Person)
RETURN a.name, b.name;

// Match with multiple labels
MATCH (n:Person:Employee)
RETURN n;

Patterns can include any combination of nodes, relationships, and directions. Nodes are enclosed in parentheses (), relationships in square brackets [], and direction is indicated by arrows -> or <-.

WHERE

The WHERE clause filters results from MATCH patterns. It supports comparison operators, boolean logic, string matching, list predicates, and null checks.

Cypher
// Comparison operators
MATCH (n:Person)
WHERE n.age > 25 AND n.age <= 40
RETURN n.name, n.age;

// String matching
MATCH (n:Person)
WHERE n.name STARTS WITH "A"
RETURN n;

// Regular expression
MATCH (n:Person)
WHERE n.email =~ ".*@example\\.com"
RETURN n;

// Null checks
MATCH (n:Person)
WHERE n.email IS NOT NULL
RETURN n;

// IN list
MATCH (n:Person)
WHERE n.name IN ["Alice", "Bob", "Carol"]
RETURN n;

// Pattern predicates (exists)
MATCH (n:Person)
WHERE (n)-[:WORKS_ON]->()
RETURN n.name;
OperatorDescriptionExample
=Equaln.age = 30
<>Not equaln.name <> "Alice"
<, >, <=, >=Comparisonn.age >= 18
AND, OR, NOTBoolean logicn.age > 20 AND n.active = true
INList membershipn.status IN ["active", "pending"]
STARTS WITHString prefixn.name STARTS WITH "Al"
ENDS WITHString suffixn.name ENDS WITH "ice"
CONTAINSString containsn.name CONTAINS "li"
=~Regex matchn.email =~ ".*@example\\.com"
IS NULLNull checkn.deleted IS NULL
IS NOT NULLNot nulln.email IS NOT NULL

RETURN

RETURN specifies which values to include in the result set. You can return nodes, relationships, properties, expressions, or aggregations.

Cypher
// Return specific properties
MATCH (n:Person)
RETURN n.name, n.age;

// Alias with AS
MATCH (n:Person)
RETURN n.name AS person_name, n.age AS years;

// Return all properties as a map
MATCH (n:Person)
RETURN properties(n);

// Return distinct values
MATCH (n:Person)-[:WORKS_ON]->(p:Project)
RETURN DISTINCT p.name;

// Expressions in RETURN
MATCH (n:Person)
RETURN n.name, n.age * 12 AS age_in_months;

ORDER BY / LIMIT / SKIP

Control the ordering and pagination of results.

Cypher
// Order by a property
MATCH (n:Person)
RETURN n.name, n.age
ORDER BY n.age DESC;

// Limit results
MATCH (n:Person)
RETURN n.name
ORDER BY n.name
LIMIT 10;

// Pagination with SKIP and LIMIT
MATCH (n:Person)
RETURN n.name
ORDER BY n.name
SKIP 20
LIMIT 10;

// Multiple sort keys
MATCH (n:Person)
RETURN n.name, n.age
ORDER BY n.age DESC, n.name ASC;

WITH

WITH acts as a pipeline separator, allowing you to chain query stages together. Variables not listed in WITH are not available in subsequent clauses.

Cypher
// Filter intermediate results
MATCH (p:Person)-[:WORKS_ON]->(proj:Project)
WITH proj, count(p) AS team_size
WHERE team_size > 3
RETURN proj.name, team_size
ORDER BY team_size DESC;

// Chain queries
MATCH (n:Person)
WITH n
ORDER BY n.age DESC
LIMIT 5
MATCH (n)-[:KNOWS]->(friend)
RETURN n.name, collect(friend.name) AS friends;

UNWIND

UNWIND expands a list into individual rows. Useful for bulk operations and working with list parameters.

Cypher
// Expand a list
UNWIND [1, 2, 3] AS x
RETURN x;

// Bulk create from parameters
UNWIND $people AS person
CREATE (n:Person {name: person.name, age: person.age});

// Combine with MATCH
UNWIND ["Alice", "Bob"] AS name
MATCH (n:Person {name: name})
RETURN n;

OPTIONAL MATCH

OPTIONAL MATCH works like MATCH but returns null for missing parts of the pattern instead of excluding the row entirely. Equivalent to a left outer join.

Cypher
// Return all people, even those without projects
MATCH (p:Person)
OPTIONAL MATCH (p)-[:WORKS_ON]->(proj:Project)
RETURN p.name, proj.name;

CREATE

CREATE adds new nodes and relationships to the graph. It always creates new elements (use MERGE to avoid duplicates).

Cypher
// Create a single node
CREATE (n:Person {name: "Frank", age: 28})
RETURN n;

// Create multiple nodes
CREATE (a:Person {name: "Grace"}),
       (b:Person {name: "Hank"});

// Create a node with multiple labels
CREATE (n:Person:Developer {name: "Ivy"});

// Create a relationship between existing nodes
MATCH (a:Person {name: "Grace"}), (b:Person {name: "Hank"})
CREATE (a)-[:KNOWS {since: 2024}]->(b)
RETURN a, b;

// Create a full path in one statement
CREATE (a:Module {name: "auth"})-[:IMPORTS]->(b:Module {name: "crypto"})
RETURN a, b;

MERGE

MERGE ensures a pattern exists in the graph. If the pattern is found, it is bound. If not found, it is created. Use ON CREATE SET and ON MATCH SET to conditionally set properties.

Cypher
// Merge a node (create if not exists)
MERGE (n:Person {name: "Alice"})
ON CREATE SET n.created = timestamp()
ON MATCH SET n.lastSeen = timestamp()
RETURN n;

// Merge a relationship
MATCH (a:Person {name: "Alice"}), (b:Person {name: "Bob"})
MERGE (a)-[r:KNOWS]->(b)
ON CREATE SET r.since = 2024
RETURN r;
Note: MERGE matches the entire pattern. If you merge on (a)-[:KNOWS]->(b) and the relationship does not exist, it creates only the relationship, not the nodes (they must already be bound by a preceding MATCH or MERGE).

SET

SET updates properties on nodes and relationships, or adds labels to nodes.

Cypher
// Set a property
MATCH (n:Person {name: "Alice"})
SET n.age = 31
RETURN n;

// Set multiple properties
MATCH (n:Person {name: "Alice"})
SET n.age = 31, n.email = "alice@example.com"
RETURN n;

// Replace all properties with a map
MATCH (n:Person {name: "Alice"})
SET n = {name: "Alice", age: 31, active: true}
RETURN n;

// Merge properties (add without removing existing)
MATCH (n:Person {name: "Alice"})
SET n += {department: "engineering"}
RETURN n;

// Add a label
MATCH (n:Person {name: "Alice"})
SET n:Employee
RETURN n;

REMOVE

REMOVE deletes properties from nodes/relationships and removes labels from nodes.

Cypher
// Remove a property
MATCH (n:Person {name: "Alice"})
REMOVE n.email
RETURN n;

// Remove a label
MATCH (n:Person:Employee {name: "Alice"})
REMOVE n:Employee
RETURN labels(n);

DELETE / DETACH DELETE

DELETE removes nodes and relationships. A node cannot be deleted if it still has relationships. Use DETACH DELETE to delete a node and all its relationships in one operation.

Cypher
// Delete a relationship
MATCH (a:Person)-[r:KNOWS]->(b:Person)
WHERE a.name = "Alice" AND b.name = "Bob"
DELETE r;

// Delete a node (must have no relationships)
MATCH (n:Person {name: "Frank"})
DELETE n;

// Detach delete (node + all relationships)
MATCH (n:Person {name: "Alice"})
DETACH DELETE n;

// Delete all nodes and relationships in the database
MATCH (n) DETACH DELETE n;
Warning: MATCH (n) DETACH DELETE n removes the entire graph. There is no undo. Make a snapshot before running destructive queries on production data.

Variable-length Paths

Variable-length path patterns match paths of varying depth using the * syntax inside relationship brackets.

Cypher
// Paths of exactly 2 hops
MATCH (a:Person)-[:KNOWS*2]->(c:Person)
RETURN a.name, c.name;

// Paths of 1 to 5 hops
MATCH (a:Person)-[:KNOWS*1..5]->(c:Person)
RETURN a.name, c.name;

// Paths of any length (use with caution)
MATCH (a:Person {name: "Alice"})-[:KNOWS*]->(c:Person)
RETURN DISTINCT c.name;

// Capture the path
MATCH path = (a:Person {name: "Alice"})-[:KNOWS*1..3]->(c:Person)
RETURN path, length(path) AS hops;
Note: Unbounded variable-length paths (* without limits) can be expensive on large graphs. Always set an upper bound when possible.

shortestPath / allShortestPaths

Find the shortest path(s) between two nodes.

Cypher
// Find one shortest path
MATCH (a:Person {name: "Alice"}),
      (b:Person {name: "Eve"})
MATCH p = shortestPath((a)-[*..10]-(b))
RETURN p, length(p) AS hops;

// Find all shortest paths (same length)
MATCH (a:Person {name: "Alice"}),
      (b:Person {name: "Eve"})
MATCH p = allShortestPaths((a)-[*..10]-(b))
RETURN p;

// With relationship type filter
MATCH (a:Person {name: "Alice"}),
      (b:Person {name: "Eve"})
MATCH p = shortestPath((a)-[:KNOWS|WORKS_WITH*..10]-(b))
RETURN p;

BFS Traversal

Breadth-first search traversal is available for exploring graphs level by level. BFS guarantees that nodes are visited in order of increasing distance from the start node.

Cypher
// BFS with upper bound
MATCH (start:Person {name: "Alice"})
MATCH path = (start)-[:KNOWS BFS]->(target)
RETURN target.name, length(path) AS distance
ORDER BY distance;

BFS traversal is the default algorithm used by shortestPath. Use the explicit BFS syntax when you need to enumerate all reachable nodes by distance layer.

Aggregation

Aggregation functions operate on groups of rows. Non-aggregated columns in RETURN act as implicit group keys (similar to SQL GROUP BY).

Cypher
// Count
MATCH (n:Person)
RETURN count(n) AS total_people;

// Group and count
MATCH (p:Person)-[:WORKS_ON]->(proj:Project)
RETURN proj.name, count(p) AS team_size
ORDER BY team_size DESC;

// Sum, average, min, max
MATCH (n:Person)
RETURN
  sum(n.age) AS total_age,
  avg(n.age) AS avg_age,
  min(n.age) AS youngest,
  max(n.age) AS oldest;

// Collect into a list
MATCH (p:Person)-[:WORKS_ON]->(proj:Project)
RETURN proj.name, collect(p.name) AS members;

// Standard deviation and percentile
MATCH (n:Person)
RETURN
  stDev(n.age) AS std_dev,
  percentileCont(n.age, 0.5) AS median;
FunctionDescriptionExample
count(expr)Number of non-null valuescount(n)
sum(expr)Sum of numeric valuessum(n.salary)
avg(expr)Average of numeric valuesavg(n.age)
min(expr)Minimum valuemin(n.created)
max(expr)Maximum valuemax(n.score)
collect(expr)Collect values into a listcollect(n.name)
percentileCont(expr, p)Continuous percentile (interpolated)percentileCont(n.age, 0.5)
percentileDisc(expr, p)Discrete percentile (nearest value)percentileDisc(n.age, 0.9)
stDev(expr)Standard deviation (sample)stDev(n.score)
stDevP(expr)Standard deviation (population)stDevP(n.score)

Indexing & Constraints

Indexes accelerate lookups by property value. Constraints enforce data integrity rules.

Cypher
// Create a label-property index
CREATE INDEX ON :Person(name);

// Neo4j-compatible named index syntax
CREATE INDEX person_name_idx
FOR (n:Person)
ON (n.name);

// Composite index
CREATE INDEX ON :Person(name, age);

// Drop an index
DROP INDEX ON :Person(name);

// Unique constraint
CREATE CONSTRAINT ON (n:Person) ASSERT n.email IS UNIQUE;

// Existence constraint
CREATE CONSTRAINT ON (n:Person) ASSERT EXISTS (n.name);

// Show index info
SHOW INDEX INFO;

// List all constraints
SHOW CONSTRAINT INFO;
Tip: Always create indexes on properties used in WHERE clauses and MATCH patterns. Without an index, the engine must scan all nodes of a given label.

Transactions

xrayGraphDB supports both auto-commit transactions (single query) and explicit transactions (multi-query).

Auto-commit Transactions

Every query sent via session.run() runs in its own auto-commit transaction. If the query succeeds, it is committed. If it fails, it is rolled back.

Explicit Transactions

Use explicit transactions when you need to execute multiple queries atomically.

Python
with driver.session() as session:
    tx = session.begin_transaction()
    try:
        tx.run("CREATE (a:Account {id: $id, balance: $bal})",
               id="A001", bal=1000)
        tx.run("CREATE (a:Account {id: $id, balance: $bal})",
               id="A002", bal=500)
        tx.commit()
    except Exception:
        tx.rollback()
        raise
Cypher
// Explicit transaction commands (Bolt protocol)
BEGIN;
CREATE (n:Temp {data: "test"});
COMMIT;

// Or rollback
BEGIN;
CREATE (n:Temp {data: "test"});
ROLLBACK;
Built-in Functions and Procedures. xrayGraphDB ships with a comprehensive library of built-in functions, procedures, and GFQL operators — every entry below is queryable at runtime via CALL xg.builtin_functions() and available in the Community edition at no cost.

GFQL Overview

GFQL (Graph Frame Query Language) is a dataframe-native query language for graph traversal and analysis. It is designed for data scientists and developers who prefer chainable, functional-style operations over declarative pattern matching.

GFQL queries run natively inside the xrayGraphDB engine alongside Cypher. They operate on the same in-memory graph as Cypher queries, with the same transaction isolation guarantees.

Note: GFQL is available starting with xrayGraphDB v4.0. It can be used alongside Cypher in the same database without conflicts.

SET GFQL_CONTEXT

Before executing GFQL operations, set a query context that defines the working scope (labels, edge types, or property filters).

GFQL
// Set context to all Function nodes
SET GFQL_CONTEXT label='Function';

// Set context with edge filter
SET GFQL_CONTEXT label='Module', edge_type='IMPORTS';

// Set context with property filter
SET GFQL_CONTEXT label='Person', WHERE age > 25;

chain(), n(), e_forward(), e_reverse()

GFQL operations are chained together using a fluent API. The core primitives are:

FunctionDescriptionExample
chain()Start a GFQL operation chainchain()
n()Select nodes (optionally filtered)n(label='Person')
e_forward()Traverse outgoing edgese_forward(type='CALLS')
e_reverse()Traverse incoming edgese_reverse(type='CALLS')
.filter()Filter current frame.filter(complexity > 5)
.hop()Multi-hop traversal.hop(edge_type='CALLS', depth=3)
.select()Project specific columns.select('name', 'module')
.aggregate()Group and aggregate.aggregate(by='module', count='name')
GFQL
// Find high-complexity functions and their callees
chain()
  .n(label='Function')
  .filter(complexity > 10)
  .e_forward(type='CALLS')
  .select('source.name', 'target.name');

// Multi-hop traversal
chain()
  .n(label='Module', name='auth')
  .hop(edge_type='IMPORTS', depth=3)
  .select('name', '_hop_depth');

// Aggregate by module
chain()
  .n(label='Function')
  .aggregate(by='module', count='name', avg_complexity='complexity');

Filter Predicates

GFQL supports the following predicates inside .filter() expressions:

OperatorDescriptionExample
=, !=Equality / inequality.filter(status = 'active')
>, <, >=, <=Comparison.filter(age >= 18)
AND, ORLogical operators.filter(age > 18 AND active = true)
NOTLogical negation.filter(NOT deleted)
INList membership.filter(status IN ['active', 'pending'])
LIKEPattern matching (% wildcard).filter(name LIKE 'auth%')
IS NULLNull check.filter(email IS NULL)
IS NOT NULLNot null.filter(email IS NOT NULL)

Function Reference

395 built-in functions. Click a category in the sidebar to filter, or use the search box. Click any card to expand its full signature and details.

Procedure Reference

125 callable procedures invoked with CALL.

GFQL Operators

Graph operators available through the GFQL chain syntax.