Patches
A mutation selects the entities that a patch changes. A direct patch supplies entity ids without a selection query. It changes many entities in one transaction. The native database merge patch call commits every change together or commits nothing.
The document follows JSON Merge Patch semantics in the selected document format. It does not use JSON Patch operations.
Start with an empty database. Submit this merge patch. The temporary IDs
#_ada, #_bob and #_cy request new entities. Bob's mentor links to Ada
within the same patch.
{
"#_ada": {
"name": "Ada",
"role": "engineer",
"age": 36,
"active": true,
"retired": false
},
"#_bob": {
"name": "Bob",
"role": "engineer",
"age": 41,
"mentor": {
"#link": "#_ada"
},
"active": false
},
"#_cy": {
"name": "Cy",
"role": "designer",
"age": 29
},
"#_tx": {
"author": "tutorial",
"reason": "create-people"
}
}
The commit result includes the transaction ID and an ids mapping from each
temporary ID to its allocated entity ID. In this fresh database, these fields
are:
{
"transaction": 4,
"ids": {
"#_ada": 1,
"#_bob": 2,
"#_cy": 3
}
}Keep the IDs from your actual result. An existing database can allocate
different numbers. The examples below use the returned IDs 1 for Ada,
2 for Bob and 3 for Cy. The special temporary ID #_tx attaches its
author and reason metadata to transaction 4.
Merge patch keyed by id
A patch is a document whose keys are entity ids. Each key is the id of a data entity. Its value contains the fields to change.
{
"1": {
"active": false,
"retired": null,
"role": "engineer"
},
"2": {
"active": true,
"note": "reviewed"
}
}
This patch gives:
{
"1": {
"name": "Ada",
"role": "engineer",
"age": 36,
"active": false
},
"2": {
"name": "Bob",
"role": "engineer",
"age": 41,
"mentor": {
"#link": 1
},
"active": true,
"note": "reviewed"
}
}The merge patch rules produce that result:
- An omitted member stays as it is.
- The patch removes a member set to
null. - Each other value replaces the current value.
Stardust applies those rules to each entity that the patch names. The members
of an entity object are its fields. That is why bob keeps role engineer,
ada loses retired, and each other supplied field gets its new value. A transaction document uses the same decimal ID keys.
Compare-and-set per entity
Put per-entity conditions in #_tx.stardust/checkAndSet. Each map key is a
decimal entity ID. Its value is the expected transaction.
For a separate example, submit only the initial patch in a fresh database.
It changes Ada and Bob in transaction 4. Then submit:
{
"1": {
"age": 37
},
"#_tx": {
"stardust/checkAndSet": {
"1": 4,
"2": 4
}
}
}
This patch changes Ada only if both entities still have revision 4. A
change to another entity does not invalidate these checks. A check can guard
an entity that the patch does not change.
Stardust checks every revision in the atomic write transaction. A mismatch
rejects the complete patch with conflict. The diagnostic identifies the
entity and gives the expected and actual revisions. No changes or temporary
IDs are committed. The engine consumes stardust/checkAndSet; it does not
store these conditions as transaction metadata.
A revision includes field removals and entity deletion. Changing a value and then restoring it does not restore the old revision. To retry after a conflict, read the current state and obtain its revision before computing a new patch.
Positive expectations require an exact last-touch transaction. Zero instead requires that the entity has no current facts, including after deletion:
{
"#_tx": {
"stardust/checkAndSet": {
"1": 0
}
}
}
A request with only conditions checks them without creating a transaction.
It returns no_change on success. The same checks apply when supplied field
values already match. Other transaction metadata accompanies data changes.
The condition must be an object. Checked IDs must be decimal user entity IDs and appear only once. Expected transactions must be nonnegative integers. An empty condition map imposes no entity conditions. Temporary IDs, deletions and other transaction metadata work as in an ordinary patch.
Reading an entity for a conditional write
Read entity returns one document from one store snapshot. Read Bob:
{
"entity": 2
}
The result is:
{
"document": {
"active": true,
"age": 41,
"mentor": {
"#link": 1
},
"name": "Bob",
"note": "reviewed",
"role": "engineer"
},
"revisions": {
"2": 5
},
"truncated": false
}document holds the entity's current fields. revisions maps each entity
the result reflects to its last-touch transaction, so it serves directly as
the stardust/checkAndSet map of a patch computed from that document. The
returned document is independent of the database. Close it when finished.
Read results omit object fields whose values are empty objects or arrays,
including objects that become empty after nested fields are omitted. This
applies to entity data and metadata: links is absent when empty. Null, empty
strings, false, zero, and array positions stay intact.
document is absent if all its fields are omitted.
An entity with no current facts has a null document. Its revision is the
deletion's transaction after a deletion, and zero for a never-touched entity.
Thus, a positive condition can require an exact deletion revision, while zero
checks only the absence of current facts.
With max_depth 1, the read also expands Bob's mentor in place.
links preserves the original mentor reference, and revisions adds Ada:
{
"entity": 2,
"max_depth": 1
}
The result is:
{
"document": {
"active": true,
"age": 41,
"mentor": {
"active": false,
"age": 36,
"name": "Ada",
"role": "engineer"
},
"name": "Bob",
"note": "reviewed",
"role": "engineer"
},
"links": {
"/mentor": {
"#link": 1
}
},
"revisions": {
"1": 5,
"2": 5
},
"truncated": false
}links maps JSON Pointer paths
within document to their original references. /employer/city
selects a nested field; /friends/0 selects an array element. In a field
name, ~ becomes ~0 and / becomes ~1, so /a~1b selects the field
a/b. References replaced by null or an omitted empty object keep their
entries in links.
max_depth counts link hops along each path, up to 32. A link to an entity
with no current fields becomes null, and the field stays; revisions still
records that entity. Links inside stardust/ fields stay references. An
entity reached on several paths, or on a cycle, is expanded again on each
path until max_depth. Every link one hop away is expanded before any two
hops away.
max_entities (default 1000) bounds the entity documents in the result, and
the result counts against the database memory budget. When either limit
refuses an expansion, that link and every later one stay {#link id}
references and truncated is true. The conditions in revisions then cover
exactly the entities in the document.
A link in a patch can name an ID that no entity has yet. An ID of 0, a
negative ID or an ID at or above 2^40 is refused with invalid_argument and
the code invalid_link, which names the key and field.
A no-change commit does not give a new entity revision. Do not infer the last touch from the newest remaining fact: a field removal can be more recent.
Creating with temporary names
A key that starts with #_ is a temporary name for an entity the patch
creates. Stardust allocates an id for the entity when the patch commits.
{#link #_name} refers to that id anywhere in the same patch, including in a
new or existing entity. The temporary name applies only within the patch. After the commit, use the
entity ID to address the entity. The engine rejects a repeated name, an undeclared temporary link target,
and null on a temporary name.
{
"#_carol": {
"name": "Carol",
"mentor": {
"#link": 1
}
},
"#_dave": {
"name": "Dave",
"buddy": {
"#link": "#_carol"
}
},
"1": {
"protege": {
"#link": "#_carol"
}
}
}
This commits two new entities and one change to Ada, all in one transaction. After the preceding examples, the commit returns these fields:
{
"transaction": 8,
"ids": {
"#_carol": 6,
"#_dave": 7
}
}Use the returned IDs to refer to Carol and Dave in later calls.
#_tx is a special temporary ID for the transaction that commits the patch.
Its object supplies metadata, such as author and reason, on that transaction.
It does not create an ordinary data entity. {#link #_tx} links to the same
transaction from elsewhere in the patch. This metadata accompanies data
changes. Stardust rejects a patch that contains only #_tx.
One value at a time
A list or a map stored in a field is one Stardust value. The patch gives the complete intended value for that field. It does not give editing operations inside the value. An object inside an entity is a map in that field, never a second entity: a data entity has no children.
Stardust compares each field with the current facts before it writes. Stardust skips a field
that already matches. It also skips null for a field that is absent. A patch with no effect commits no transaction.
Deleting
An entity id set to null deletes that entity.
{
"2": null
}
After this patch, bob has no facts:
{
"2": null
}Entities elsewhere that link to a deleted entity survive, and their links keep the deleted target's stable numeric id.
Invalid patch forms
The keys of a patch are ids, so a field at the root is an error.
{
"missing": true
}
Error
Rejected
An invalid id is an error, and the patch changes nothing:
{
"ffffffffffff": {
"role": "visitor"
}
}
Error
Rejected
A #_name key creates a data entity. References to that temporary name resolve
within the patch. The native commit result gives the resolved ids.
Stardust also rejects text that is not a document. No rejected patch changes a stored fact.
A schema-checked patch
The core patch operation can validate each staged entity against a schema before it commits. A deleted entity has no document to validate. The current public merge-patch call does not expose that schema scope.
Reading the committed transaction
A commit result identifies its transaction. Reading that transaction document
gives the patch for the commit, keyed by entity id. Each value holds asserted
fields and null for retractions. Unchanged fields are absent.
The transaction entity appears under its own ID with stardust/transaction and
stardust/committedAt. There are no entities or transaction wrappers.
Definition and object changes include their structural facts and numeric
references. The recorded patch stays the same after a later rename or deletion.
Native documents and DUST contain the same changes.