-
Notifications
You must be signed in to change notification settings - Fork 388
Description
Preamble
We need an update or insert operation to make sure updates in Sophia engine are write-only.
The semantics of the operation is this: if there is a tuple matching the specifying key, it's updated, otherwise the default tuple which is an extra argument of upsert, is inserted. Since upsert execution is delayed in 99.99% of the time, it should not return any results.
We could have modified our standard "update" command, to accept the fourth argument, a tuple, for the case when the original tuple does not exist, but the semantics would not be quite the same.
The difference in the semantics comes from error handling: in short, upsert should never fail to execute.
This is an important distinction between Tarantool upsert and upsert, e.g. in PostgreSQL, which still needs to read the original row, since there may be other errors other than duplicate key error during execution.
For example, in update, if you try to set a field to a value, and the subject tuple has no such field, you get an error. You know that there is no such field only when you read the old tuple. This can't work for upsert, which must work so that it can be committed before it can be "executed". Many upsert commands are "executed" at once, during merge of sophia WAL and an existing sophia branch.
So the task of this ticket is to come up with an entirely error-free semantics and syntax to support it.
The syntax
box.space[space-name]:upsert(tuple, ops)
In future, when we support update via a secondary unique index (and if we find out how
to do upsert with multiiple unique indexes), we may also add:
box.space[space-name].index[index-name]:upsert(tuple, ops)
ops
- an array of update-like operations, such as set, +, - (see below)tuple
- default tuple to insert if there is no tuple matching the primary key taken from this tuple
Supported operations are:
- '+' - add a value to a numeric field. If the filed is not numeric, it's changed to 0 first. If the field does not exist, the operation is skipped. There is no error in case of overflow either, the value simply wraps around in C style. The range of the
integer is MsgPack: from -2^63 to 2^64-1 - '-' - same as the previous, but subtract a value
- '=' - assign a field to a value. The field must exist, if it does not exist, the operation is skipped.
- '!' - insert a field. It's only possible to insert a field if this create no nil "gaps" between fields. E.g. it's possible to add a field between existing fields or as the last field of the tuple.
- '#' - delete a field. If the field does not exist, the operation is skipped.
It's not possible to change with update operations a part of the primary key (this is validated before performing upsert).Error handling
Duplicate in a secondary unique key
We can't possibly modify more than 1 tuple per statement, so we can't implement upsert()
if there are multiple unique keys.
Possible errors from future features: constraints and triggers
upsert() is designed to not return the old/new tuple to the constraint, so whatever
error may happen in a constraint, is not related to the tuple. If we ever implement
foreign key constraints, upsert() would have to be "downgraded" to update()
for spaces with foreign keys.
Open issues
What happens if upsert() creates a tupele which is too large?
If upsert ever leads to panic, it could be a security issue - and we may require statement-level privileges to deal with it.