Relations are edges in Entity-Relationship model. In relational databases, they can be modeled with foreign keys, but not necessarily.

In AML, they can be defined either as an attribute option:

users
  id uuid pk

roles
  id uuid pk
  created_by uuid -> users(id)

user_roles
  user_id uuid pk -> users(id)
  role_id uuid pk -> roles(id)

Or standalone:

users
  id uuid pk

roles
  id uuid pk
  created_by uuid

user_roles
  user_id uuid pk
  role_id uuid pk

rel roles(created_by) -> users(id)
rel user_roles(user_id) -> users(id)
rel user_roles(role_id) -> roles(id)

If the target entity has a single attribute primary key, the target attribute can be omitted (will be assigned to the primary key).

users
  id uuid pk

roles
  id uuid pk
  created_by uuid

user_roles
  user_id uuid pk -> users
  role_id uuid pk -> roles

rel roles(created_by) -> users

Also, if omitted, the attribute type will be defined using the targeted attribute, so this is equivalent to the previous definition:

users
  id uuid pk

roles
  id uuid pk
  created_by

user_roles
  user_id pk -> users
  role_id pk -> roles

rel roles(created_by) -> users

Many to One #

Relations defined with -> are many-to-one relations: several rows can target a single one.

One to One #

Relation with one-to-one cardinality can be defined using the -- symbol. If neither side of the relation has a unique index, AML compiler will issue a warning as the logical definition doesn't match the physical one.

users
  id uuid pk

profiles
  id uuid pk -- users(id)

Or as standalone:

users
  id uuid pk

profiles
  id uuid pk

rel profiles(id) -- users(id)

You can ignore attribute specification when there is single attribute primary key:

rel profiles -- users

Also, a different implementation could be:

users
  id uuid pk

profiles
  id uuid pk
  user_id uuid unique -- users

Many to Many #

Relations with many-to-many cardinality are usually implemented with a join entity having two many-to-one relations like:

users
  id uuid pk

projects
  id uuid pk

user_projects
  user_id pk -> users
  project_id pk -> project

If you don't care about the join entity, you can define it logically in AML with:

users
  id uuid pk

projects
  id uuid pk

rel projects(id) <> users(id)

And even without defining the attribute as there is single attribute primary keys:

rel projects <> users

Nested attributes #

Relations can connect nested attributes as well:

users
  id uuid pk
  details
    twitter_id varchar

companies
  id uuid pk

events
  id uuid pk
  details json
    company json
      id uuid -> companies(id)

tweets
  id uuid pk
  profile varchar -> users(details.twitter_id)

Of course, this also works with standalone relations:

users
  id uuid pk
  details
    twitter_id varchar

companies
  id uuid pk

events
  id uuid pk
  details json
    company json
      id uuid

tweets
  id uuid pk
  profile varchar

rel events(details.company.id) -> companies(id)
rel tweets(profile) -> users(details.twitter_id)

Composite relation #

If you have a composite primary key, you may also want composite foreign keys. You can easily define them by listing all the attributes:

users
  id uuid pk

projects
  id uuid

user_projects
  user_id pk -> users
  project_id pk -> projects

user_project_rights
  user_id pk
  project_id pk
  access project_right(read, write)=read

rel user_project_rights(user_id, project_id) -> user_projects(user_id, project_id)

This kind of relation can only be defined using standalone relation.

Polymorphic relation #

Polymorphic relations are used to target different entities depending on the value of another attribute.

For example, if you want to make a comment system in your app and be able to comment on different entities, you can either create one comment entity for each entity, like:

posts
  id uuid pk
  title varchar

pages
  id uuid pk
  title varchar

post_comments
  id uuid pk
  post_id -> posts
  content text

page_comments
  id uuid pk
  page_id -> pages
  content text

But this can become painful as the number of commentable entities grows, to keep everything consistent or to get all the comments from a user.

Instead, you can create a single comment entity targeting different entities depending on another attribute value (discriminator):

posts
  id uuid pk
  title varchar

pages
  id uuid pk
  title varchar

comments
  id uuid pk
  item_kind comment_kind(posts, pages)
  item_id uuid
  content text

rel comments(item_id) -item_kind=posts> posts(id)
rel comments(item_id) -item_kind=pages> pages(id)

We could even make nested comments with:

rel comments(item_id) -item_kind=comments> comments(id)

Just make sure all your target entities have the same primary key type (used for the item_id attribute).

The value in the item_kind attribute is not always the table name, some ORMs use the model name instead, so it could be Post instead of posts.

Metadata #

Relations can also have custom properties and documentation:

rel projects(created_by) -> users(id) {onDelete: cascade, ignore} | the user creating the project

But this works only for standalone definition, when inline, properties and documentation will be assigned to the attribute ^^