Mutations
July 19, 2026 · View on GitHub
Write statements live inside a query declaration whose body is one or more
mutation statements (the query language covers the read
shape and shared declaration syntax).
query onboard($name: String, $title: String) {
insert Person { name: $name, title: $title }
}
An edge type is inserted the same way — its endpoint columns are just
properties in the assignment block (insert WorksAt { person: $p, org: $o }).
Statements
insert <Type> { prop: <value>, … }update <Type> set { prop: <value>, … } where <prop> <op> <value>delete <Type> where <prop> <op> <value>
<value> is a literal, $param, or now().
On a blob-bearing type, an update materializes and rewrites blob payloads only for the rows matched by its predicate, including blobs the update does not change. This keeps correctness independent of physical index state, but adds read/write I/O proportional to the matched blob bytes; use selective update predicates for large blobs.
Atomicity
A change query publishes one commit at the end of the query. Multiple insert/update statements accumulate in memory and commit together — a mid-query failure leaves the graph untouched. See transactions for the per-query atomicity contract and branches for multi-query workflows.
Concurrent changes use optimistic concurrency over the whole target branch.
Retryable insert/upsert/load operations whose branch authority changed before
physical effects may be discarded and fully revalidated with a bounded
internal retry. A load in append mode remains strict insert through such a
retry; it never changes mode. Strict Update/Delete/Overwrite operations instead
return a structured read-set conflict. This branch-wide token is deliberately
conservative: a change to a different table can invalidate a prepared strict
write because constraints may have read it.
Every node and edge table is keyed physically by id. The storage transaction
for an insertion or upsert carries an exact-id conflict filter:
- a keyed node
insertis an upsert by its derivedid; - generated-ID node and edge inserts are strict inserts;
load --mode appendis strict insert: an existingidreturns structuredkey_conflictand the existing row is unchanged;load --mode mergeis upsert: an existingidis updated.
For a node with @key, the derived id represents the complete typed key
tuple, not only its first property. Single-column keys use the canonical scalar
spelling; composite keys use a JSON array of canonical scalar strings in stable
property-identity order. The same renderer is shared by mutation and load, and
every member of a composite key is immutable during an update. See the
schema language for the exact scalar rules.
A concurrent same-key conflict has effect-aware handling. If no table effect
from the attempt landed, strict insert is rechecked against fresh
manifest-visible state and returns terminal key_conflict only when one of its
attempted IDs now exists. A generic storage conflict with no exact match is
never mislabeled as a duplicate: the engine discards the whole strict attempt
and performs a bounded reprepare without changing it to upsert. Upsert likewise
discards the whole attempt and reruns preparation and validation. If an earlier
table already advanced, or the engine cannot prove the attempt effect-free, it
returns recovery_required and retains the recovery intent. No partial attempt
is retried around unresolved state.
Keyed mutation and load tables use one storage transaction per graph commit.
Each table's accumulated strict-insert or upsert input is limited to 8,192 rows
and 32 MiB of staged Arrow memory. An update also streams its predicate matches
against the table's remaining budget after pending rows are counted and
same-query pending IDs shadow committed rows; stored Blob size is checked before
its payload is read. Exceeding either limit returns HTTP 413 with structured
resource_limit details before the recovery intent is armed, with no durable
effect. Submit a larger incremental load as explicit chunks—
each chunk is a separate atomic graph commit—or use --mode overwrite for an
initial bulk replacement.
Blob values supplied as external URIs have mode-dependent storage semantics.
Keyed insert/upsert and load append/merge sum the declared ranges or object
sizes before reading payload bytes, reject an aggregate above 32 MiB, and copy
accepted bytes into the staged blob. Load overwrite keeps the external URI
cell as a reference. This distinction exists because Lance's merge-insert
builder cannot accept the WriteParams option used by Overwrite.
If the synchronous barrier finds an unresolved overlapping recovery intent, or
if a conflict is discovered after a Lance table effect is durable, the request
returns recovery_required with an operation id. Do not immediately retry that
request; reopen the graph read-write (or restart the server) so the durable
recovery intent is resolved first.
Inserts/updates and deletes cannot mix in one query
A single change query must be either insert/update-only or delete-only. Mixing the two is rejected at parse time, before any I/O:
mutation '<name>' on the same query mixes inserts/updates and deletes; split into separate mutations: (1) inserts and updates, then (2) deletes.
Run two separate queries instead — the inserts/updates first, then the deletes. Each query is still atomic on its own. This is a deliberate rule: inserts, updates, and deletes all stage and commit through the same path, but keeping a single query to one kind means its read-your-writes stays unambiguous (a read within the query never has to reconcile rows you inserted against rows you deleted in the same query). If you need the inserts/updates and deletes to land as one atomic commit, run them on a branch and merge it.
Bulk loading
For loading data from files rather than inline statements, use
omnigraph load (--mode overwrite|append|merge) — it is the
single bulk-write command and applies the same schema validation and atomic
publish as inline mutations. append means strict insert, merge means upsert,
and overwrite replaces the target image; these are logical semantics, not
names of raw Lance operations.