NornicDB Cypher Compatibility¶
Date: November 26, 2025
Status: Complete β production ready
Purpose: Comprehensive audit of Cypher implementation against Neo4j
Currently Implemented¶
Core Clauses¶
- β MATCH - Pattern matching with property filters
- β MATCH...CREATE - Create relationships between matched nodes (like Neo4j's variable scoping)
- β CREATE - Node and relationship creation
- β MERGE - Upsert operations with ON CREATE/ON MATCH
- β DELETE - Node deletion
- β DETACH DELETE - Delete with relationship removal
- β SET - Property updates
- β SET += - Property merging
- β REMOVE - Property removal
- β RETURN - Result projection
- β WHERE - Filtering
- β WITH - Intermediate result projection
- β UNWIND - List expansion
- β OPTIONAL MATCH - Outer join equivalent
- β UNION / UNION ALL - Query combination
- β FOREACH - Iteration with updates
Schema Management¶
- β CREATE CONSTRAINT - All constraint families (see below)
- β CREATE INDEX - Property indexes
- β CREATE FULLTEXT INDEX - Fulltext search indexes
- β CREATE VECTOR INDEX - Vector similarity indexes
- β DROP CONSTRAINT / DROP INDEX - Schema deletion
Constraint Types¶
All constraint DDL supports IF NOT EXISTS for idempotent creation and named or unnamed forms.
Node constraints (FOR (var:Label)):
| Constraint | Syntax |
|---|---|
| Uniqueness | REQUIRE var.prop IS UNIQUE |
| Existence | REQUIRE var.prop IS NOT NULL |
| Node key | REQUIRE (var.p1, var.p2) IS NODE KEY |
| Property type | REQUIRE var.prop IS :: TYPE |
| Temporal no-overlap | REQUIRE (var.key, var.from, var.to) IS TEMPORAL NO OVERLAP |
| Domain/enum | REQUIRE var.prop IN ['val1', 'val2'] |
Cardinality constraints (FOR ()-[var:TYPE]->() or FOR ()<-[var:TYPE]-()):
| Constraint | Syntax |
|---|---|
| Max outgoing | FOR ()-[var:TYPE]->() REQUIRE MAX COUNT N |
| Max incoming | FOR ()<-[var:TYPE]-() REQUIRE MAX COUNT N |
Limits the number of outgoing or incoming edges of a given type per node. Direction is encoded in the FOR clause arrows.
Relationship endpoint policies (FOR (:SrcLabel)-[var:TYPE]->(:TgtLabel)):
| Constraint | Syntax |
|---|---|
| Allowed pair | FOR (:Src)-[var:TYPE]->(:Tgt) REQUIRE ALLOWED |
| Disallowed pair | FOR (:Src)-[var:TYPE]->(:Tgt) REQUIRE DISALLOWED |
ALLOWED policies form a union whitelist: once any ALLOWED policy exists for a relationship type, only declared (source, target) label pairs are permitted. DISALLOWED policies are a blacklist and take precedence over ALLOWED.
Relationship constraints (FOR ()-[var:TYPE]-()):
| Constraint | Syntax |
|---|---|
| Uniqueness | REQUIRE var.prop IS UNIQUE |
| Composite uniqueness | REQUIRE (var.p1, var.p2) IS UNIQUE |
| Existence | REQUIRE var.prop IS NOT NULL |
| Relationship key | REQUIRE (var.p1, var.p2) IS RELATIONSHIP KEY |
| Property type | REQUIRE var.prop IS :: TYPE |
| Temporal no-overlap | REQUIRE (var.key, var.from, var.to) IS TEMPORAL NO OVERLAP |
| Domain/enum | REQUIRE var.prop IN ['val1', 'val2'] |
Temporal no-overlap, domain/enum, cardinality, and endpoint policy constraints are NornicDB extensions not available in Neo4j. Uniqueness and key constraints on relationships automatically create owned backing indexes. SHOW CONSTRAINTS returns all constraint types with entity type, direction, maxCount, sourceLabel, targetLabel, and policyMode columns where applicable. For operational guidance on NornicDB-specific schema features, including REQUIRE { ... } block contracts and SHOW CONSTRAINT CONTRACTS, see Managing Constraints.
CALL Procedures¶
- β db.labels() - List all labels
- β db.propertyKeys() - List all property keys
- β db.relationshipTypes() - List all relationship types
- β db.indexes() - List indexes
- β db.constraints() - List constraints
- β db.index.vector.queryNodes() - Vector similarity search
- β db.index.fulltext.queryNodes() - Fulltext search
- β apoc.path.subgraphNodes() - Graph traversal
- β apoc.path.expand() - Path expansion
SHOW Commands¶
- β SHOW INDEXES - Display indexes
- β SHOW CONSTRAINTS - Display constraints
- β SHOW CONSTRAINT CONTRACTS - Display block-style constraint contract metadata
- β SHOW PROCEDURES - List procedures
- β SHOW FUNCTIONS - List functions
- β SHOW DATABASE - Database info
Aggregation Functions¶
- β COUNT() - Count aggregation
- β SUM() - Sum aggregation
- β AVG() - Average aggregation
- β MIN() / MAX() - Min/max aggregation
- β COLLECT() - List collection
Scalar Functions (52 total)¶
- β String functions: substring, replace, trim, upper, lower, split, etc.
- β Math functions: abs, ceil, floor, round, sqrt, sin, cos, etc.
- β List functions: size, head, tail, last, range, etc.
- β Type functions: toInteger, toFloat, toString, toBoolean
- β Spatial functions: point, distance
- β Date/time functions: date, datetime, timestamp
Bolt Handshake Compatibility for cypher-shell¶
NornicDB speaks Bolt directly, and most Bolt drivers work without special handling. One notable edge case is Neo4j's cypher-shell, which may reject an otherwise valid Bolt connection if the Bolt HELLO success metadata does not advertise a Neo4j-style server string.
For that case, NornicDB includes an explicit compatibility override:
export NORNICDB_BOLT_SERVER_ANNOUNCEMENT="Neo4j/5.26.0"
cypher-shell -a bolt://localhost:7687 -u admin -p password
Equivalent YAML setting:
This changes only the announced Bolt server string used during handshake compatibility checks. It does not change Cypher behavior or query semantics. Leave it unset unless you specifically need compatibility with cypher-shell or another strict Neo4j client.
Recently Verified Features¶
1. ORDER BY Clause¶
Status: Implemented
Impact: Full sorting support
Features:
- β Single and multiple sort fields
- β ASC/DESC modifiers
- β String and numeric sorting
- β Integration with LIMIT/SKIP
2. LIMIT / SKIP Clauses¶
Status: Implemented
Impact: Full pagination support
Features:
- β LIMIT with any number
- β SKIP with any number
- β Combined SKIP + LIMIT for pagination
- β Works with ORDER BY
3. DISTINCT Keyword¶
Status: Implemented
Impact: Full deduplication support
Features:
- β RETURN DISTINCT
- β Deduplication of result rows
- β Works with aggregations
4. AS Aliasing in RETURN¶
Status: Implemented
Impact: Full aliasing support
5. Variable-length Paths¶
Status: Implemented
6. EXISTS Subqueries¶
Status: Implemented
7. COUNT Subqueries¶
Status: Implemented
8. Map Projections¶
Status: Implemented
9. List Comprehensions¶
Status: Implemented
10. WHERE after YIELD¶
Status: Implemented (6 passing tests)
CALL db.index.vector.queryNodes('idx', 10, $vector)
YIELD node, score
WHERE score > 0.8
RETURN node
-- Also works with CONTAINS, <>, =
CALL db.labels() YIELD label WHERE label CONTAINS 'Person'
Implemented November 26, 2025¶
11. CASE Expressions¶
Status: Complete
Files: pkg/cypher/case_expression.go (376 lines)
-- Searched CASE
MATCH (n:Person)
RETURN n.name,
CASE
WHEN n.age < 18 THEN 'minor'
WHEN n.age < 65 THEN 'adult'
ELSE 'senior'
END AS ageGroup
-- Simple CASE
MATCH (n:Person)
RETURN CASE n.age
WHEN 30 THEN 'thirty'
WHEN 25 THEN 'twenty-five'
ELSE 'other'
END AS ageLabel
Features Implemented:
- β Searched CASE with WHEN/THEN/ELSE
- β Simple CASE with value matching
- β NULL handling (IS NULL, IS NOT NULL)
- β Comparison operators (<, >, <=, >=, =, <>)
- β Nested expression evaluation
- β Multiple WHEN clauses
- β Optional ELSE clause (returns NULL if omitted)
12. shortestPath() / allShortestPaths()¶
Status: Complete (16 passing tests)
Files: pkg/cypher/shortest_path.go (372 lines), pkg/cypher/traversal.go (617 lines)
-- shortestPath with MATCH variable resolution
MATCH (start:Person {name: 'Alice'}), (end:Person {name: 'Carol'})
MATCH p = shortestPath((start)-[:KNOWS*]->(end))
RETURN p, length(p) AS pathLength
-- allShortestPaths
MATCH (start:Person {name: 'Alice'}), (end:Person {name: 'Carol'})
MATCH p = allShortestPaths((start)-[:KNOWS*]->(end))
RETURN p
-- Path functions
MATCH p = shortestPath((a)-[*]-(b))
RETURN nodes(p), relationships(p), length(p)
Features Implemented:
- β BFS shortest path algorithm (unweighted)
- β allShortestPaths() - finds all paths of minimum length
- β Variable resolution from MATCH clause (like Neo4j's LogicalVariable)
- β Direction support (outgoing ->, incoming <-, both -)
- β Relationship type filtering
- β Max hops limiting (*..max)
- β Path functions: nodes(p), relationships(p), length(p)
- β Cycle detection
Recent Fix: shortestPath now correctly resolves variable references (e.g., start, end) from the preceding MATCH clause, matching Neo4j's behavior where variables are "in scope" and referenced, not re-queried.
13. Transaction Atomicity¶
// Transaction support with full rollback
tx := engine.BeginTransaction()
// All operations are buffered
tx.CreateNode(&storage.Node{...})
tx.CreateEdge(&storage.Edge{...})
tx.UpdateNode(nodeID, &storage.Node{...})
tx.DeleteNode(nodeID)
// Atomic commit - all or nothing
err := tx.Commit()
// Or rollback to discard all changes
tx.Rollback()
Features Implemented:
- β
BeginTransaction()- Start new transaction - β
Commit()- Atomically apply all buffered operations - β
Rollback()- Discard all buffered operations - β
CreateNode/UpdateNode/DeleteNode- Node operations in transaction - β
CreateEdge/DeleteEdge- Edge operations in transaction - β
GetNode()- Read-your-writes consistency - β
IsActive()- Check transaction status - β Isolation - Uncommitted changes not visible to other operations
- β Atomicity - All operations succeed or all fail together
14. Composite Indexes¶
Status: Complete
Files: pkg/storage/schema.go
Features:
- β Multi-property indexes
- β SHA256-based composite keys
- β Efficient prefix lookups
- β Full and partial key matching
- β Neo4j-compatible behavior
15. MATCH...CREATE¶
Status: Complete
Files: pkg/cypher/create.go (427 lines)
-- Create relationship between existing matched nodes
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS]->(b)
Key Feature: Like Neo4j, variables from MATCH are "in scope" - CREATE only creates what's NEW. If variables reference matched nodes, use those existing nodes (not create new ones).
16. EXPLAIN / PROFILE¶
Status: Complete (27 passing tests)
Files: pkg/cypher/explain.go (560 lines), pkg/cypher/explain_test.go
-- EXPLAIN - show execution plan without executing
EXPLAIN MATCH (n:Person) RETURN n
EXPLAIN MATCH (n:Person) WHERE n.age > 25 RETURN n ORDER BY n.name LIMIT 10
-- PROFILE - execute and show plan with statistics
PROFILE MATCH (n:Person) RETURN n
PROFILE MATCH (n:Person)-[:KNOWS]->(m) RETURN n, m
Features Implemented:
- β EXPLAIN mode (shows plan, doesn't execute)
- β PROFILE mode (executes and shows plan with stats)
- β Execution plan tree structure
- β Operator types: NodeByLabelScan, AllNodesScan, NodeIndexSeek, Filter, Expand, Sort, Limit, ProduceResults, etc.
- β Estimated rows per operator
- β DB hits estimation
- β Actual rows and timing (PROFILE only)
- β Visual plan formatting
Example Output:
+--------------------------------------------------------------+
| PROFILE Query Plan |
+--------------------------------------------------------------+
| Total Time: 1.234ms |
| Total Rows: 3 |
| Total DB Hits: 2006 |
+--------------------------------------------------------------+
| +- ProduceResults (Return results) |
| | Est: 100, Actual: 3, Hits: 100 |
| +- NodeByLabelScan (Scan all :Person nodes) |
| | Est: 1000, Actual: 3, Hits: 2000 |
+--------------------------------------------------------------+
βΊοΈ Optional Features (Not Critical)¶
1. Multi-database Support π’ LOW PRIORITY¶
Status: NOT IMPLEMENTED
Impact: Single database only
Estimated Effort: 1-2 weeks
Priority: LOW (most deployments use single database)
Implementation Status Summary¶
| Feature | Status | Tests | Coverage |
|---|---|---|---|
| CASE expressions | Complete | 7+ tests | 376 lines |
| shortestPath() | Complete | 16 tests | 372 lines |
| allShortestPaths() | Complete | 16 tests | included |
| Transaction Atomicity | Complete | 12 tests | 521 lines |
| WHERE after YIELD | Complete | 6 tests | integrated |
| MATCH...CREATE | Complete | 16+ tests | 427 lines |
| Composite Indexes | Complete | multiple | integrated |
| EXPLAIN/PROFILE | Complete | 27 tests | 560 lines |
Test Coverage¶
| Package | Tests | Coverage |
|---|---|---|
| pkg/cypher | 863 tests | 82%+ |
| pkg/storage | 308 tests | 85.2% |
| Total | 1,171 tests | ~83% |
Current Status Summary¶
Compatibility: 100% β production ready
Status: All critical features implemented
Deployment: Ready for production use
Complete Feature Set¶
Core Query (100%):
- β All 16 Cypher clauses implemented and tested
- β All result modifiers (ORDER BY, LIMIT, SKIP, DISTINCT, AS)
- β All pattern types (variable-length, bidirectional, multiple)
- β All subqueries (EXISTS, COUNT)
- β All collections (map projections, list/pattern comprehensions)
- β WHERE after YIELD filtering
Advanced Features (100%):
- β CASE expressions (searched and simple)
- β shortestPath() and allShortestPaths() with MATCH variable resolution
- β Variable-length path traversal
- β Composite indexes with prefix lookup
- β MATCH...CREATE with variable scoping (like Neo4j)
Transaction Support (100%):
- β BeginTransaction/Commit/Rollback
- β Atomic operations (all-or-nothing)
- β Read-your-writes consistency
- β Transaction isolation
Schema & Indexes (100%):
- β All constraint families: UNIQUE, EXISTS, NODE KEY, RELATIONSHIP KEY, property type
- β Constraints on both nodes and relationships with full write-path enforcement
- β NornicDB extensions: temporal no-overlap, domain/enum, cardinality, endpoint policy constraints
- β IF NOT EXISTS idempotent creation, owned backing indexes
- β Property indexes (single and composite)
- β Fulltext indexes (BM25 scoring)
- β Vector indexes (cosine/euclidean/dot similarity)
Functions (100%):
- β 52 scalar functions
- β 5 aggregation functions
- β 10 CALL procedures
βΊοΈ Optional (Not Required for Most Deployments)¶
Low Priority:
- βΊοΈ Multi-database - Not needed
Recent Changes (November 26, 2025)¶
shortestPath Variable Resolution Fix¶
Problem: shortestPath((start)-[:KNOWS*]->(end)) was not correctly resolving start and end variables from the preceding MATCH clause.
Solution: Implemented Neo4j-style variable resolution:
- Parse the first MATCH clause to extract variable bindings
- Resolve which
nodePatternInfoeach variable maps to - Find actual nodes matching those patterns
- Use those specific nodes for shortestPath calculation
Reference: Neo4j uses LogicalVariable references in their query planner to bind variables from MATCH before using them in subsequent clauses.
Transaction Atomicity Implementation¶
Added: Full transaction support with:
- Buffered operations (Write-Ahead Log pattern)
- Atomic commit (all operations applied together)
- Rollback support (discard all buffered changes)
- Read-your-writes consistency
- Transaction isolation
Last Updated: November 26, 2025 (Post EXPLAIN/PROFILE implementation)
Status: Production ready
Test Results: 1,171 tests passing