Home / Documentation

xrayGraphDB Documentation

Complete reference for xrayGraphDB v4.x. Covers installation, query language, functions, protocols, server configuration, and administration.

System Requirements

xrayGraphDB runs on 64-bit Linux, macOS, and Docker. Minimum and recommended specifications are listed below.

ComponentMinimumRecommended
CPU2 cores, x86_64 or ARM648+ cores with AVX2 support
RAM2 GB16 GB+ (in-memory engine)
Disk1 GB free (snapshots)SSD, 10 GB+ for snapshots
OSUbuntu 22.04+, Debian 12+, macOS 13+Ubuntu 24.04 LTS
DockerDocker Engine 24+Docker Engine 27+
KernelLinux 5.15+Linux 6.x
Note: xrayGraphDB is an in-memory database. All graph data must fit in RAM. Plan memory allocation to be roughly 2x your expected data size to allow for indexes and query working memory.

Installation: Docker

Docker is the fastest way to start xrayGraphDB. A single command pulls the image and starts the server with both Bolt and xrayProtocol ports exposed.

Shell
# Pull and run xrayGraphDB
docker run -d \
  --name xraygraphdb \
  -p 7687:7687 \
  -p 7689:7689 \
  -v xraydata:/data \
  xraygraphdb:4.0.2

The -v xraydata:/data flag creates a named volume for snapshot persistence. Without it, data is lost when the container stops.

Docker Compose

For production deployments, use a docker-compose.yml file:

YAML
version: "3.8"
services:
  xraygraphdb:
    image: xraygraphdb:4.0.2
    ports:
      - "7687:7687"   # Bolt
      - "7689:7689"   # xrayProtocol
    volumes:
      - xraydata:/data
    environment:
      - XRAY_DATA_DIRECTORY=/data
      - XRAY_LOG_LEVEL=INFO
      - XRAY_SNAPSHOT_ON_EXIT=true
    restart: unless-stopped

volumes:
  xraydata:
Shell
docker compose up -d
docker compose logs -f xraygraphdb

Verify the Server

Shell
# Check container status
docker ps --filter name=xraygraphdb

# Check logs for startup confirmation
docker logs xraygraphdb | grep "listening"
# Expected: "Bolt server listening on 0.0.0.0:7687"
#           "xrayProtocol server listening on 0.0.0.0:7689"

Installation: Linux

Binary packages are available for Ubuntu/Debian (amd64 and arm64). Download the tarball, extract it, and run.

Shell
# Download
wget https://releases.emtailabs.com/xraygraphdb/xraygraphdb-4.0.2-linux-amd64.tar.gz

# Extract
tar xzf xraygraphdb-4.0.2-linux-amd64.tar.gz
cd xraygraphdb-4.0.2

# Start the server
./bin/xraygraphdb-wrapper

# Server listens on:
#   Bolt      -> :7687
#   xrayProto -> :7689

Systemd Service

To run xrayGraphDB as a system service:

INI
# /etc/systemd/system/xraygraphdb.service
[Unit]
Description=xrayGraphDB Graph Database
After=network.target

[Service]
Type=simple
User=xraygraph
ExecStart=/opt/xraygraphdb/bin/xraygraphdb-wrapper \
  --data-directory=/var/lib/xraygraphdb \
  --log-level=INFO
ExecStop=/bin/kill -SIGTERM $MAINPID
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
Shell
sudo systemctl daemon-reload
sudo systemctl enable xraygraphdb
sudo systemctl start xraygraphdb
sudo systemctl status xraygraphdb
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-4.0.2-macos-arm64.tar.gz

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

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

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;

String Functions

FunctionSignatureDescriptionExample
toLowertoLower(string)Convert to lowercasetoLower("HELLO") = "hello"
toUppertoUpper(string)Convert to uppercasetoUpper("hello") = "HELLO"
trimtrim(string)Remove leading/trailing whitespacetrim(" hi ") = "hi"
ltrimltrim(string)Remove leading whitespaceltrim(" hi") = "hi"
rtrimrtrim(string)Remove trailing whitespacertrim("hi ") = "hi"
replacereplace(string, search, replacement)Replace all occurrencesreplace("hello", "l", "r") = "herro"
splitsplit(string, delimiter)Split string into listsplit("a,b,c", ",") = ["a","b","c"]
substringsubstring(string, start [, length])Extract substringsubstring("hello", 1, 3) = "ell"
leftleft(string, length)First N charactersleft("hello", 3) = "hel"
rightright(string, length)Last N charactersright("hello", 3) = "llo"
reversereverse(string)Reverse stringreverse("hello") = "olleh"
startsWithn.prop STARTS WITH strCheck string prefixn.name STARTS WITH "Al"
endsWithn.prop ENDS WITH strCheck string suffixn.name ENDS WITH "ce"
containsn.prop CONTAINS strCheck substring presencen.name CONTAINS "li"
sizesize(string)String lengthsize("hello") = 5
toStringtoString(value)Convert value to stringtoString(42) = "42"
Cypher
// String function examples
RETURN
  toLower("Hello World") AS lower,
  split("one:two:three", ":") AS parts,
  replace("foo-bar-baz", "-", "_") AS replaced,
  substring("xrayGraphDB", 4) AS suffix;

Math Functions

FunctionSignatureDescriptionExample
absabs(number)Absolute valueabs(-5) = 5
ceilceil(number)Round upceil(2.3) = 3.0
floorfloor(number)Round downfloor(2.7) = 2.0
roundround(number)Round to nearest integerround(2.5) = 3.0
sqrtsqrt(number)Square rootsqrt(16) = 4.0
loglog(number)Natural logarithmlog(e()) = 1.0
log10log10(number)Base-10 logarithmlog10(100) = 2.0
expexp(number)e raised to powerexp(1) = 2.718...
signsign(number)Sign (-1, 0, 1)sign(-42) = -1
randrand()Random float [0, 1)rand()
pipi()Pi constantpi() = 3.14159...
ee()Euler's numbere() = 2.71828...
sinsin(radians)Sinesin(pi()/2) = 1.0
coscos(radians)Cosinecos(0) = 1.0
tantan(radians)Tangenttan(pi()/4) = 1.0
asinasin(number)Arcsineasin(1) = pi()/2
acosacos(number)Arccosineacos(1) = 0
atanatan(number)Arctangentatan(1) = pi()/4
atan2atan2(y, x)Two-argument arctangentatan2(1, 1) = pi()/4
degreesdegrees(radians)Radians to degreesdegrees(pi()) = 180.0
radiansradians(degrees)Degrees to radiansradians(180) = pi()

List Functions

FunctionSignatureDescriptionExample
rangerange(start, end [, step])Generate integer listrange(0, 5) = [0,1,2,3,4,5]
sizesize(list)Number of elementssize([1,2,3]) = 3
headhead(list)First elementhead([1,2,3]) = 1
tailtail(list)All elements except firsttail([1,2,3]) = [2,3]
lastlast(list)Last elementlast([1,2,3]) = 3
reversereverse(list)Reverse list orderreverse([1,2,3]) = [3,2,1]
sortsort(list)Sort ascendingsort([3,1,2]) = [1,2,3]
reducereduce(acc = init, x IN list | expr)Fold list to single valuereduce(s = 0, x IN [1,2,3] | s + x) = 6
filter[x IN list WHERE pred]Filter elements[x IN range(1,10) WHERE x % 2 = 0]
keyskeys(node_or_map)Property keyskeys(n)
labelslabels(node)Node labelslabels(n)
nodesnodes(path)Nodes in a pathnodes(p)
relationshipsrelationships(path)Relationships in a pathrelationships(p)
Cypher
// List comprehension
WITH [1, 2, 3, 4, 5] AS nums
RETURN [x IN nums WHERE x > 2 | x * 10] AS result;
// result: [30, 40, 50]

// Reduce
RETURN reduce(total = 0, x IN [1, 2, 3, 4] | total + x) AS sum;
// sum: 10

// Extract nodes from a path
MATCH p = (a:Person)-[:KNOWS*1..3]->(b:Person)
RETURN [n IN nodes(p) | n.name] AS names;

Type Conversion

FunctionSignatureDescriptionExample
toIntegertoInteger(value)Convert to integertoInteger("42") = 42
toFloattoFloat(value)Convert to floattoFloat("3.14") = 3.14
toStringtoString(value)Convert to stringtoString(42) = "42"
toBooleantoBoolean(value)Convert to booleantoBoolean("true") = true
typetype(relationship)Relationship type stringtype(r) = "KNOWS"
labelslabels(node)Node labels as listlabels(n) = ["Person"]
keyskeys(node_or_map)Property keys as listkeys(n) = ["name", "age"]
propertiesproperties(node_or_rel)All properties as mapproperties(n)
idid(node_or_rel)Internal IDid(n)

Temporal Functions

FunctionSignatureDescriptionExample
timestamptimestamp()Current Unix timestamp (ms)timestamp()
datetimedatetime([map])Create a datetime valuedatetime({year: 2026, month: 4})
datedate([map])Create a date valuedate({year: 2026, month: 4, day: 2})
localtimelocaltime([map])Create a local time valuelocaltime({hour: 14, minute: 30})
localdatetimelocaldatetime([map])Create local datetimelocaldatetime()
durationduration(map)Create a durationduration({days: 7, hours: 3})
Cypher
// Store a timestamp
CREATE (e:Event {name: "deploy", ts: timestamp()});

// Query by time range
MATCH (e:Event)
WHERE e.ts > timestamp() - 86400000
RETURN e.name, e.ts
ORDER BY e.ts DESC;

// Duration arithmetic
RETURN datetime() + duration({hours: 3}) AS three_hours_later;

Vector Functions

xrayGraphDB provides a built-in EMBED() function that converts text into a 384-dimensional vector embedding. These vectors can be stored as node properties and used for similarity search.

Cypher
// Generate and store an embedding
MATCH (n:Document {title: "README"})
SET n.vec = EMBED("Getting started guide for xrayGraphDB")
RETURN n;

// Cosine similarity search
MATCH (n:Document)
WHERE cosine_similarity(n.vec, EMBED("installation guide")) > 0.75
RETURN n.title,
       cosine_similarity(n.vec, EMBED("installation guide")) AS score
ORDER BY score DESC
LIMIT 10;
FunctionSignatureDescriptionReturns
EMBEDEMBED(text)Convert text to a 384-dimensional vectorList of 384 floats
cosine_similaritycosine_similarity(vec1, vec2)Cosine similarity between two vectorsFloat [-1.0, 1.0]
l2_distancel2_distance(vec1, vec2)Euclidean (L2) distanceFloat >= 0
dot_productdot_product(vec1, vec2)Dot product of two vectorsFloat

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. 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)

Bolt Protocol (port 7687)

Bolt is the primary protocol for xrayGraphDB. It supports the complete Cypher feature set including stored procedures (CALL...YIELD), DDL operations (CREATE INDEX, CREATE CONSTRAINT), EXPLAIN/PROFILE, and all query types. Use Bolt for application development and any operation that requires the full query engine.

xrayGraphDB is compatible with the entire Neo4j driver ecosystem. Any application using a Neo4j driver can connect without code changes.

Supported Bolt Versions

VersionStatusNotes
Bolt v3SupportedMinimum version, used by older drivers
Bolt v4.xSupportedMulti-database messages accepted (single-db only)
Bolt v5.0-5.6SupportedFull feature support including notifications

Bolt TLS is supported. Pass the --bolt-cert-file and --bolt-key-file flags to enable encrypted connections.

Driver Compatibility

DriverLanguageMinimum VersionStatus
neo4j-python-driverPython5.0+Tested
neo4j-driver (npm)JavaScript5.0+Tested
neo4j-java-driverJava5.0+Tested
neo4j-go-driverGo5.0+Tested
Neo4j.Driver (NuGet)C# / .NET5.0+Tested
py2neoPython2021.1+Compatible
neomodelPython5.0+Compatible

Bolt Connection Examples

All Neo4j drivers connect the same way. The only requirement is to point the driver at the xrayGraphDB Bolt port.

Connection Strings
# Unencrypted
bolt://localhost:7687

# With TLS (if server has cert configured)
bolt+s://xraygraphdb.example.com:7687

# Neo4j scheme (resolves to bolt://)
neo4j://localhost:7687

Authentication uses the same credentials as xrayGraphDB user management. The default admin account is admin with the password you set during setup.

xrayProtocol (port 7689)

xrayProtocol is xrayGraphDB's native binary protocol optimized for high-throughput, columnar data streaming. It delivers up to 24x higher throughput than Bolt for large result sets.

Important: Stored procedures (CALL...YIELD), EXPLAIN, PROFILE, and DDL commands (CREATE INDEX, CREATE CONSTRAINT) are currently Bolt-only. xrayProtocol supports MATCH, CREATE, MERGE, DELETE, and RETURN queries. Use Bolt for the full Cypher feature set.

Protocol Feature Comparison

FeatureBolt (7687)xrayProtocol (7689)
MATCH / CREATE / MERGE / DELETEYesYes
RETURN with result streamingYesYes (columnar, 24x faster)
Parameters ($param)YesYes
Transactions (BEGIN/COMMIT)YesYes
CALL...YIELD (stored procedures)YesNot yet
EXPLAIN / PROFILEYesNot yet
CREATE INDEX / CONSTRAINTYesNot yet
GFQL queriesYesYes
LZ4 compressionNoYes
Query pipeliningNoYes
Neo4j driver compatibleYesNo (native client)
Best forFull feature set, stored procedures, DDLBulk analytics, large result sets, data pipelines

When to Use xrayProtocol

When to Use Bolt

Connection String
# xrayProtocol URI
xray://localhost:7689

xrayProtocol Message Types

xrayProtocol uses a binary message format with the following core message types:

MessageDirectionDescription
HELLOClient to ServerInitiate connection with auth credentials
WELCOMEServer to ClientConfirm authentication and protocol version
RUNClient to ServerExecute a query with optional parameters
COLUMNSServer to ClientColumn metadata for the result set
CHUNKServer to ClientColumnar data batch (Arrow-compatible)
DONEServer to ClientQuery complete with summary statistics
ERRORServer to ClientError response with code and message
GOODBYEEitherClose the connection gracefully

Connection Lifecycle

An xrayProtocol session follows this sequence:

Sequence
Client                           Server
  |                                  |
  |------ HELLO (auth) ------------>|
  |<----- WELCOME (version) --------|
  |                                  |
  |------ RUN (query, params) ----->|
  |<----- COLUMNS (metadata) -------|
  |<----- CHUNK (data batch 1) -----|
  |<----- CHUNK (data batch 2) -----|
  |<----- ...                       |
  |<----- DONE (summary) -----------|
  |                                  |
  |------ RUN (next query) -------->|
  |<----- ...                       |
  |                                  |
  |------ GOODBYE ----------------->|
  |<----- GOODBYE ------------------|
  |                                  |

Data is streamed in columnar CHUNK messages. Each CHUNK contains a batch of rows (default batch size: 8192). The client does not need to wait for all chunks before processing.

Server Flags

xrayGraphDB is configured via command-line flags. Flags can also be set via environment variables using the format XRAY_FLAG_NAME (uppercase, underscores instead of hyphens).

Shell
# Command-line
./bin/xraygraphdb-wrapper --bolt-port=7687 --data-directory=/data

# Environment variable equivalent
export XRAY_BOLT_PORT=7687
export XRAY_DATA_DIRECTORY=/data
./bin/xraygraphdb-wrapper

Data & Storage Flags

FlagDefaultDescription
--data-directory/var/lib/xraygraphdbDirectory for snapshots and WAL files
--storage-gc-cycle-sec30Garbage collection interval in seconds
--storage-snapshot-interval-sec300Automatic snapshot interval (0 to disable)
--storage-snapshot-on-exittrueTake snapshot on graceful shutdown
--storage-recover-on-startuptrueLoad latest snapshot on startup
--storage-snapshot-retention-count3Number of snapshots to keep
--storage-wal-enabledtrueEnable write-ahead logging

Network Flags

FlagDefaultDescription
--bolt-port7687Port for the Bolt protocol
--bolt-address0.0.0.0Bind address for Bolt
--bolt-num-workers(auto)Number of Bolt worker threads
--bolt-cert-file(none)Path to TLS certificate for Bolt
--bolt-key-file(none)Path to TLS private key for Bolt
--xray-port7689Port for xrayProtocol
--xray-address0.0.0.0Bind address for xrayProtocol

Snapshot & Persistence Flags

FlagDefaultDescription
--storage-snapshot-interval-sec300Seconds between automatic snapshots (0 = manual only)
--storage-snapshot-on-exittrueCreate snapshot during graceful shutdown
--storage-snapshot-retention-count3How many snapshot files to retain on disk
--storage-recover-on-startuptrueAutomatically load latest snapshot on start
--storage-wal-enabledtrueWrite-ahead log for crash recovery between snapshots
--storage-wal-file-size-kib20480Max WAL file size before rotation (KiB)

Performance Flags

FlagDefaultDescription
--query-execution-timeout-sec180Max execution time per query (0 = no limit)
--plan-cache-size1024Number of compiled query plans to cache
--bolt-session-inactivity-timeout1800Idle session timeout in seconds
--storage-parallel-index-recoverytrueRebuild indexes in parallel during recovery
--storage-parallel-schema-recoverytrueRebuild schemas in parallel during recovery

Auth Flags

FlagDefaultDescription
--auth-enabledtrueEnable authentication (set false for local dev only)
--auth-password-strength8Minimum password length
--auth-module-timeout-ms10000Timeout for external auth module calls
Warning: Never disable authentication in production. The --auth-enabled=false flag is for isolated development environments only.

License Flags

FlagDefaultDescription
--license-key(none)Path to license key JSON file
--organization-name(none)Licensed organization name (must match key)

Without a license key, xrayGraphDB operates in Community mode with all core features available. A license unlocks commercial features (HA clustering, per-tenant encryption, RBAC). Reads, writes, and startup are never blocked by license status.

Persistence Model

xrayGraphDB is an in-memory database with disk-based persistence. Data lives in RAM during operation and is periodically written to disk as snapshots.

Snapshots

Snapshots are full-state dumps of the graph written to the configured --data-directory. They are triggered by:

Recovery on Startup

When --storage-recover-on-startup=true, the server loads the most recent snapshot on start, then replays any WAL entries created after that snapshot. This provides durability with minimal data loss.

Critical: Never use kill -9 to stop xrayGraphDB. A forced kill skips the exit snapshot and may corrupt in-progress WAL writes. Always use SIGTERM, systemctl stop, or docker stop.

Snapshot File Layout

Directory Structure
/var/lib/xraygraphdb/
  snapshots/
    snapshot-20260401T120000.bin
    snapshot-20260401T115500.bin
    snapshot-20260401T115000.bin
  wal/
    wal-000001.bin
    wal-000002.bin

Memory Configuration

Since xrayGraphDB stores all data in RAM, memory planning is critical.

Memory Estimation

As a rule of thumb, allocate 2x the estimated raw data size to account for indexes, query working memory, and GC overhead.

Docker Memory Limits

Shell
# Limit container to 16 GB
docker run -d \
  --name xraygraphdb \
  --memory=16g \
  -p 7687:7687 \
  xraygraphdb:4.0.2

User Management

xrayGraphDB supports built-in user management via Cypher commands. Users are authenticated against the internal user store.

Cypher
// Create a new user
CREATE USER analyst IDENTIFIED BY "<strong-password>";

// Change a user's password
ALTER USER analyst SET PASSWORD "<new-password>";

// Drop a user
DROP USER analyst;

// List all users
SHOW USERS;
Note: The admin user is created at first startup. Set a strong password immediately. All examples in this documentation use admin / <your-password> as the credentials.

Role Management

Roles define permissions that are granted to users. A user can have multiple roles. Permissions are additive across roles.

Cypher
// Create a role
CREATE ROLE analyst;

// Grant read access on all labels
GRANT READ ON LABELS * TO analyst;

// Grant read access on specific label
GRANT READ ON LABELS :Person TO analyst;

// Deny write access
DENY WRITE ON LABELS * TO analyst;

// Assign a role to a user
SET ROLE FOR analyst_user TO analyst;

// Revoke a permission
REVOKE READ ON LABELS :Secret FROM analyst;

// Show all roles and their privileges
SHOW PRIVILEGES;

// Drop a role
DROP ROLE analyst;
PermissionScopeDescription
READLABELS, EDGE_TYPESRead nodes/relationships of specified types
WRITELABELS, EDGE_TYPESCreate/update/delete specified types
CREATELABELSCreate nodes of specified labels
DELETELABELSDelete nodes of specified labels
INDEXGLOBALCreate and drop indexes
AUTHGLOBALManage users and roles

Admin CLI (xraygraph-admin)

The xraygraph-admin command-line tool provides administrative operations that cannot be performed through Cypher. It connects directly to the data directory and does not require a running server for some operations.

CommandDescriptionRequires Running Server
--validate-license <file>Verify license key signature, expiry, and organizationNo
--admin-reset --auth-token=<epoch>Reset admin password. Auth token is the Unix epoch from the server's first-start timestamp.No
--verify-integrity --auth-token=<epoch>Check snapshot and WAL integrity hashesNo
--repair --auth-token=<epoch>Attempt automatic repair of corrupted snapshot segmentsNo
Shell
# Validate a license key file
xraygraph-admin --validate-license /path/to/license.json

# Reset admin password (requires auth token from first-start log)
xraygraph-admin \
  --admin-reset \
  --auth-token=1711929600 \
  --data-directory=/var/lib/xraygraphdb

# Verify data integrity
xraygraph-admin \
  --verify-integrity \
  --auth-token=1711929600 \
  --data-directory=/var/lib/xraygraphdb

# Attempt repair of corrupted data
xraygraph-admin \
  --repair \
  --auth-token=1711929600 \
  --data-directory=/var/lib/xraygraphdb
Warning: The --repair command modifies snapshot files. Always make a backup of the data directory before running repair.

Backup & Recovery

Backup xrayGraphDB by copying the snapshot files from the data directory. The safest approach is to stop the server, copy the files, and restart.

Online Backup (server running)

Shell
# Trigger a snapshot first
# (connect via driver and run)
# FREE MEMORY;  -- triggers GC + snapshot

# Then copy the latest snapshot
cp /var/lib/xraygraphdb/snapshots/snapshot-latest.bin \
   /backup/xraygraphdb-$(date +%Y%m%d).bin

Offline Backup (server stopped)

Shell
# Stop the server (creates exit snapshot)
sudo systemctl stop xraygraphdb

# Copy the entire data directory
cp -r /var/lib/xraygraphdb /backup/xraygraphdb-$(date +%Y%m%d)

# Restart
sudo systemctl start xraygraphdb

Restore from Backup

Shell
# Stop the server
sudo systemctl stop xraygraphdb

# Replace data directory with backup
rm -rf /var/lib/xraygraphdb
cp -r /backup/xraygraphdb-20260401 /var/lib/xraygraphdb

# Restart (will load the restored snapshot)
sudo systemctl start xraygraphdb

License Management

xrayGraphDB uses Ed25519-signed JSON license keys. Community mode (no license) provides full query functionality with single-node deployment. Licensed mode unlocks HA clustering, per-tenant encryption, and RBAC.

Install a License at Startup

Shell
./bin/xraygraphdb-wrapper \
  --license-key=/path/to/license.json \
  --organization-name="Your Company"

Validate a License Offline

Shell
xraygraph-admin --validate-license /path/to/license.json
# Output:
#   Organization: Your Company
#   Expires:      2027-04-01
#   Features:     ha, encryption, rbac
#   Signature:    VALID

License Key Format

JSON
{
  "organization": "Your Company",
  "issued": "2026-04-01T00:00:00Z",
  "expires": "2027-04-01T00:00:00Z",
  "features": ["ha", "encryption", "rbac"],
  "signature": "<ed25519-signature>"
}
Note: A license is never required for reads, writes, or server startup. xrayGraphDB Community is fully functional as a single-node graph database. Only horizontal scaling, per-tenant encryption, and fine-grained RBAC require a license.

Supported Neo4j Syntax

xrayGraphDB supports the openCypher standard plus commonly-used Neo4j extensions. The following table summarizes compatibility.

FeatureStatusNotes
MATCH / RETURN / WHEREFullopenCypher standard
CREATE / MERGE / SET / REMOVE / DELETEFullopenCypher standard
Variable-length pathsFullIncluding shortestPath and allShortestPaths
Aggregation functionsFullcount, sum, avg, min, max, collect, percentile, stDev
List comprehensionsFullIncluding reduce, filter, map
Pattern comprehensionsFull[(n)-->(m) | m.name]
CASE expressionsFullSimple and generic CASE
Named indexes (CREATE INDEX name FOR ...)FullNeo4j 4.x+ syntax
SHOW INDEX INFOFull
SHOW CONSTRAINT INFOFull
Explicit transactions (BEGIN/COMMIT/ROLLBACK)Full
EXPLAIN / PROFILEFullQuery plan inspection
CALL proceduresPartialBuilt-in procedures only; no APOC
Multi-databaseNot supportedSingle database per instance
Subqueries (CALL { ... })Not supportedUse WITH for query chaining
LOAD CSVNot supportedUse driver-side bulk import instead

Driver Compatibility

xrayGraphDB is wire-compatible with Neo4j Bolt protocol versions 3 through 5.6. Any driver that speaks Bolt can connect. The recommended driver versions are listed in the Driver Compatibility table.

Connection URI Schemes

SchemeBehavior
bolt://Direct unencrypted connection
bolt+s://Direct TLS connection (verify server cert)
bolt+ssc://Direct TLS connection (self-signed cert accepted)
neo4j://Routing driver (resolves to bolt://, no cluster routing)
neo4j+s://Routing driver with TLS
Note: The neo4j:// scheme is accepted for compatibility. Since xrayGraphDB Community runs as a single instance, routing resolves to a direct connection. Cluster routing is available with a license.

Migration from Neo4j

Migrating from Neo4j to xrayGraphDB is straightforward for most applications.

Step 1: Export Data from Neo4j

Use the Neo4j APOC export or neo4j-admin dump to extract your data as Cypher statements.

Shell
# Using APOC in Neo4j to export Cypher statements
# Run inside Neo4j Browser:
# CALL apoc.export.cypher.all("/export/data.cypher", {})

# Or use neo4j-admin
neo4j-admin database dump neo4j --to-path=/export/

Step 2: Import into xrayGraphDB

Feed the Cypher export statements into xrayGraphDB via a driver script.

Python
from neo4j import GraphDatabase

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

with open("data.cypher") as f:
    statements = f.read().split(";")

with driver.session() as session:
    for stmt in statements:
        stmt = stmt.strip()
        if stmt:
            session.run(stmt)

driver.close()
print("Import complete")

Step 3: Update Connection String

Update your application's connection string from the Neo4j URI to the xrayGraphDB URI. No other code changes are needed if you use standard Cypher.

Differences to Be Aware Of