Entities are nodes in Entity-Relationship model, they are often used to model tables or collections in databases.
Here is the simplest entity definition, just its name:
users
The name is an identifier and can be prefixed with a namespace:
core.public."user list"
Entities can contain attributes (corresponding to columns or fields in most databases). They are defined one per line with two spaces indentation:
users
id
name
Attributes can have options such as type, nullability, constraints, relations and more. Here are some examples:
users
id uuid pk
name varchar
email varchar(256) unique
bio text nullable
role user_role(admin, guest)=guest index
profile_id int -> profiles(id)
Metadata #
Entities can also have custom properties and documentation (as well as attributes):
users {color: red, tags: [pii, sensitive], deprecated} | storing all users
id int pk {autoIncrement} | the user id
name
There are specific properties for entities, for examples to define view:
admins {view: "SELECT * FROM users WHERE role = 'admin'"}
id
name
Alias #
Finally, entities can be aliased to simplify their references, entity aliases are also identifiers:
db1.referential.identity.accounts as users
id
name
posts
id
author -> users(id) # refers to `db1.referential.identity.accounts` entity
Attribute #
Attributes define possible values inside an entity, such as columns in relational databases and fields in document ones.
They are defined with 2 space indentation under the entity they belong to, but they can also have several nesting levels.
The only required thing is their name, which is an identifier. After they have several options for the type, nullable, constraints and relations.
Here is an example:
users
id # only the name
name varchar # the name and the type
email varchar unique # the name, type and unique constraint
bio nullable # the name and allowing null (by default not null constraint is applied)
profile_id -> profiles(id) # the name and relation
Attribute type #
The attribute type should come just after the attribute name, if there is space or special character inside the type, surround it with "
, here are some examples:
events
id uuid
name varchar(50)
age int
rating decimal(5,2)
details json
tags "varchar[]"
created_at "timestamp with time zone"
You can define the default values for the attribute with the =
symbol:
users
id uuid
name varchar(50)=John
age int=0
rating decimal(5,2)=0.5
details json="{}"
tags "varchar[]"="[]"
admin boolean=false
created_at "timestamp with time zone"=`now()`
Known types are automatically inferred:
- boolean for
true
andfalse
- number when only numbers and one dot (at most)
- object when starting with
{
- array when starting with
[
- expression when starting with backticks (
`
) - string otherwise, use
"
for multi-word string
Custom types can be defined in standalone and used for an attribute:
type post_status (draft, publiched, archived)
posts
id uuid
status post_status=draft
But enums can also be defined inline with the attribute:
posts
id uuid
status post_status(draft, publiched, archived)=draft
code post_code(0, 1, 2)=2
In this case, they inherit the namespace of the entity, and of course, they can be reused elsewhere (but in this case it's best to define them standalone).
Not null #
Contrary to SQL, in AML the attributes come with the NOT NULL constraint by default.
To remove it, you can mark the attribute as nullable
. This "not constraint" should come after the attribute name and type (if present).
Here are some examples:
profiles
id uuid
user_id uuid -> users(id)
company nullable
company_size int nullable
Index and constraint #
Entity attributes may have constraints and AML allows defining them, though not as detailed as SQL.
They come in this order: primary key, unique, index, check and relation, but most of the time you will have just one per attribute ^^
Here is an example:
users
id uuid pk # define a primary key constraint
email varchar unique # define a unique constraint on email attribute
name varchar index # define an index for the name attribute
age int check # define a check constraint for the age attribute
profile_id uuid -> profiles(id) # define a relation for the profile_id attribute
Check should hold a predicate (even if not strictly required in AML), you can define it as an expression in parentheses:
users
id uuid pk
age int check(`age > 0`)
Constraints can be named using the =
symbol:
users
id uuid pk=users_pk
email varchar unique=users_email_uniq
name varchar index=users_name_idx
age int check(`age >= 0`)=age_chk
profile_id uuid -> profiles(id)
Constraints with the same name are put together to form a composite constraint. Only the primary key doesn't need this as there is just one per an entity:
users # unique constraint on first_name AND last_name
id uuid pk
first_name varchar unique=users_name_uniq
last_name varchar unique=users_name_uniq
user_roles # composite primary key on user_id and role_id
user_id uuid pk -> users(id)
role_id uuid pk -> roles(id)
For now, AML doesn't allow additional properties on indexes and constraints, this is in thinking.
We plan using parentheses on index
and unique
to define custom properties like:
deleted_at timestamp nullable index(kind: HASH, where: `not null`, include: [deleted_by])=soft_delete_idx
We also plan to allow defining standalone constraints to be more flexible.
Let us know what you think, what are your needs and what seems the most intuitive to you.
Nested attribute #
Attributes may have nested attributes, this is especially useful to define the schema of complex objects for document database or json
columns.
Nested attributes are just like other attributes, just with an additional indentation level under the attribute they belong to. Here is how they look:
users
id uuid pk
name varchar
details json
github_url varchar nullable unique
twitter_url varchar nullable unique
company json nullable
id uuid -> companies(id)
name varchar index=users_company_name_idx
size number
job varchar
address json nullable
no number
street varchar
city varchar
zipcode number
country varchar
gender varchar nullable
age number nullable