Writing an aggregate
8 / 11 · save and delete in the full document · PDF
The shape that loads an aggregate also saves it. save writes the view's row and makes each owned collection in the database equal to the value's; delete removes the row and everything it owns. Both run in your transaction, or a savepoint of it, and each statement runs when it is called: there is no session to flush. A #[view(version)] column makes concurrent writers fail instead of overwriting each other.
Save. save creates or replaces the row by key: an UPDATE, and only if no row has the key, an upsert — INSERT … ON CONFLICT (key) DO UPDATE on PostgreSQL and SQLite, INSERT … AS new ON DUPLICATE KEY UPDATE on MySQL — that also updates a row inserted meanwhile, with its columns, embedded values and json fields MPA-WRITE-3. An owned collection is made equal to the value's: rows of elements that are gone are deleted with what they own, deepest first, and the others are saved with the parent's key, their position for an index list, and their key for a map MPA-WRITE-4. Keys come from the application MPA-WRITE-2, or from the database: a #[view(generated)] key that is None is inserted with RETURNING (or LAST_INSERT_ID() on MySQL), and what the row owns is written under the new key MPA-WRITE-13.
What is not written. A many-to-many collection replaces its link rows, and the linked views are not written; a to-one reference writes its foreign key only MPA-WRITE-5. An enum in columns writes its tag as a literal, so any tag column type accepts it, its variant's columns, and NULL to the others'; an enum in a table per variant upserts its variant's row and deletes the others' MPA-WRITE-6. A view of only some of a table's columns saves them in a row that exists; it fails only when the row is new and the other columns are NOT NULL without a default MPA-WRITE-12.
Optimistic locking. With a #[view(version)] column, save updates the row only if it has the value's version, incrementing it, and otherwise inserts it only if no row has its key; anything else fails with Error::Conflict and the transaction is rolled back MPA-WRITE-9. MySQL counts a found row as affected by ON DUPLICATE KEY, so there the insert is INSERT … SELECT … WHERE NOT EXISTS. The new versions are written back into the value and the elements of its owned collections, so it can be saved again without reloading MPA-WRITE-10.