A single page for every query feature and operational capability
LoraDB does not support today, so you can decide whether LoraDB fits
your use case and know what to reach for instead. Every unsupported
feature below raises a clear error (a parse error,
SemanticError::UnsupportedFeature, or UnknownFunction) —
nothing silently misbehaves.
Wording convention used below:
- Not supported — hard absence with no near-term plan.
- Not yet supported — absence the docs intentionally signal as
future capability.
- Not implemented — the grammar accepts it but the analyzer or
executor rejects it today.
For the machine-checkable feature list see the
Cypher support matrix
in the internal documentation.
| Theme | Biggest gaps |
|---|
| Storage | WAL-backed open exists on Rust, Node, Python, Go, Ruby, and lora-server; WASM stays snapshot-only; explicit RANGE/TEXT/POINT/LOOKUP/VECTOR/FULLTEXT indexes and schema constraints exist; HNSW vector search is opt-in |
| Concurrency | Snapshot reads can overlap; write commits and explicit read-write transactions serialize; timeout coverage is API-dependent |
| Clauses | FOREACH is supported for updating clauses; no general-purpose CALL or LOAD CSV |
| Patterns | No quantified path patterns |
| Operators | No BETWEEN; cross-type comparisons return null |
| Aggregates | No GROUP BY / HAVING keywords |
| Functions | No external utility compatibility layer; case conversion is Unicode-aware but not locale-specific |
| Parameters | No dynamic labels / relationship types; no parse-time type check |
| Spatial | No WKT I/O, no CRS transforms; geo.within_bbox exists for same-SRID boxes |
| Vectors | VECTOR indexes are exact (flat scan) by default, approximate with vector.indexProvider: 'hnsw'; no embedding generation; no list-of-vectors properties |
| Browser playground | Params are browser-local tab state; no shared hosted database, no true query abort, and no remote import |
| Feature | Status |
|---|
General-purpose CALL | Not supported — analyzer rejects ordinary procedures such as CALL db.labels() |
Index procedure CALL | Supported for db.index.vector.queryNodes, db.index.vector.queryRelationships, db.index.fulltext.queryNodes, and db.index.fulltext.queryRelationships |
FOREACH | Supported for updating clauses (CREATE, MERGE, SET, REMOVE, DELETE, and nested FOREACH) |
CREATE INDEX / DROP INDEX / SHOW INDEXES | Supported for RANGE, TEXT, POINT, LOOKUP, VECTOR, and FULLTEXT indexes; see Indexes |
CREATE CONSTRAINT / DROP CONSTRAINT / SHOW CONSTRAINTS | Supported for uniqueness, existence, node key, relationship key, and property type constraints; see Constraints |
LOAD CSV | Not supported |
USE <graph> (multi-database) | Not supported |
EXPLAIN / PROFILE Cypher keywords | Not supported — use the explicit binding methods (db.explain, db.profile, Explain, Profile) or HTTP /explain and /profile endpoints |
| Feature | Status |
|---|
Quantified path patterns ((:X)-[:R]->(:Y)){1,3} | Not supported |
Inline WHERE inside variable-length relationships | Not supported — rejected at parse time |
| Feature | Status |
|---|
BETWEEN a AND b | Not supported — use x >= a AND x <= b |
| Type-mismatch detection in comparisons | Not implemented — cross-type < / > returns null instead of erroring |
Aggregates inside WHERE | Not supported — use WITH … WHERE instead |
| Feature | Status |
|---|
DISTINCT on stdev, stdevp, percentileCont, percentileDisc | Not supported — the DISTINCT is silently ignored; use collect(DISTINCT x) and aggregate the unwound list |
GROUP BY keyword | Not supported — non-aggregated columns are the implicit group key |
HAVING keyword | Not supported — filter post-aggregate through WITH … WHERE |
| Feature | Status |
|---|
| External utility compatibility layer | Not supported |
| User-defined functions | Not supported — no registration surface |
| Truncation units | Every Cypher unit, from "millennium" down to "microsecond". temporal.truncate(unit, value) returns null for a unit the value's type does not have; date.truncate(…) and the other <type>.truncate forms fail instead |
<type>.transaction(), .statement(), .realtime() | Supported; LoraDB has one clock, so the three agree. They take no time zone argument |
| Named time zones | Supported for DATETIME, with daylight saving from the built-in IANA database. A TIME in a named zone takes the zone's current offset. A DATETIME stored with a named zone cannot be read by LoraDB versions before 0.19.0 |
string.lower / string.upper | Unicode case mapping is supported; locale-specific case folding is not |
string.normalize(str[, form]) | Unicode NFC/NFD/NFKC/NFKD normalization is supported; locale-specific transliteration is not |
| Feature | Status |
|---|
| Binary / byte arrays | Supported as a byte-string property value through binding wire formats; there is no Cypher byte literal |
| Fixed-precision decimals | Not supported — use scaled integers or strings |
| User-defined types | Not supported |
| Numeric overflow guarding | Integer +, -, *, % and unary - fail the query on overflow (LORA_VALIDATION); list.sum over integers still wraps |
| Very large or deeply nested values | Bounded; see Query limits |
| Feature | Status |
|---|
WGS-84 3D geo.distance honouring height | Not yet supported — computes surface great-circle only |
geo.within_bbox() | Supported for same-SRID 2D/3D bounding boxes, including WGS-84 boxes across the antimeridian (lower-left longitude greater than upper-right, as in Neo4j's point.withinBBox); mixed SRIDs (including 2D vs 3D) fail the query |
point.fromWKT() / WKT output | Not yet supported |
| CRS transformation between SRIDs | Not yet supported — cross-SRID geo.distance fails the query |
| Custom SRIDs | Not supported — only 7203, 9157, 4326, 4979 |
| Feature | Status |
|---|
| Vector ANN structure | Opt-in per index with vector.indexProvider: 'hnsw' (tunable vector.hnsw.* keys); the default flat provider scans every matching entity. See Indexes → Providers |
| Built-in embedding generation | Not supported — no plugin surface; generate embeddings in host code |
| List-of-vectors as a property | Not supported — rejected at write time; hang many embeddings off separate nodes |
| Dimension > 4096 | Not supported — rejected at construction time |
ORDER BY on a VECTOR column | Deterministic but implementation-defined; order by a scalar score instead |
| Metric extensions (e.g. Minkowski, Chebyshev) | Not yet supported — current metrics are listed in Vectors → Signed distance metrics |
Passing a typed VECTOR helper over HTTP | HTTP accepts JSON params, not host-language helper objects — pass a numeric list and cast it with $q::VECTOR<COORD>(DIM) |
| Feature | Status |
|---|
| Parameter as a label or relationship type | Not supported |
| Parameter as a property key | Not supported |
| Parameter type checking at parse time | Not yet supported |
| Parameters over HTTP | Supported for JSON scalar, list, and map values on /query, /explain, and /profile; use query casts for typed values such as DATE, POINT, and VECTOR |
| Gap | Impact |
|---|
| WAL controls are not uniform across bindings | Rust and lora-server expose the full WAL surface. Node exposes archive/raw WAL opens plus sync-mode control. Python, Go, and Ruby expose archive/raw WAL opens with managed snapshot options. WASM remains snapshot-only. |
| Time-based checkpoint scheduler — not yet supported | Explicit WAL helpers can write managed snapshots after N committed transactions, and Rust / lora-server expose explicit checkpoints. Nothing schedules checkpoints by wall-clock time in the background for you. |
| Constraint coverage is scoped | Uniqueness, existence, node key, relationship key, and property type constraints exist for a label or relationship type. There is no database-wide uniqueness constraint, and existence constraints are single-property only. |
| Index coverage is scoped | CREATE INDEX, CREATE TEXT INDEX, CREATE POINT INDEX, CREATE VECTOR INDEX, CREATE FULLTEXT INDEX, DROP INDEX, and SHOW INDEXES exist. Composite RANGE indexes are cataloged, but current planner rewrites are single-property. Vector indexes are exact flat scans unless created with the 'hnsw' provider. |
| Explicit transactions are surface-dependent | Rust and in-process bindings expose transaction APIs; HTTP has no multi-query transaction endpoint |
| ID reuse — not supported | Deleting an entity does not free its u64 id |
Every stage of the engine walks queries and values recursively, so a
query is bounded in shape and in the size of the values it builds. A
query over a limit fails with an error instead of exhausting the stack
or memory of the process it runs in.
| Limit | Value | Error |
|---|
Nesting of brackets, parentheses, braces, CASE expressions and LIST<...> types in query text | 64 levels | LORA_PARSE |
Depth of an operator chain: each operator in a + b + c, each NOT, comparison, sign and postfix lookup (.key, [i], ::TYPE) adds a level, summed through nested expressions | 256 levels | LORA_PARSE |
Clauses in one query, counting the clauses of CALL { ... } bodies; each UNION branch counts separately | 256 | LORA_PARSE |
Hops named by a variable-length relationship (-[*1..n]->); without an upper bound a pattern stops at 100 hops | 1000 | LORA_PARSE |
| Nesting of lists and maps in any value: parameters, values a query builds, stored properties | 128 levels | LORA_INVALID_PARAMS for a parameter, otherwise LORA_VALIDATION |
Size of a single value: a string or byte array, or a list or map counted as 8 bytes per element plus the strings it holds; also the properties one CREATE writes for one node or relationship | 128 MiB | LORA_INVALID_PARAMS for a parameter, otherwise LORA_VALIDATION |
Paths one variable-length expansion holds at once when a query has no LIMIT to stream against | 128 MiB of relationship ids | LORA_VALIDATION |
| Length of a regular expression | 16 KiB | LORA_VALIDATION |
| Compiled size of a regular expression (the regex engine's default) | 10 MiB | LORA_VALIDATION |
Character pairs text.distance and text.similarity compare (the product of both lengths, except for 'hamming') | 2^28 | LORA_VALIDATION |
Queries do not need a large thread stack. When a deep query runs low on
the stack of the thread that called the engine, the engine continues on a
separately allocated stack segment instead of overflowing, so any thread
works: a 128 KiB musl thread, a 512 KiB macOS secondary thread, a Ruby or
Go thread. On a thread with less than about 512 KiB of stack, each query,
and each row pulled from a stream, allocates such a segment, which adds a
few microseconds. WebAssembly builds cannot switch stacks; there, a query
at every limit at once fits the 1 MiB stack a wasm build gets.
Staying well inside the limits is still better. For example, a long chain
of OR tests is better written with IN:
// 300 alternatives is past the operator-chain limit:
// MATCH (n) WHERE n.id = 1 OR n.id = 2 OR ... OR n.id = 300 RETURN n
// One list membership test is a single operator:
MATCH (n) WHERE n.id IN $ids RETURN n
The long chain fails with code LORA_PARSE and the message:
parse error at 16..4105: expression nests more than 256 operators deep; split it up, for example with WITH or a list function
A value nested too deeply is refused while it is built:
RETURN reduce(acc = [], i IN range(1, 200) | [acc]) AS deep
fails with code LORA_VALIDATION and the message:
value nests lists and maps more than 128 levels deep
- Auto-commit read-only queries load Arc snapshots and can overlap.
- Write commits serialize, and explicit read-write transactions hold the
writer slot until commit or rollback.
- Query timeouts are cooperative and not exposed uniformly across every
binding or HTTP endpoint.
- HTTP rate limiting — not supported.
- Authentication — not supported. TLS — not supported. Bind to
127.0.0.1 only in production until this changes; see the
security notes on the Contact page.
- Admin endpoints (
POST /admin/snapshot/save
and /admin/snapshot/load, plus /admin/checkpoint,
/admin/wal/status, and /admin/wal/truncate when WAL is enabled)
are opt-in via --snapshot-path / --wal-dir and have no
authentication. The optional path body field is rejected unless
the server runs with --admin-path-root, and must then stay inside
that directory. Do not enable them on a network-reachable host
without authenticated ingress in front.
- HTTP results are built in memory before they are sent.
--max-result-rows (default 1,000,000) and --max-response-bytes
(default 256 MiB) cap how large one can get, and --query-timeout-ms
(default 30 s) bounds how long a query can run. Page through larger
results with SKIP and LIMIT.
- Multi-query transactions over HTTP — not supported. Each
/query
call executes independently.
- Multi-database — not supported. One process serves exactly one
in-memory graph; run multiple processes for isolation.
- Parameter values are local tab state. The playground supports a
JSON Params panel and share links can include that JSON, but params
are not stored in a hosted database or synced between users.
- Shared hosted database — not supported. The hosted app runs against
a browser-origin database; saved queries, snapshots, settings,
history, and auto-restored graph state are local browser data.
- True query abort — not yet supported. The Cancel button drops the
pending result from the UI, but the current WASM call still runs until
it returns.
- Multi-database selector — not supported. Use a different browser
profile/origin or clear site data when you need a separate local
scratch graph.
- Remote import — not supported. Import accepts local snapshot files;
the app does not fetch remote URLs to seed a graph.
| Instead of | Use |
|---|
BETWEEN a AND b | x >= a AND x <= b |
HAVING | WITH … WHERE |
GROUP BY cols | Non-aggregated columns in RETURN / WITH |
CREATE INDEX ON :L(prop) | Use CREATE INDEX name FOR (n:L) ON (n.prop) |
CONSTRAINT UNIQUE shorthand | CREATE CONSTRAINT name FOR (n:Label) REQUIRE n.key IS UNIQUE |
LOAD CSV | Parse on host, pass as $rows, UNWIND $rows |
| External utility procedures | Re-implement in the host language |
geo.within_bbox() | Use geo.within_bbox(p, lowerLeft, upperRight) with matching SRIDs |
point.fromWKT() | Parse host-side, pass as a point param |
GROUP BY year | RETURN e.at.year AS year, count(*) |
IF/THEN/ELSE expressions | CASE … WHEN … THEN … END |
Window function row_number() per group | ORDER BY … collect(…)[..N] pipeline |
COUNT(*) FILTER (WHERE …) | count(CASE WHEN … THEN 1 END) |
These are part of standard Cypher but not on the short-term
roadmap:
- General-purpose stored procedures (
CALL family), except the supported
index query procedures
LOAD CSV-based ingestion
- Multi-database
USE
See Why LoraDB for the project's intended direction.