Skip to main content

UPDATE statement

It is only possible to update the value of an attribute that has been explicitly marked with mutable:

entity my_entity {
key my_key: text;
mutable my_mutable: text;
}

If you have a reference to an entity, updating an attribute is as simple as making an assignment:

my_entity_instance.my_mutable = "new value";

This corresponds to the following SQL statement:

UPDATE "c0.my_entity" A00 SET "my_mutable" = ? WHERE A00."rowid" = ?

If you have multiple mutable attributes to update, assigning them one by one may be tempting, causing a new database roundtrip each time. A better approach is to use the update keyword in Rell. It works similar to the at-operator.

FROM        CARDINALITY  WHERE         WHAT
update entity_name @ { condition } ( .my_mutable = "new value" );

The WHAT part has been replaced with an assignment, and the TAIL part is omitted.

This way, we can update several fields in a single database roundtrip. If we already have a reference to an entity, we can pass it as the condition $ == entity_instance.

Example

Let's have a look at a concrete example:

entity user_info {
key name;
key pubkey;
mutable address: text;
}

operation update_address(name, new_address: text) {
val user = find_and_check_signer(name);
user.address = new_address;
}

function find_and_check_signer(name): user_info {
val user_pubkey = user_info @ { name } (user = $, pubkey = .pubkey);
require(op_context.is_signer(user_pubkey.pubkey), "User must sign operation");
return user_pubkey.user;
}

Here, we have a user entity with a unique key in its name, a public key, and a mutable address property. In operation update_address, we want to find the user and update the address. The function find_and_check_signer thus retrieves not only the user entity with a specific name in the first at-expression. It also fetches the pubkey property to be able to require a signature matching this key without making an additional SQL select. The new address is then updated with a simple assignment.

Let's say that we instead want the address split into a street name and a zip code. The new model would thus look like:

entity user_info {
key name;
key pubkey;
mutable street_address: text;
mutable zip_code: integer;
}

We would then update our operation to:

operation update_address(name, new_street_address: text, new_zip_code: integer) {
val verified_user = find_and_check_signer(name);
update user_info @ { $ == verified_user } (
.street_address = new_street_address,
.zip_code = new_zip_code
);
}

In this way, we can update both attributes at once.

tip

Use the update operation in combination with { $ == <instance> } to be able to update several attributes in a single database roundtrip when possible.

Practical example

The example provided in this section aims to create a new user, update this user, and validate the updated data using the public key of the user.

Add the user_info entity to the entities.rell file stored in the src/main directory:

src/main/entities.rell
entity user_info {
key name;
key pubkey;
mutable street_address: text;
mutable zip_code: integer;
}

Next, add the find_and_check_signer function to the src/main/functions.rell file. This function validates the public key of a user:

src/main/functions.rell
function find_and_check_signer(name): user_info {
val user_pubkey = user_info @ { name } (user = $, pubkey = .pubkey);
require(op_context.is_signer(user_pubkey.pubkey), "User must sign operation");
return user_pubkey.user;
}

Then, add the following code to the src/main/operations.rell file. This operation creates a new user:

src/main/operations.rell
operation create_user_info(name: text, pubkey: byte_array, street_address: text, zip_code: integer) {
create user_info( name, pubkey, street_address, zip_code );
}

Additionally, we must update the user information using the update_address function, which relies on verifying the user's public key. Add the code below to the src/main/operations.rell file:

operation update_address(name, new_street_address: text, new_zip_code: integer) {
val verified_user = find_and_check_signer(name);
update user_info @ { $ == verified_user } (
.street_address = new_street_address,
.zip_code = new_zip_code
);
}

We also need to create a query that retrieves information about all existing users. Add the code provided below to the src/main/queries.rell file.

src/main/queries.rell
query get_users_info() = user_info @* { } ( $.to_struct() );

To interact with the database we need to run the node:

chr node start

Generate a new key-pair using the corresponding command:

chr keygen --file .secret

Invoke the create_user_info function to create new users:

chr tx create_user_info Artem 028A589B9EAA836ECFC52C59039A04231DBA4925873803BCD9B5E7151CF8611D89 "Drottninggatan 2" "100000"
chr tx create_user_info Erik 0835A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6C7D8E9F0A1B2C3D4E5F6A7"Stureplan 52" "100001"

Update the user address using the update_address operation:

chr tx update_address Erik "Stureplan 60" "200002" --secret .secret

Then request all existing users:

chr query get_users_info

The result of this query looks as follows:

result
[
[
"name": "Artem",
"pubkey": x"028A589B9EAA836ECFC52C59039A04231DBA4925873803BCD9B5E7151CF8611D89",
"street_address": "Drottninggatan 2",
"zip_code": 100000
],
[
"name": "Erik",
"pubkey": x"0835A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6C7D8E9F0A1B2C3D4E5F6A7",
"street_address": "Stureplan 60",
"zip_code": 200002
]
]