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.

dust314 B
#_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:

Result / 82 B
{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.

dust89 B
1 {active  false
   retired null
   role    engineer}
2 {active true
   note   reviewed}

This patch gives:

Result / 174 B
{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:

dust80 B
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:

dust34 B
#_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:

dust9 B
entity 2

The result is:

Result / 189 B
{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:

dust24 B
entity    2
max_depth 1

The result is:

Result / 338 B
{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.

dust135 B
#_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:

Result / 64 B
{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.

dust7 B
2 null

After this patch, bob has no facts:

Result / 8 B
{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.

dust13 B
missing true

Error

Rejected

An invalid id is an error, and the patch changes nothing:

dust28 B
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.

Run it

database merge patch.