Found while giving graph-bench a zu column for the linkbench, snb-update and finbench write operations. Two refusals, both about the same thing, which is that a node's identity is not something a write statement can name.
The first is that an INSERT carrying an id is rejected outright:
INSERT (:Obj {id: 9000001, otype: 1, version: 0, time: 0, payload: 'x'})
-- invalid argument: the element carries 'id', which is not a column of the table it is created in
That is crates/zu/src/insert.rs around line 505. It is accurate about the storage: a zu node id is the dense row offset the loader handed out, and id is not a column. But it means a statement cannot decide what the row it just created is called, so nothing written ahead of the run can refer to it afterwards.
The second is that a node table that does have a key index refuses appended rows at all:
INSERT (:Person {id: 9000001, name: 'x'})
-- invalid argument: unsupported folding appended rows into a keyed table id 2
That is crates/zu-zu1/src/fold.rs around line 747, the guard Some(_) if new_from != old.from_count. zu copy builds a key index for any node table whose ids are not dense, so a store loaded from real data usually has one, and on those stores no node can be created at all.
What this costs in graph-bench, which is the concrete reason to raise it:
- lb-update-node and lb-delete-node skip. Their setup creates a scratch object and the timed statement has to find it again. On every other engine the setup gives it an id. On zu it lands at whatever offset the store was next free at, and no id written ahead of the run names it. Matching on the payload instead would put a full table scan inside the timed statement, which is not the point operation LinkBench is measuring, so the honest outcome is a skip.
- fb-w2 skips for the same reason, and so does every one of the six snb-update shapes, which all create an entity at a negative literal id and then refer to it.
That is three workloads out of the write side of the matrix, and none of them come back without this.
The fix worth having is a node key that a write statement can name: either accept id in an INSERT and have the store map it to an offset through the key index, or accept the key label set the graph type declares and use that. Both need the keyed table to take appended rows first, so the fold guard is the prerequisite either way.
Found while giving graph-bench a zu column for the linkbench, snb-update and finbench write operations. Two refusals, both about the same thing, which is that a node's identity is not something a write statement can name.
The first is that an INSERT carrying an id is rejected outright:
That is crates/zu/src/insert.rs around line 505. It is accurate about the storage: a zu node id is the dense row offset the loader handed out, and id is not a column. But it means a statement cannot decide what the row it just created is called, so nothing written ahead of the run can refer to it afterwards.
The second is that a node table that does have a key index refuses appended rows at all:
That is crates/zu-zu1/src/fold.rs around line 747, the guard
Some(_) if new_from != old.from_count. zu copy builds a key index for any node table whose ids are not dense, so a store loaded from real data usually has one, and on those stores no node can be created at all.What this costs in graph-bench, which is the concrete reason to raise it:
That is three workloads out of the write side of the matrix, and none of them come back without this.
The fix worth having is a node key that a write statement can name: either accept id in an INSERT and have the store map it to an offset through the key index, or accept the key label set the graph type declares and use that. Both need the keyed table to take appended rows first, so the fold guard is the prerequisite either way.