Entity
You store values (instances) of an entity in Rell in a database, not in
memory. You can create or delete them explicitly using the Rell
create
and delete
expressions. An in-memory equivalent of an entity
in Rell is a struct.
A variable of an entity type holds an ID (primary key) of the corresponding database record but not its attribute values.
entity company {
name: text;
address: text;
}
entity user {
first_name: text;
last_name: text;
year_of_birth: integer;
mutable salary: integer;
}
If the attribute type is not specified, it's the same as the attribute name:
entity user {
name; // built-in type "name"
company; // user-defined type "company" (error if no such type)
}
Attributes may have default values:
entity user {
home_city: text = 'New York';
}
You can use the rowid
implicit attribute (type rowid
) to get an entity value's ID (primary database key).
val u = user @ { .name == 'Bob' };
print(u.rowid);
val alice_id = user @ { .name == 'Alice' } ( .rowid );
print(alice_id);
Keys and indices
Entities in Rell can be associated with key
and index
clauses, allowing for organized and efficient data access. A key
is a unique identifier for an entity, ensuring its distinctiveness within the blockchain. An index
, on the other hand, facilitates fast querying of entities based on specific attributes. Simply put, an index is a pointer to data in a table.
entity user {
name: text;
address: text;
key name;
index address;
}
Keys and indices may have multiple attributes:
entity user {
first_name: text;
last_name: text;
key first_name, last_name;
}
In this example, you can have multiple with the same first_name
or last_name
but not with the combination of both.
You can specify mutability within a key or index clause. Here one can also set a default value:
entity address{
index mutable city: text = 'Rome';
}
You can combine attribute definitions with key
or index
clauses, but such definition has restrictions (can't specify mutable
):
entity user {
key first_name: text, last_name: text;
index address: text;
}
The entity represents a database object. An example to visualize how to maintain relations between objects is as follows:
entity user {
key pubkey;
index name;
}
entity address {
key pubkey;
street: text;
}
entity residence {
key user, address;
}
//both queries will result lists of records, as it is a many-to-many relationships
//they might be of different sizes. The constraint is that the composite of the two keys are unique.
function get_addresses(user): list<address> = residence @* { user }.address;
function get_users(address): list<user> = residence @* { address }.user;
is many-to-many relations, a user can have more than one residence. You can implement many-to-one relations by changing the key:
entity residence {
key user;
index address;
}
//There is a unique constraint on the user key.
function get_addresses(user): list<address> = residence @* { user }.address; //will only return zero or one addresses
function get_users(address): list<user> = residence @* { address }.user; // will return zero to n users
And for completeness, one-to-many would be reverse of many-to-one:
entity residence {
index user;
key address;
}
It also works without making the address an entity, for example:
entity user { key name; }
// allow user to have many tags
entity user_tag {
key user, tag: text;
}
and one-to-one (optional) as:
entity residence {
key user;
key address; //make sure that address can be claimed by at most one user
}
There is a unique constraint on the user key and the address key.
function get_addresses(user): list<address> = residence @* { user }.address; //will only return zero or one addresses
function get_users(address): list<user> = residence @* { address }.user; // will only return zero to one users
Entity annotations
@log entity user {
name: text;
}
The @log
annotation has the following effects:
- adds a special attribute
transaction
of typetransaction
to the entity - result of
op_context.transaction
(current transaction) sets thetransaction
value - an entity can't have mutable attributes
- can't delete the values
Changing existing entities
When you start a Rell app, a database structure update happens: tables for new entities and objects get created, and tables for existing ones get altered. There are limitations on changes that you can make in the existing entities and objects.
What's allowed:
- Adding an attribute with a default value (a column gets added to the table and initialized with the default value).
- Adding an attribute without a default value - only if there are no records in the table.
- Removing an attribute (database column isn't dropped; you can read the attribute later).
What's not allowed:
- Any changes in keys/indices, including adding a new key/index attribute, making an existing attribute into a key/index, removing an attribute from an index, etc
- Changing attribute's type
- Adding/removing the
@log
annotation