Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 48 additions & 25 deletions benchmarks/technical-report.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Won 48/48 queries vs Neo4j Won 45/47 queries vs Memgraph

And remember:

All of this is running over plain JSON/HTTP for now, while competitors used Bolt (binary protocol), which actually makes the benchmark conservative in our case.
These runs queried TuringDB through the Python client it shipped at the time, which exchanged JSON with the server, while competitors used Bolt (a binary protocol). That makes the benchmark conservative in our case. Since version 3.1, TuringDB's clients use its own binary protocol.

We’re also running completely out-of-the-box with no manual tuning, no custom configuration, no indexes on our side.

Expand Down Expand Up @@ -133,7 +133,7 @@ All databases were run locally on the same machine, one at a time, to avoid reso

### Client Protocol

TuringDB is queried over **HTTP** via its Python client, while Neo4j and Memgraph are queried over the **Bolt** binary protocol. Bolt is a more efficient wire protocol than HTTP for database communication, meaning TuringDB’s measured times include higher protocol overhead. This makes the benchmark conservative in TuringDB’s favor — with an equivalent binary protocol, TuringDB’s results would be even faster.
These results were measured with an earlier TuringDB Python client, which exchanged JSON with the server, while Neo4j and Memgraph are queried over the **Bolt** binary protocol. JSON costs more to encode and decode than a binary wire protocol, meaning TuringDB’s measured times include higher protocol overhead. This makes the benchmark conservative in TuringDB’s favor. Since version 3.1, TuringDB’s clients use its own binary protocol.

---

Expand Down Expand Up @@ -598,7 +598,7 @@ The following **2 queries** (4% of benchmark) show a competitor outperforming Tu
- **Single-machine benchmark.** All engines ran locally; distributed deployment characteristics are not measured.
- **Cold-run only.** Results reflect first-execution performance. Workloads with repeated identical queries would benefit from Neo4j/Memgraph’s query caching.
- **Import-provided configuration.** Neo4j and Memgraph use the indexes and constraints from the original dump, and Memgraph runs in in-memory analytical mode. No additional tuning was applied. Production deployments may include further optimizations.
- **Client-side timing.** Measurements include Python client overhead and network round-trip (localhost). TuringDB uses HTTP while Neo4j and Memgraph use the more efficient Bolt protocol, giving the competitors a wire-protocol advantage.
- **Client-side timing.** Measurements include Python client overhead and network round-trip (localhost). At the time of these runs TuringDB's client exchanged JSON with the server, while Neo4j and Memgraph use the more efficient Bolt protocol, giving the competitors a wire-protocol advantage.
- **Two datasets.** Results may not generalize to all graph structures or query patterns. Additional datasets would strengthen conclusions.
- **No concurrent load.** The benchmark runs queries sequentially with no simulated concurrent users.

Expand Down Expand Up @@ -654,10 +654,13 @@ The benchmark script generates a timestamped report and optionally updates the R
### Property-Based Filtering

```
MATCH (p:Person {name: 'John'})-[:PARTY_TO]->(c:Crime) RETURN p, c
MATCH (p:Person {name: 'John'})-[:PARTY_TO]->(c:Crime)
RETURN p, c
MATCH (p:Person {surname: 'Smith'})-[r]->(n) RETURN p
MATCH (p:Person)-[r]->(n) WHERE p.surname = 'Smith' RETURN p
MATCH (p1:Person)-[:PARTY_TO]->(c:Crime)<-[:PARTY_TO]-(p2:Person) WHERE p1 <> p2 RETURN p1.name, p2.name, c.type
MATCH (p1:Person)-[:PARTY_TO]->(c:Crime)
<-[:PARTY_TO]-(p2:Person)
WHERE p1 <> p2 RETURN p1.name, p2.name, c.type
```

### Aggregations
Expand All @@ -671,8 +674,10 @@ The benchmark script generates a timestamped report and optionally updates the R
### Relationship Type Traversal

```
MATCH (p:Person)-[:PARTY_TO]->(c:Crime) RETURN p.name, p.surname, c.type
MATCH (p1:Person)-[:KNOWS]->(p2:Person)-[:PARTY_TO]->(c:Crime) RETURN p1.name, p2.name
MATCH (p:Person)-[:PARTY_TO]->(c:Crime)
RETURN p.name, p.surname, c.type
MATCH (p1:Person)-[:KNOWS]->(p2:Person)
-[:PARTY_TO]->(c:Crime) RETURN p1.name, p2.name
MATCH (c:Crime)-[:OCCURRED_AT]->(l:Location) RETURN l.postcode
```

Expand All @@ -689,29 +694,45 @@ The benchmark script generates a timestamped report and optionally updates the R
### Property-Based Filtering

```
MATCH (n{displayName:"Autophagy"}) RETURN n
MATCH (n{displayName:"Autophagy"})-->(m) RETURN m
MATCH (n{displayName:"APOE-4 [extracellular region]"}) RETURN n
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m) RETURN m
MATCH (n{displayName:"Autophagy"})
RETURN n
MATCH (n{displayName:"Autophagy"})
-->(m) RETURN m
MATCH (n{displayName:"APOE-4 [extracellular region]"})
RETURN n
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m) RETURN m
MATCH (n:DatabaseObject{isChimeric:false}) RETURN n
MATCH (n:DatabaseObject{isChimeric:true}) RETURN n
```

### Multi-Hop Traversals

```
MATCH (n{displayName:"Autophagy"})-->(m)-->(p) RETURN p
MATCH (n{displayName:"Autophagy"})-->(m)-->(p)-->(q) RETURN q
MATCH (n{displayName:"Autophagy"})-->(m)-->(p)-->(q)-->(r) RETURN r
MATCH (n{displayName:"Autophagy"})-->(m)-->(p)-->(q)-->(r)-->(s) RETURN s
MATCH (n{displayName:"Autophagy"})-->(m)-->(p)-->(q)-->(r)-->(s)-->(t) RETURN t
MATCH (n{displayName:"Autophagy"})-->(m)-->(p)-->(q)-->(r)-->(s)-->(t)-->(v) RETURN v
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m)-->(p) RETURN p
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m)-->(p)-->(q) RETURN q
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m)-->(p)-->(q)-->(r) RETURN r
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m)-->(p)-->(q)-->(r)-->(s) RETURN s
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m)-->(p)-->(q)-->(r)-->(s)-->(t) RETURN t
MATCH (n{displayName:"APOE-4 [extracellular region]"})-->(m)-->(p)-->(q)-->(r)-->(s)-->(t)-->(v) RETURN v
MATCH (n{displayName:"Autophagy"})
-->(m)-->(p) RETURN p
MATCH (n{displayName:"Autophagy"})
-->(m)-->(p)-->(q) RETURN q
MATCH (n{displayName:"Autophagy"})
-->(m)-->(p)-->(q)-->(r) RETURN r
MATCH (n{displayName:"Autophagy"})
-->(m)-->(p)-->(q)-->(r)-->(s) RETURN s
MATCH (n{displayName:"Autophagy"})
-->(m)-->(p)-->(q)-->(r)-->(s)-->(t) RETURN t
MATCH (n{displayName:"Autophagy"})
-->(m)-->(p)-->(q)-->(r)-->(s)-->(t)-->(v) RETURN v
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m)-->(p) RETURN p
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m)-->(p)-->(q) RETURN q
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m)-->(p)-->(q)-->(r) RETURN r
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m)-->(p)-->(q)-->(r)-->(s) RETURN s
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m)-->(p)-->(q)-->(r)-->(s)-->(t) RETURN t
MATCH (n{displayName:"APOE-4 [extracellular region]"})
-->(m)-->(p)-->(q)-->(r)-->(s)-->(t)-->(v) RETURN v
```

### Relationship Type Traversal
Expand All @@ -721,8 +742,10 @@ The benchmark script generates a timestamped report and optionally updates the R
MATCH (n)-[e:interactor]->(m) RETURN n,m
MATCH (n)-[e:surroundedBy]->(m) RETURN n,m
MATCH (n)-[:hasEvent]->(m) RETURN n,m
MATCH (n:Pathway)-[:hasEvent]->(m:ReactionLikeEvent) RETURN n,m
MATCH (r:ReactionLikeEvent)-[:output]->(s:PhysicalEntity) RETURN r,s
MATCH (n:Pathway)-[:hasEvent]->(m:ReactionLikeEvent)
RETURN n,m
MATCH (r:ReactionLikeEvent)-[:output]->(s:PhysicalEntity)
RETURN r,s
```

### Complex Patterns
Expand Down
2 changes: 1 addition & 1 deletion claude-skill.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The skill is structured so Claude only loads the reference it needs:

| Module | Covers |
| --- | --- |
| `startup` | Installation, starting the server, connecting (HTTP, authentication, embedded), loading a graph |
| `startup` | Installation, starting the server, connecting (server, authentication, embedded), loading a graph |
| `querying` | `MATCH`, `OPTIONAL MATCH`, label expressions, paths, `WITH`, `NEXT`, `WHEN`, aggregation, `UNWIND`, maps, subqueries, built-in functions, parameters, `LOAD CSV` |
| `writing` | `CREATE`, `MERGE`, `SET`, `DELETE`, `FOREACH`, bulk edges by node ID, the change and commit workflow |
| `importing` | CSV, Parquet, JSONL, GML and Neo4j imports |
Expand Down
50 changes: 25 additions & 25 deletions commands.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "CLI Commands"
sidebarTitle: "Commands"
description: "Reference for TuringDB CLI commands (start, UI and stop), the interactive shell, environment variables and the HTTP API."
description: "Reference for TuringDB CLI commands (start, UI and stop), the interactive shell, environment variables and how clients connect."
---

## Start
Expand All @@ -17,8 +17,8 @@ turingdb start [options]
| Flag | Description | Default |
|---|---|---|
| `-demon` | Run TuringDB as a background daemon | off |
| `-p <port>` | HTTP server listen port | `6666` |
| `-i <addr>` | HTTP server listen address | `127.0.0.1` |
| `-p <port>` | Server listen port | `6666` |
| `-i <addr>` | Server listen address | `127.0.0.1` |
| `-turing-dir <path>` | Root Turing home directory | `~/.turing` |
| `-load <graph>` | Load a graph at startup (can be repeated) | |
| `-in-memory` | Run without writing graphs to disk | off |
Expand Down Expand Up @@ -62,10 +62,10 @@ Require authentication:
TURINGDB_AUTH_TOKEN=s3cret turingdb start -demon -auth-on
```

Serve the binary protocol instead of JSON (see [Environment variables](#environment-variables)):
Serve a Node.js application, whose `fetch` refuses the default port (see [Connecting clients](#connecting-clients)):

```bash
USE_TURING_PROTO=1 turingdb start -demon
turingdb start -demon -p 6686
```

<Note>
Expand All @@ -74,15 +74,16 @@ USE_TURING_PROTO=1 turingdb start -demon

### Interactive shell

Without `-demon`, `turingdb start` serves HTTP **and** opens an interactive Cypher shell. Besides Cypher queries and [engine commands](/query/procedures#commands), the shell has a few local commands:
Without `-demon`, `turingdb start` serves clients **and** opens an interactive Cypher shell. Besides Cypher queries and [engine commands](/query/procedures#commands), the shell has a few local commands:

| Command | Description |
|---|---|
| `cd <graph>` | Switch the active graph |
| `checkout change-<id>` | Work on a change. `checkout <commit>` opens a past commit, and `checkout` returns to the latest commit of main |
| `read <file>` | Run the queries in a file |
| `:param <name> => <value>` | Set a [query parameter](/query/cypher_subset#query-parameters), with the value written as JSON: `:param label => "Person"`. `:param clear` removes them all |
| `SHOW PARAMETERS` | List the parameters set in the shell |
| `:param <name> => <value>` | Set a [query parameter](/query/cypher_subset#query-parameters), with the value written as a Cypher expression: `:param label => 'Person'`, `:param ids => [0, 1, 2]`, `:param since => datetime('2024-01-01T00:00:00Z')`. `:param clear` removes them all |
| `SHOW PARAMETERS` | List the parameters set in the shell, with their types |
| `connect <addr> <port>` | Send the next queries to another TuringDB server, for example `connect 127.0.0.1 6666`. `disconnect` returns to the local engine |
| `help` | List the available commands |
| `quit` / `exit` | Leave the shell |

Expand Down Expand Up @@ -156,25 +157,24 @@ turingdb stop -turing-dir /data/myturing

| Variable | Read by | Effect |
|---|---|---|
| `USE_TURING_PROTO` | server | `1` makes the server speak TuringDB's **binary protocol** instead of JSON over HTTP. The server then serves the binary protocol only: connect with `TuringDB(type="native")`, and use a server in the default mode for `curl`, the JSON client and the visualizer. See [client backends](/pythonsdk/reference#client-backends) |
| `TURINGDB_AUTH_TOKEN` | server and Python SDK | With `-auth-on`, the token the server requires. The SDK sends it as a bearer token when set |
| `TURINGDB_TYPE` | Python SDK | Default client backend (`json`, `native` or `embedded`) when `type` is not passed |
| `TURINGDB_AUTH_TOKEN` | server and Python SDK | With `-auth-on`, the token the server requires. The Python SDK sends it as a bearer token when set. The JavaScript SDK takes the token as its `authToken` option |

---

## HTTP API
## Connecting clients

The Python SDK is a thin client over a single endpoint, so you can query a server from any language. With the default (JSON) server, send the query as a JSON object:
The server speaks TuringDB's binary protocol, on port 6666 unless you pass `-p`. Query it with one of these clients:

```bash
curl -s -X POST 'http://localhost:6666/query?graph=mygraph' \
-d '{"query": "MATCH (n:$label) RETURN count(n)", "parameters": {"label": "Person"}}'
```

- The request body is a JSON object with a `query` string and an optional `parameters` object (see [query parameters](/query/cypher_subset#query-parameters)).
- The optional query-string parameters `change=<change id>` and `commit=<hash>` target a change or a past commit. The change ID is the decimal number that `CHANGE NEW` returns.
- With `-auth-on`, send `Authorization: Bearer <token>`. A missing or incorrect token returns HTTP `401` with an empty body.
- A request, headers included, can be up to 1 MiB. A larger one returns HTTP `413` with the code `REQUEST_TOO_BIG`.
- A successful response is `{"header": {"column_names": [...], "column_types": [...]}, "data": [chunk, ...], "time": <ms>}`, where each chunk is a list of columns. Doubles are written with full precision.
- An error response is `{"error": "PARSE_ERROR", "error_details": "...", "time": <ms>}`. The code is one of `PARSE_ERROR`, `ANALYZE_ERROR`, `PLAN_ERROR`, `EXEC_ERROR`, `GRAPH_NOT_FOUND`, `CHANGE_NOT_FOUND`, `COMMIT_NOT_FOUND` or `REQUEST_TOO_BIG`, and the details name the graph, change or commit that was not found.
- Queries on a non-HEAD commit need `LOAD COMMIT '<hash>'` first when you talk to the API directly. The CLI and the SDK do this for you.
| Client | Install | Connect |
|---|---|---|
| [Python SDK](/pythonsdk/reference) | `pip install turingdb` | `TuringDB(host="localhost", port=6666)` |
| [JavaScript SDK](/jssdk/reference) | `npm install turingdb` | `new TuringClient({ url: "http://localhost:6686/query" })` |
| Interactive shell | ships with the server | `turingdb` without `-demon`, or `connect <addr> <port>` from another shell |
| [Visualizer](#ui) | ships with the server | `turingdb -ui` |

- The protocol runs over HTTP/1.1: each query is a request to `/query`, and its result streams back as binary packets, one per HTTP chunk, on a connection kept open for the next query. Standard reverse proxies such as nginx forward it with a plain `proxy_pass`.
- Each query carries the graph, change and commit it runs on, so the server keeps no session for a client.
- A request can be up to 1 MiB, the query text and its parameters together. A larger one is refused with `The request is over the server limit of 1048576 bytes for the request line, headers and body together`. Split large batches, or load bulk data from a file with [`LOAD PARQUET`](/import_data/parquet) or [`LOAD JSONL`](/import_data/jsonl).
- With `-auth-on`, every request needs the token. A missing or wrong one is refused with `Unauthorized: missing or invalid authentication token`.
- Browsers and the `fetch` of Node.js refuse port 6666. For Node.js, start the server on another port, such as `-p 6686`. A web page reaches the server through a proxy on its own origin, see [In the browser](/jssdk/get_started#in-the-browser).
- Upgrade the clients and the server together: a 3.1 client speaks the protocol of a 3.1 server.
3 changes: 2 additions & 1 deletion concepts/columnar_storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ This model was designed for early relational systems and doesn't fit high-perfor
### TuringDB’s Columnar Streaming Model

```
[Column of Nodes] → [Filter Column by Age] → [Filter Column by Location] → [Column of Edges]
[Column of Nodes] → [Filter Column by Age]
→ [Filter Column by Location] → [Column of Edges]
```

- Works on **columns of nodes or edges** (stored in DataParts)
Expand Down
14 changes: 14 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@
"pythonsdk/reference"
]
},
{
"group": "JavaScript SDK",
"pages": [
"jssdk/get_started",
"jssdk/reference"
]
},
{
"group": "Release Notes",
"pages": [
"releases/overview",
"releases/v3-1"
]
},
{
"group": "Troubleshooting",
"pages": [
Expand Down
6 changes: 2 additions & 4 deletions graph_dev/create_graph.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ description: "Create a TuringDB graph"
```python
from turingdb import TuringDB

# Create TuringDB client
# set host parameter to the URL (as string) on which TuringDB is running,
# default "http://localhost:6666"
client = TuringDB(host="http://localhost:6666")
# Connect to the TuringDB server (default: localhost:6666)
client = TuringDB(host="localhost", port=6666)

# Create a new graph called my_graph
client.create_graph("mygraph")
Expand Down
7 changes: 5 additions & 2 deletions graph_dev/create_nodes_edges.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ description: "Once you have a valid change, you can start adding new nodes and e
// You need to commit the changes after node creation
COMMIT

// Match Jane and John nodes to create edge of label KNOWS between them
// Match Jane and John, then create a KNOWS edge between them
MATCH (n:Person {name: 'Jane'}), (m:Person {name: 'John'})
CREATE (n)-[e:KNOWS]->(m)

Expand All @@ -39,7 +39,10 @@ description: "Once you have a valid change, you can start adding new nodes and e
<Tab title="Python SDK">
```python
# Create a node Person (Jane) - Edge (knows) - node Person (John)
client.query("CREATE (:Person {name: 'Jane'})-[:KNOWS]->(:Person {name: 'John'})")
client.query(
"CREATE (:Person {name: 'Jane'})"
"-[:KNOWS]->(:Person {name: 'John'})"
)

# Commit the changes
client.query("COMMIT")
Expand Down
16 changes: 10 additions & 6 deletions graph_dev/graph_examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ description: "Complete example on how to create and query a graph from scratch i
```python
from turingdb import TuringDB

# Create TuringDB client
# set host parameter to the URL (as string) on which TuringDB is running,
# default "http://localhost:6666"
client = TuringDB(host="http://localhost:6666")
# Connect to the TuringDB server (default: localhost:6666)
client = TuringDB(host="localhost", port=6666)

# Create a new graph called my_graph
client.create_graph("mygraph")
Expand All @@ -25,7 +23,10 @@ change = client.new_change()
client.checkout(change=change)

# Create a node Person (Jane) - Edge (knows) - node (John)
client.query("CREATE (n:Person {name: 'Jane'})-[e:KNOWS]->(m:Person {name: 'John'})")
client.query(
"CREATE (n:Person {name: 'Jane'})"
"-[e:KNOWS]->(m:Person {name: 'John'})"
)

# Commit the change
client.query("COMMIT")
Expand All @@ -35,6 +36,9 @@ client.query("CHANGE SUBMIT")
client.checkout()

# Query graph
df = client.query("MATCH (n:Person {name:'Jane'})-[e:KNOWS]-(m:Person {name:'John'}) RETURN n, e, m")
df = client.query(
"MATCH (n:Person {name:'Jane'})-[e:KNOWS]-"
"(m:Person {name:'John'}) RETURN n, e, m"
)
print(df)
```
Loading
Loading