Skip to main content

Entity

Entities in Rell are persistent data structures that reside in the database, not in memory. This means they offer long-term storage and can be explicitly managed using the create and delete expressions. Structs in Rell are similar to entities in concept but reside in memory, offering temporary data storage within code execution.

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 which are applied when the entity is created, but not specified:

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);

Unique keys and indices

Entities in Rell can be associated with a key or an index, allowing for organized and efficient data access.

  • Keys: A key is a unique identifier for an entity, ensuring its distinctiveness within the blockchain. Declare a set of attributes as a key to guarantee that no two entities share the same combination of values for those attributes. This maintains data integrity and prevents duplicates.
  • Indices: An index facilitates fast querying of entities based on specific attributes. Simply put, an index is a pointer to data in a table. Create an index on specific attributes to significantly speed up queries that filter or retrieve entities based on those attributes. Think of them as specialized pointers that guide you directly to the relevant data.
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';
}
note

Mutating an indexed field may be slow if there are many rows in the table.

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;
}

Relations

Relations in Rell define how entities are connected to each other in the database. They allow you to model the relationships between different entities, ensuring data integrity and efficient querying. There are several types of relations, each serving a specific purpose in organizing and accessing data.

One-to-one

A one-to-one relation ensures that a record in one entity is linked to a single record in another entity. This type of relation is used when an entity needs to have a unique association with another entity.

For example, if we want each user to have a unique residence address:

entity residence {
key user;
key address; // Ensures that each address can be associated with at most one user
}

Here, the user and address both serve as keys, ensuring that each user can have only one address and each address can be assigned to only one user.

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

These functions enforce the one-to-one relationship by ensuring the uniqueness constraint on both user and address.

One-to-many

A one-to-many or many-to-one relation means a single record in one entity can be linked to multiple records in another entity. This type of relation is used when an entity needs to have multiple associations with another entity, and vice versa.

For example, if a user can have multiple residences, and a residence can belong to one user:

entity residence {
key user;
index address;
}

Here, the user is a key, ensuring that each user can have multiple addresses, but each address is indexed for faster querying.

Functions to query this relationship:

//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

This setup allows a user to have multiple addresses and provides a way to retrieve all addresses associated with a user or all users associated with an address.

To reverse the relationship, where multiple users can share a single address:

entity residence {
index user;
key address;
}

This setup allows multiple users to share the same address and provides a way to retrieve all users associated with a particular address.

Many-to-many

A many-to-many relation allows multiple records in one entity to be linked to multiple records in another entity. This type of relation is used when there is a need for a complex association between entities.

For example, if users can have multiple addresses and each address can belong to multiple users:

entity user {
key pubkey;
index name;
}

entity address {
key pubkey;
street: text;
}

entity residence {
key user, address;
}

Here, the composite key (user, address) ensures that each combination of user and address is unique, allowing multiple users to be associated with multiple addresses.

Functions to query this relationship:

//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;
info

If you're interested in learning more about relationships, consider exploring our course Understand Relationships in Rell.

Annotations

You can transform entities into immutable historical records by applying the @log annotation:

@log entity user {
name: text;
}

The @log annotation has the following effects:

  • Transaction context: Automatically adds a transaction attribute of type transaction to each entity instance, capturing its creation within a specific transaction.
  • Immutable nature: Prevents any modifications to attributes or deletion of instances, ensuring the integrity of historical data.
  • Automatic timestamp: Effectively creates a timestamped log of entity events, providing a transparent and auditable history of data changes.

Changing entity definitions

When you start a Rell dapp, 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.

Compatible changes

  • Adding attributes with defaults: Seamlessly introduce new attributes to your entity. Each existing record will receive the defined default value, ensuring predictable behavior.
  • Adding attributes to empty tables: If no data has been stored yet, you can freely add additional attributes, even without default values, for future use.
  • Removing attributes: Declutter your entity by removing attributes. The corresponding database column will be retained, allowing you to access historical data if needed.
  • Changing attribute mutability: You can alter an attribute's mutability (from non-mutable to mutable or vice versa) for entities that are not annotated with @log. This provides flexibility in managing attribute update behaviors.

Incompatible changes

  • Key/Index modifications: Changes to keys or indices can impact data integrity and query performance. Consider alternative approaches like creating new entities or using versions if significant structural modifications are necessary.

  • Attribute type changes: Changing an attribute's type risks compatibility issues and data corruption. Plan your entity structures effectively to minimize the need for such adjustments.

    Adding/Removing @log annotation: Once you choose the 'immutable history' path with @log, you can't switch back. Adding the annotation to existing entities or removing it entirely is not supported.