Skip to content

upsert: update or insert operation #905

@pmwkaa

Description

@pmwkaa

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.

Metadata

Metadata

Assignees

Labels

featureA new functionality

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions