-
Notifications
You must be signed in to change notification settings - Fork 67
Description
Hi Cypher team,
I'm working on a Python library for parsing and transpiling GQL/Cypher. While building our Neo4j dialect I relied heavily on the conformance documentation. Here are a few things I ran into, plus some features that seem to be missing from the docs.
1. Questions and potential issues
SELECT: Select statements (e.g. SELECT 1) are rejected by Neo4j but I don't see it mentioned on the unsupported mandatory page.
GV39 temporal literals: The type support works, but the GQL literal form DATE '2026-01-01' is rejected (the Cypher constructor date('2026-01-01') works fine). A note on the GV39 entry would help clarify this for implementers.
STDDEV_SAMP / STDDEV_POP: The analogous Cypher page (GF10) spells these STDEV_SAMP / STDEV_POP (single D). The GQL standard uses STDDEV_SAMP / STDDEV_POP (double D). Looks like a typo.
MOD(): GQL defines MOD() for modulus; Cypher uses % instead and rejects MOD(). Not mentioned on any conformance page.
Trailing decimal: RETURN 3. (no fractional digits) is rejected; RETURN 3.0 works. Minor edge case.
DOUBLE vs FLOAT64: The analogous page maps both to Cypher's FLOAT. In the GQL standard FLOAT64 is exactly IEEE 754 binary64 while DOUBLE has implementation-defined precision (>= FLOAT). Since Cypher's FLOAT is 64-bit IEEE 754, the FLOAT64 mapping is precise, but the DOUBLE mapping could use a clarifying note.
2. Possibly undocumented optional features
Through integration testing (parse GQL, generate Cypher, execute against Neo4j 2026 community) I found several optional GQL features that work but don't appear on any of the three conformance pages.
| Feature | Description | Test query | Notes |
|---|---|---|---|
| G005 | Path search prefix in a path pattern | MATCH SHORTEST 2 (a)-[:KNOWS]->{1,5}(b) RETURN b.name |
Prerequisite for G016-G020 which are all listed; likely just missing from the docs |
| G044 | Basic abbreviated edge patterns | MATCH (a)-->(:Person) RETURN a.name |
GQL -> / <- / - vs Cypher --> / <-- / -- |
| G111 | IS LABELED predicate | MATCH (n) WHERE n:Person RETURN n.name |
GQL n IS LABELED Person vs Cypher n:Person |
| GA05 | Cast specification | RETURN toFloat(42) |
GQL CAST(x AS FLOAT) vs Cypher toFloat(x) etc. |
| GB03 | Double solidus comments | // comment |
Same syntax |
| GD02 | Graph label set changes | MATCH (n) SET n:Intern RETURN n.name |
Same syntax |
| GD04 | DELETE with simple expression | MATCH ()-[r:LIKES]->() DELETE r |
Same syntax |
| GE07 | Boolean XOR | WHERE n.active = TRUE XOR n.age > 30 |
Same syntax |
| GF20 | Aggregate functions in sort keys | ORDER BY COUNT(m) DESC |
Same syntax |
| GL01 | Hexadecimal literals | RETURN 0xFF |
Same syntax |
| GL02 | Octal literals | RETURN 0o77 |
Same syntax |
| GL04 | Exact number in common notation | RETURN 3.14 |
Same syntax (trailing-dot 3. fails) |
| GP02 | Inline procedure: implicit scope | CALL { RETURN 42 AS x } RETURN x |
Same syntax |
| GQ12 | OFFSET clause | ORDER BY n.name SKIP 3 |
GQL OFFSET n vs Cypher SKIP n |
| GQ14 | Complex expressions in sort keys | ORDER BY n.age * -1 |
Same syntax |
| GQ16 | Pre-projection aliases in sort keys | ORDER BY person_name |
Same syntax |
| GQ22 | EXISTS predicate: multiple MATCH | WHERE EXISTS { MATCH ... MATCH ... } |
Same syntax |
| GV41 | Duration types | RETURN duration('P1Y') |
GQL DURATION 'P1Y' literal vs Cypher duration(...) constructor |
| GV48 | Nested record types | RETURN {a: {b: 'hello'}}.a.b |
Same syntax |
Additionally, G043 (complete full edge patterns) and G045 (complete abbreviated edge patterns) are partially supported. Neo4j handles the directed forms (<-[r]-> for G043, <--> for G045) but rejects the tilde forms (~[r]~, <~[r]~, ~[r]~> for G043 and ~, <~, ~> for G045). Since Cypher only has directed edges, this is expected. Whether these count as supported probably depends on how strictly partial conformance is interpreted.
3. What's working well
Overall the conformance documentation has been genuinely useful for our implementation. The analogous Cypher page in particular: explicit function-name mappings like COLLECT_LIST -> collect and PATH_LENGTH -> length saved us a lot of guesswork, and every mapping we tested works correctly.
Thanks for maintaining these docs.