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.
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores, x86_64 or ARM64 | 8+ cores with AVX2 support |
| RAM | 2 GB | 16 GB+ (in-memory engine) |
| Disk | 1 GB free (snapshots) | SSD, 10 GB+ for snapshots |
| OS | Ubuntu 22.04+, Debian 12+, macOS 13+ | Ubuntu 24.04 LTS |
| Docker | Docker Engine 24+ | Docker Engine 27+ |
| Kernel | Linux 5.15+ | Linux 6.x |
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.
# 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:
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:
docker compose up -d docker compose logs -f xraygraphdb
Verify the Server
# 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.
# 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:
# /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
sudo systemctl daemon-reload sudo systemctl enable xraygraphdb sudo systemctl start xraygraphdb sudo systemctl status xraygraphdb
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.
# 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.
pip install neo4j
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
npm install neo4j-driver
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.
<!-- Maven dependency --> <dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>5.x</version> </dependency>
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
go get github.com/neo4j/neo4j-go-driver/v5
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
dotnet add package Neo4j.Driver
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.
// 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;
$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.
// 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.
// 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;
| Operator | Description | Example |
|---|---|---|
| = | Equal | n.age = 30 |
| <> | Not equal | n.name <> "Alice" |
| <, >, <=, >= | Comparison | n.age >= 18 |
| AND, OR, NOT | Boolean logic | n.age > 20 AND n.active = true |
| IN | List membership | n.status IN ["active", "pending"] |
| STARTS WITH | String prefix | n.name STARTS WITH "Al" |
| ENDS WITH | String suffix | n.name ENDS WITH "ice" |
| CONTAINS | String contains | n.name CONTAINS "li" |
| =~ | Regex match | n.email =~ ".*@example\\.com" |
| IS NULL | Null check | n.deleted IS NULL |
| IS NOT NULL | Not null | n.email IS NOT NULL |
RETURN
RETURN specifies which values to include in the result set. You can return nodes, relationships, properties, expressions, or aggregations.
// 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.
// 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.
// 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.
// 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.
// 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).
// 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.
// 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;
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.
// 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.
// 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.
// 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;
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.
// 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;
* 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.
// 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.
// 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).
// 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;
| Function | Description | Example |
|---|---|---|
| count(expr) | Number of non-null values | count(n) |
| sum(expr) | Sum of numeric values | sum(n.salary) |
| avg(expr) | Average of numeric values | avg(n.age) |
| min(expr) | Minimum value | min(n.created) |
| max(expr) | Maximum value | max(n.score) |
| collect(expr) | Collect values into a list | collect(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.
// 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;
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.
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
// Explicit transaction commands (Bolt protocol) BEGIN; CREATE (n:Temp {data: "test"}); COMMIT; // Or rollback BEGIN; CREATE (n:Temp {data: "test"}); ROLLBACK;
String Functions
| Function | Signature | Description | Example |
|---|---|---|---|
| toLower | toLower(string) | Convert to lowercase | toLower("HELLO") = "hello" |
| toUpper | toUpper(string) | Convert to uppercase | toUpper("hello") = "HELLO" |
| trim | trim(string) | Remove leading/trailing whitespace | trim(" hi ") = "hi" |
| ltrim | ltrim(string) | Remove leading whitespace | ltrim(" hi") = "hi" |
| rtrim | rtrim(string) | Remove trailing whitespace | rtrim("hi ") = "hi" |
| replace | replace(string, search, replacement) | Replace all occurrences | replace("hello", "l", "r") = "herro" |
| split | split(string, delimiter) | Split string into list | split("a,b,c", ",") = ["a","b","c"] |
| substring | substring(string, start [, length]) | Extract substring | substring("hello", 1, 3) = "ell" |
| left | left(string, length) | First N characters | left("hello", 3) = "hel" |
| right | right(string, length) | Last N characters | right("hello", 3) = "llo" |
| reverse | reverse(string) | Reverse string | reverse("hello") = "olleh" |
| startsWith | n.prop STARTS WITH str | Check string prefix | n.name STARTS WITH "Al" |
| endsWith | n.prop ENDS WITH str | Check string suffix | n.name ENDS WITH "ce" |
| contains | n.prop CONTAINS str | Check substring presence | n.name CONTAINS "li" |
| size | size(string) | String length | size("hello") = 5 |
| toString | toString(value) | Convert value to string | toString(42) = "42" |
// 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
| Function | Signature | Description | Example |
|---|---|---|---|
| abs | abs(number) | Absolute value | abs(-5) = 5 |
| ceil | ceil(number) | Round up | ceil(2.3) = 3.0 |
| floor | floor(number) | Round down | floor(2.7) = 2.0 |
| round | round(number) | Round to nearest integer | round(2.5) = 3.0 |
| sqrt | sqrt(number) | Square root | sqrt(16) = 4.0 |
| log | log(number) | Natural logarithm | log(e()) = 1.0 |
| log10 | log10(number) | Base-10 logarithm | log10(100) = 2.0 |
| exp | exp(number) | e raised to power | exp(1) = 2.718... |
| sign | sign(number) | Sign (-1, 0, 1) | sign(-42) = -1 |
| rand | rand() | Random float [0, 1) | rand() |
| pi | pi() | Pi constant | pi() = 3.14159... |
| e | e() | Euler's number | e() = 2.71828... |
| sin | sin(radians) | Sine | sin(pi()/2) = 1.0 |
| cos | cos(radians) | Cosine | cos(0) = 1.0 |
| tan | tan(radians) | Tangent | tan(pi()/4) = 1.0 |
| asin | asin(number) | Arcsine | asin(1) = pi()/2 |
| acos | acos(number) | Arccosine | acos(1) = 0 |
| atan | atan(number) | Arctangent | atan(1) = pi()/4 |
| atan2 | atan2(y, x) | Two-argument arctangent | atan2(1, 1) = pi()/4 |
| degrees | degrees(radians) | Radians to degrees | degrees(pi()) = 180.0 |
| radians | radians(degrees) | Degrees to radians | radians(180) = pi() |
List Functions
| Function | Signature | Description | Example |
|---|---|---|---|
| range | range(start, end [, step]) | Generate integer list | range(0, 5) = [0,1,2,3,4,5] |
| size | size(list) | Number of elements | size([1,2,3]) = 3 |
| head | head(list) | First element | head([1,2,3]) = 1 |
| tail | tail(list) | All elements except first | tail([1,2,3]) = [2,3] |
| last | last(list) | Last element | last([1,2,3]) = 3 |
| reverse | reverse(list) | Reverse list order | reverse([1,2,3]) = [3,2,1] |
| sort | sort(list) | Sort ascending | sort([3,1,2]) = [1,2,3] |
| reduce | reduce(acc = init, x IN list | expr) | Fold list to single value | reduce(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] |
| keys | keys(node_or_map) | Property keys | keys(n) |
| labels | labels(node) | Node labels | labels(n) |
| nodes | nodes(path) | Nodes in a path | nodes(p) |
| relationships | relationships(path) | Relationships in a path | relationships(p) |
// 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
| Function | Signature | Description | Example |
|---|---|---|---|
| toInteger | toInteger(value) | Convert to integer | toInteger("42") = 42 |
| toFloat | toFloat(value) | Convert to float | toFloat("3.14") = 3.14 |
| toString | toString(value) | Convert to string | toString(42) = "42" |
| toBoolean | toBoolean(value) | Convert to boolean | toBoolean("true") = true |
| type | type(relationship) | Relationship type string | type(r) = "KNOWS" |
| labels | labels(node) | Node labels as list | labels(n) = ["Person"] |
| keys | keys(node_or_map) | Property keys as list | keys(n) = ["name", "age"] |
| properties | properties(node_or_rel) | All properties as map | properties(n) |
| id | id(node_or_rel) | Internal ID | id(n) |
Temporal Functions
| Function | Signature | Description | Example |
|---|---|---|---|
| timestamp | timestamp() | Current Unix timestamp (ms) | timestamp() |
| datetime | datetime([map]) | Create a datetime value | datetime({year: 2026, month: 4}) |
| date | date([map]) | Create a date value | date({year: 2026, month: 4, day: 2}) |
| localtime | localtime([map]) | Create a local time value | localtime({hour: 14, minute: 30}) |
| localdatetime | localdatetime([map]) | Create local datetime | localdatetime() |
| duration | duration(map) | Create a duration | duration({days: 7, hours: 3}) |
// 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.
// 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;
| Function | Signature | Description | Returns |
|---|---|---|---|
| EMBED | EMBED(text) | Convert text to a 384-dimensional vector | List of 384 floats |
| cosine_similarity | cosine_similarity(vec1, vec2) | Cosine similarity between two vectors | Float [-1.0, 1.0] |
| l2_distance | l2_distance(vec1, vec2) | Euclidean (L2) distance | Float >= 0 |
| dot_product | dot_product(vec1, vec2) | Dot product of two vectors | Float |
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.
SET GFQL_CONTEXT
Before executing GFQL operations, set a query context that defines the working scope (labels, edge types, or property filters).
// 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:
| Function | Description | Example |
|---|---|---|
| chain() | Start a GFQL operation chain | chain() |
| n() | Select nodes (optionally filtered) | n(label='Person') |
| e_forward() | Traverse outgoing edges | e_forward(type='CALLS') |
| e_reverse() | Traverse incoming edges | e_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') |
// 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:
| Operator | Description | Example |
|---|---|---|
| =, != | Equality / inequality | .filter(status = 'active') |
| >, <, >=, <= | Comparison | .filter(age >= 18) |
| AND, OR | Logical operators | .filter(age > 18 AND active = true) |
| NOT | Logical negation | .filter(NOT deleted) |
| IN | List membership | .filter(status IN ['active', 'pending']) |
| LIKE | Pattern matching (% wildcard) | .filter(name LIKE 'auth%') |
| IS NULL | Null check | .filter(email IS NULL) |
| IS NOT NULL | Not 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
| Version | Status | Notes |
|---|---|---|
| Bolt v3 | Supported | Minimum version, used by older drivers |
| Bolt v4.x | Supported | Multi-database messages accepted (single-db only) |
| Bolt v5.0-5.6 | Supported | Full feature support including notifications |
Bolt TLS is supported. Pass the --bolt-cert-file and --bolt-key-file flags to enable encrypted connections.
Driver Compatibility
| Driver | Language | Minimum Version | Status |
|---|---|---|---|
| neo4j-python-driver | Python | 5.0+ | Tested |
| neo4j-driver (npm) | JavaScript | 5.0+ | Tested |
| neo4j-java-driver | Java | 5.0+ | Tested |
| neo4j-go-driver | Go | 5.0+ | Tested |
| Neo4j.Driver (NuGet) | C# / .NET | 5.0+ | Tested |
| py2neo | Python | 2021.1+ | Compatible |
| neomodel | Python | 5.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.
# 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.
Protocol Feature Comparison
| Feature | Bolt (7687) | xrayProtocol (7689) |
|---|---|---|
| MATCH / CREATE / MERGE / DELETE | Yes | Yes |
| RETURN with result streaming | Yes | Yes (columnar, 24x faster) |
| Parameters ($param) | Yes | Yes |
| Transactions (BEGIN/COMMIT) | Yes | Yes |
| CALL...YIELD (stored procedures) | Yes | Not yet |
| EXPLAIN / PROFILE | Yes | Not yet |
| CREATE INDEX / CONSTRAINT | Yes | Not yet |
| GFQL queries | Yes | Yes |
| LZ4 compression | No | Yes |
| Query pipelining | No | Yes |
| Neo4j driver compatible | Yes | No (native client) |
| Best for | Full feature set, stored procedures, DDL | Bulk analytics, large result sets, data pipelines |
When to Use xrayProtocol
- Bulk data export (millions of rows) — 24x throughput advantage
- Analytics queries returning large result sets
- Server-to-server data pipelines
- GFQL queries for graph analytics
When to Use Bolt
- Stored procedures — all CALL...YIELD operations require Bolt
- DDL operations — CREATE INDEX, CREATE CONSTRAINT, SHOW INDEX INFO
- EXPLAIN / PROFILE — query plan analysis
- Application development with existing Neo4j drivers
- Interactive queries from browser tools
# xrayProtocol URI xray://localhost:7689
xrayProtocol Message Types
xrayProtocol uses a binary message format with the following core message types:
| Message | Direction | Description |
|---|---|---|
| HELLO | Client to Server | Initiate connection with auth credentials |
| WELCOME | Server to Client | Confirm authentication and protocol version |
| RUN | Client to Server | Execute a query with optional parameters |
| COLUMNS | Server to Client | Column metadata for the result set |
| CHUNK | Server to Client | Columnar data batch (Arrow-compatible) |
| DONE | Server to Client | Query complete with summary statistics |
| ERROR | Server to Client | Error response with code and message |
| GOODBYE | Either | Close the connection gracefully |
Connection Lifecycle
An xrayProtocol session follows this 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).
# 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
| Flag | Default | Description |
|---|---|---|
| --data-directory | /var/lib/xraygraphdb | Directory for snapshots and WAL files |
| --storage-gc-cycle-sec | 30 | Garbage collection interval in seconds |
| --storage-snapshot-interval-sec | 300 | Automatic snapshot interval (0 to disable) |
| --storage-snapshot-on-exit | true | Take snapshot on graceful shutdown |
| --storage-recover-on-startup | true | Load latest snapshot on startup |
| --storage-snapshot-retention-count | 3 | Number of snapshots to keep |
| --storage-wal-enabled | true | Enable write-ahead logging |
Network Flags
| Flag | Default | Description |
|---|---|---|
| --bolt-port | 7687 | Port for the Bolt protocol |
| --bolt-address | 0.0.0.0 | Bind 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-port | 7689 | Port for xrayProtocol |
| --xray-address | 0.0.0.0 | Bind address for xrayProtocol |
Snapshot & Persistence Flags
| Flag | Default | Description |
|---|---|---|
| --storage-snapshot-interval-sec | 300 | Seconds between automatic snapshots (0 = manual only) |
| --storage-snapshot-on-exit | true | Create snapshot during graceful shutdown |
| --storage-snapshot-retention-count | 3 | How many snapshot files to retain on disk |
| --storage-recover-on-startup | true | Automatically load latest snapshot on start |
| --storage-wal-enabled | true | Write-ahead log for crash recovery between snapshots |
| --storage-wal-file-size-kib | 20480 | Max WAL file size before rotation (KiB) |
Performance Flags
| Flag | Default | Description |
|---|---|---|
| --query-execution-timeout-sec | 180 | Max execution time per query (0 = no limit) |
| --plan-cache-size | 1024 | Number of compiled query plans to cache |
| --bolt-session-inactivity-timeout | 1800 | Idle session timeout in seconds |
| --storage-parallel-index-recovery | true | Rebuild indexes in parallel during recovery |
| --storage-parallel-schema-recovery | true | Rebuild schemas in parallel during recovery |
Auth Flags
| Flag | Default | Description |
|---|---|---|
| --auth-enabled | true | Enable authentication (set false for local dev only) |
| --auth-password-strength | 8 | Minimum password length |
| --auth-module-timeout-ms | 10000 | Timeout for external auth module calls |
--auth-enabled=false flag is for isolated development environments only.
License Flags
| Flag | Default | Description |
|---|---|---|
| --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:
- Automatic interval (
--storage-snapshot-interval-sec, default 300s) - Graceful shutdown (if
--storage-snapshot-on-exit=true) - Manual trigger via admin CLI
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.
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
/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
- Each node: ~100-300 bytes base + property values
- Each relationship: ~100-200 bytes base + property values
- Each index entry: ~50-100 bytes
- String properties: string length + 32 bytes overhead
- Vector properties (384-dim): ~1.5 KB per vector
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
# 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.
// 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;
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.
// 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;
| Permission | Scope | Description |
|---|---|---|
| READ | LABELS, EDGE_TYPES | Read nodes/relationships of specified types |
| WRITE | LABELS, EDGE_TYPES | Create/update/delete specified types |
| CREATE | LABELS | Create nodes of specified labels |
| DELETE | LABELS | Delete nodes of specified labels |
| INDEX | GLOBAL | Create and drop indexes |
| AUTH | GLOBAL | Manage 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.
| Command | Description | Requires Running Server |
|---|---|---|
| --validate-license <file> | Verify license key signature, expiry, and organization | No |
| --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 hashes | No |
| --repair --auth-token=<epoch> | Attempt automatic repair of corrupted snapshot segments | No |
# 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
--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)
# 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)
# 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
# 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
./bin/xraygraphdb-wrapper \ --license-key=/path/to/license.json \ --organization-name="Your Company"
Validate a License Offline
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
{
"organization": "Your Company",
"issued": "2026-04-01T00:00:00Z",
"expires": "2027-04-01T00:00:00Z",
"features": ["ha", "encryption", "rbac"],
"signature": "<ed25519-signature>"
}
Supported Neo4j Syntax
xrayGraphDB supports the openCypher standard plus commonly-used Neo4j extensions. The following table summarizes compatibility.
| Feature | Status | Notes |
|---|---|---|
| MATCH / RETURN / WHERE | Full | openCypher standard |
| CREATE / MERGE / SET / REMOVE / DELETE | Full | openCypher standard |
| Variable-length paths | Full | Including shortestPath and allShortestPaths |
| Aggregation functions | Full | count, sum, avg, min, max, collect, percentile, stDev |
| List comprehensions | Full | Including reduce, filter, map |
| Pattern comprehensions | Full | [(n)-->(m) | m.name] |
| CASE expressions | Full | Simple and generic CASE |
| Named indexes (CREATE INDEX name FOR ...) | Full | Neo4j 4.x+ syntax |
| SHOW INDEX INFO | Full | |
| SHOW CONSTRAINT INFO | Full | |
| Explicit transactions (BEGIN/COMMIT/ROLLBACK) | Full | |
| EXPLAIN / PROFILE | Full | Query plan inspection |
| CALL procedures | Partial | Built-in procedures only; no APOC |
| Multi-database | Not supported | Single database per instance |
| Subqueries (CALL { ... }) | Not supported | Use WITH for query chaining |
| LOAD CSV | Not supported | Use 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
| Scheme | Behavior |
|---|---|
| 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 |
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.
# 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.
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
- No APOC: APOC procedures are not available. Replace with equivalent Cypher or driver-side logic.
- No multi-database: xrayGraphDB runs a single database per instance.
- No subqueries: Replace
CALL { ... }withWITHchaining. - No LOAD CSV: Import data using driver scripts or bulk loaders.
- Internal IDs: Node and relationship IDs may differ. Never rely on internal IDs for application logic.