Writing data
NamiDB Cypher supports the standard write clauses from openCypher 9 /
GQL: CREATE, MERGE, SET, REMOVE, DELETE, DETACH DELETE.
Every write statement is durably committed before the call returns
(WAL append + manifest CAS). There are no explicit BEGIN / COMMIT
in v0; the unit of atomicity is the statement.
See RFC-009 for the execution model.
CREATE
Creates nodes and relationships. Properties can be literals or $param.
CREATE (a:Person {name: 'Alice', age: 30})CREATE (a:Person {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:Person {name: 'Bob'})CREATE after a MATCH chains both bindings into the new pattern:
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})CREATE (a)-[:KNOWS {since: 2020}]->(b)A write-only query (no MATCH before) starts from an implicit single
driver row — the same pattern used by UNWIND.
MERGE
Upsert. If the pattern matches, the existing nodes / relationships are returned; if it doesn’t, the pattern is created.
MERGE (p:Person {name: 'Alice'})ON CREATE SET p.firstSeen = $now, p.lastSeen = $nowON MATCH SET p.lastSeen = $nowIn v0, MERGE accepts a single pattern part (no multi-part patterns).
Multi-label MERGE (a:A:B {...}) is rejected at parse time with
E007_MergeMultiLabel.
Concurrency: MERGE relies on the single-writer-per-namespace
invariant for serialization. With one writer per namespace, two
concurrent MERGEs against the same key are linearised by the
writer mutex.
SET
Mutate properties or add labels.
Property assignment:
MATCH (p:Person {name: 'Alice'})SET p.age = 31Replace the whole property map:
MATCH (p:Person {name: 'Alice'})SET p = {name: 'Alice', age: 31, country: 'MX'}Merge a partial map (only the listed keys change):
MATCH (p:Person {name: 'Alice'})SET p += {age: 31, country: 'MX'}Add labels:
MATCH (p:Person {name: 'Alice'})SET p:Employee:ManagerREMOVE
The inverse of SET. Removes properties or labels.
MATCH (p:Person {name: 'Alice'})REMOVE p.countryMATCH (p:Person {name: 'Alice'})REMOVE p:ManagerDELETE and DETACH DELETE
DELETE tombstones a node or relationship. A bare DELETE against a
node that still has edges fails with an explicit ExecError::Mutation
message suggesting DETACH DELETE:
MATCH (p:Person {name: 'Alice'})DELETE p -- fails if Alice has incident edgesDETACH DELETE enumerates the incident edges across every edge type
in the manifest schema, tombstones them, then tombstones the node:
MATCH (p:Person {name: 'Alice'})DETACH DELETE p -- removes Alice and every edge touching herDeleting an edge directly works without DETACH:
MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})DELETE rUNWIND for bulk writes
UNWIND expands a parameter list into rows, which CREATE then
materialises. This is the idiomatic Cypher path for bulk inserts:
UNWIND $people AS pCREATE (:Person {name: p.name, age: p.age})client.cypher( "UNWIND $people AS p CREATE (:Person {name: p.name, age: p.age})", params={"people": [ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Carol", "age": 42}, ]},)For high-volume ingest, the Python staging API (client.merge_nodes /
client.merge_edges) is faster — it batches mutations behind one
tokio-mutex round trip per call. See Python
library.
Auto-commit and the write outcome
Each write statement runs through execute_write, which:
- Walks the plan top to bottom, calling
upsert_node/upsert_edge/tombstone_*against theWriterSessionper row. - Calls
writer.commit_batch().awaitautomatically before returning.
The return value carries a counter summary:
WriteOutcome { rows: ..., nodes_created: u64, edges_created: u64, nodes_deleted: u64, edges_deleted: u64, properties_set: u64,}Counters increment per operation, not per net change of state (so
SET p.x = p.x counts as one properties_set). The counters are
exposed on the HTTP envelope as write_outcome — see the HTTP
API.
Read-your-own-writes (not yet)
Inside a single statement, writes are not visible to reads that come after them in the same plan tree. Example:
CREATE (a:Person {name: 'Ada'})MATCH (p:Person) RETURN p.nameThe MATCH runs against the snapshot pinned before the CREATE.
Workaround: split into two statements. Cypher writes auto-commit, so
the second statement (or the next Client.cypher call) sees the new
node.
RFC-009 documents why and traces the path to a future transactional model.
What’s next
- Reading data —
MATCH,WHERE,RETURN, aggregations. - Operators & functions — the complete operator and built-in function reference.
- Supported subset — the exact slice of openCypher / GQL the parser accepts.