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.
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:
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:
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:
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.
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:
[
[
"name": "Artem",
"pubkey": x"028A589B9EAA836ECFC52C59039A04231DBA4925873803BCD9B5E7151CF8611D89",
"street_address": "Drottninggatan 2",
"zip_code": 100000
],
[
"name": "Erik",
"pubkey": x"0835A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6C7D8E9F0A1B2C3D4E5F6A7",
"street_address": "Stureplan 60",
"zip_code": 200002
]
]