|
| 1 | +# AML: Azimutt Markup Language |
| 2 | + |
| 3 | +**AML is a text language allowing you to define your database schema in the quickest and most intuitive way.** |
| 4 | + |
| 5 | +It was built with the minimal boilerplate to be fast to write but also to limit the learning curve and possible mistakes. It's, of course, the language used in [Azimutt](https://azimutt.app) to define or extend your schema (along with other data sources like SQL code, database connection or even JSON). |
| 6 | + |
| 7 | +Here is a typical example of what it looks like: |
| 8 | + |
| 9 | +```aml |
| 10 | +users | store every user # AML comment |
| 11 | + id uuid pk |
| 12 | + login varchar(30) unique |
| 13 | + role user_role(guest, member, admin)=guest |
| 14 | + email varchar nullable |
| 15 | + group_id fk groups.id |
| 16 | + created timestamp=now() |
| 17 | +``` |
| 18 | + |
| 19 | +As you can see, almost all characters are your own content, no ceremony. |
| 20 | + |
| 21 | +Now let's dig more into it and see all the features... |
| 22 | +If you want to try them live, just create a [new empty project](https://azimutt.app/projects/create) on Azimutt. |
| 23 | + |
| 24 | +- [Tables](#tables) |
| 25 | +- [Columns](#columns) |
| 26 | +- [Relations](#relations) |
| 27 | +- [Comments](#comments) |
| 28 | +- [Philosophy & Conventions](#philosophy--conventions) |
| 29 | +- [Full example](#full-example) |
| 30 | + |
| 31 | +## 🔖 Tables |
| 32 | + |
| 33 | +Defining a table is the most common thing you will do with AML, and it's as simple as writing its name: |
| 34 | + |
| 35 | +```aml |
| 36 | +my_table_name |
| 37 | +``` |
| 38 | + |
| 39 | +This name should be without space or dot but to allow them you can use `"`. |
| 40 | +You can prefix your table name with its schema name followed by a dot if you want, the same rules apply to it (no space or dot, or use `"`). |
| 41 | +Finally, you can add a `*` at the end of the table name to mark it as a *view* instead of a table. |
| 42 | + |
| 43 | +Here is some examples of tables definitions: |
| 44 | + |
| 45 | +```aml |
| 46 | +users |
| 47 | +public.users |
| 48 | +"user table" |
| 49 | +"users.sql" |
| 50 | +users_view* |
| 51 | +"demo 2"."users 2" |
| 52 | +``` |
| 53 | + |
| 54 | +As you can see, it's possible to define one table per line and with as little as one word, it's quite convenient to quickly write what you have in mind! |
| 55 | + |
| 56 | +## 🔖 Columns |
| 57 | + |
| 58 | +Tables are great, but without columns, they are a bit poor... |
| 59 | +A column can be defined as simple as its name with a 2 space indentation: |
| 60 | + |
| 61 | +```aml |
| 62 | +users |
| 63 | + id |
| 64 | +``` |
| 65 | + |
| 66 | +Here you are, you just defined a `users` table with an `id` column 🎉 |
| 67 | +It's very convenient to write very fast all the tables and columns you have in mind. |
| 68 | +As for the table and schema names, if you need space or dot inside, you can use `"` around it. |
| 69 | + |
| 70 | +Of course, you may want to provide additional details on columns, here is its full structure: |
| 71 | + |
| 72 | +```aml |
| 73 | + name type nullable pk index unique check fk table.column | notes # comment |
| 74 | +``` |
| 75 | + |
| 76 | +Every part being optional except the name. Some parts may have additional options. |
| 77 | +Let's detail them... |
| 78 | + |
| 79 | +- [Column type](#column-type) |
| 80 | +- [Column modifiers](#column-modifiers) |
| 81 | +- [Column relation](#column-relation) |
| 82 | + |
| 83 | +### Column type |
| 84 | + |
| 85 | +There is no SQL validation for it, you can write anything you want and define meaningful types names to help understanding your schema. Of course, the same rule applies, if you need spaces or dots inside, you will need to use `"` around. |
| 86 | + |
| 87 | +If the type has a *default value*, you can write it just after an `=` sign (ex: `int=0`). |
| 88 | +If the type has *enumerable values*, you can write them in parenthesis (ex: `role(guest, admin)`). |
| 89 | + |
| 90 | +Here are some valid examples: |
| 91 | + |
| 92 | +- `int`: one of the most basic type ^^ |
| 93 | +- `"character varying"`: a type with space in it |
| 94 | +- `varchar(12)`: a type with a precision (not treated as enum if only one or two values which are integers) |
| 95 | +- `decimal(5, 2)`: another kind of precision |
| 96 | +- `varchar=y`: a default value |
| 97 | +- `state(active,disabled)`: an enum |
| 98 | +- `role(guest, admin)=guest`: an enum with a default value |
| 99 | + |
| 100 | +### Column modifiers |
| 101 | + |
| 102 | +As seen in the [Columns](#columns) section, a column can have several modifiers. |
| 103 | + |
| 104 | +`nullable` is a simple flag, telling the column can contain `null` values. In AML, by default columns are not nullable, this is the opposite of SQL but much more convenient and quick to write, as most of your columns should not be nullable. |
| 105 | + |
| 106 | +`pk` means *primary key*, use it to identify a column as a table primary key. You can use this flag in several columns to create a composite primary key. |
| 107 | + |
| 108 | +`index`, `unique` and `check` have a similar behavior. You can use them as flag to express the column property, but you can also give them a name using the `=` sign (ex: `unique=user_slug`). This name will be shown in the interface but also will allow to create a constraint on several columns sharing the same constraint name. |
| 109 | + |
| 110 | +For the `check` constraint, you can use this name (or label) to define the condition. |
| 111 | + |
| 112 | +Here is some examples: |
| 113 | + |
| 114 | +```aml |
| 115 | +users |
| 116 | + id uuid pk |
| 117 | + first_name varchar unique=name |
| 118 | + last_name varchar unique=name check="LEN(last_name) > 3" |
| 119 | + bio text nullable |
| 120 | +
|
| 121 | +credentials |
| 122 | + provider_id varchar pk |
| 123 | + provider_key varchar pk |
| 124 | + user_id fk users.id |
| 125 | +``` |
| 126 | + |
| 127 | +### Column relation |
| 128 | + |
| 129 | +Some columns can reference another column, eventually using a SQL foreign key. In AML, this can be done with the `fk` keyword (shortcut for *foreign key* 😉) in the column definition or as a standalone instruction (see [Relations](#relations)). |
| 130 | +This relation means a column references another one, and thus can be used in a join clause. But it does not necessarily imply there is a real foreign key in the database schema. |
| 131 | + |
| 132 | +To define a relation in the column definition, just add the `fk` keyword with a column reference after like this: `fk table.column`, or with the table schema: `fk schema.table.column`. |
| 133 | + |
| 134 | +In the case of [polymorphic relations](https://devdojo.com/tnylea/understanding-polymorphic-relationships), you can define several relations starting from a column, but the additional ones should be defined using standalone instructions (see [Relations](#relations)). |
| 135 | +*For better consistency, it's recommended to only use standalone relations to define polymorphic relations even if not required by the language.* |
| 136 | + |
| 137 | +For [composite relations](https://www.ibm.com/docs/en/informix-servers/14.10?topic=format-defining-composite-primary-foreign-keys) (involving several columns), they are **not supported** yet in AML or Azimutt. This is a planned evolution but no timeline has been decided as many other important features are still to come. If you need them, please reach out, so we can plan them. |
| 138 | + |
| 139 | +## 🔖 Relations |
| 140 | + |
| 141 | +As seen before, relations can be defined [inside the column definition](#column-relation), and it's often the most efficient way to do so. But, sometimes, is useful or needed to define them as a standalone instruction. |
| 142 | + |
| 143 | +Here is how to do it: |
| 144 | + |
| 145 | +```aml |
| 146 | +fk projects.owner -> users.id |
| 147 | +``` |
| 148 | + |
| 149 | +The standalone relation instruction should start with the `fk` keyword and then have two column references separated by a simple arrow (`->`). |
| 150 | + |
| 151 | +This is useful to define multiple relations from a column (in case of polymorphic relations) or define relations between columns that are not defined in AML (useful to declare relations that were not found in SQL or database sources because they didn't have a foreign key). |
| 152 | + |
| 153 | +Here is an example: |
| 154 | + |
| 155 | +```aml |
| 156 | +requests |
| 157 | + id uuid |
| 158 | + kind varchar |
| 159 | + item_type varchar |
| 160 | + item_id integer |
| 161 | +
|
| 162 | +fk requests.item_id -> users.id |
| 163 | +fk requests.item_id -> talks.id |
| 164 | +fk requests.item_id -> logins.id |
| 165 | +``` |
| 166 | + |
| 167 | +## 🔖 Comments |
| 168 | + |
| 169 | +Having comments on tables and relations can be a great help for people to understand how the database works. In AML you can define a *SQL comment* using the `|` symbol at the end of your table or column definition. It will be visible directly inside the interface. |
| 170 | + |
| 171 | +For example: |
| 172 | + |
| 173 | +```aml |
| 174 | +users | store all our users |
| 175 | + id | column to uniquely identify a user |
| 176 | +``` |
| 177 | + |
| 178 | +This is the only special part of AML that doesn't need `"` to contain spaces and dots. |
| 179 | + |
| 180 | +There is also *AML comments* you can use to write explanations you don't want to show in the interface. They are useful to explain why you wrote what you wrote ^^. Such comments are defined with the `#` symbol and should be at the end of the line (everything after is ignored). |
| 181 | +SQL and AML comments can be combined in the same line but the AML one should **always** be after. |
| 182 | + |
| 183 | +Let's see an example: |
| 184 | + |
| 185 | +```aml |
| 186 | +# the user table |
| 187 | +users | store ALL users |
| 188 | + id | unique identifier # not sure if I should put `uuid` or `int` |
| 189 | + name varchar # which size? |
| 190 | + created_at timestamp=now() | never update this column |
| 191 | +``` |
| 192 | + |
| 193 | +## 🔖 Philosophy & Conventions |
| 194 | + |
| 195 | +In order to be the fastest to write, AML have very few keywords and symbols, and they are all very short and preferred in lower case for fluid typing. |
| 196 | +Still, if you want to highlight the difference between keywords and your content (names, types, doc...), you can write AML keywords in upper case to ease reading. |
| 197 | +But it's strongly encouraged to be consistent. |
| 198 | + |
| 199 | +As said in introduction, AML is built to be very intuitive and fast to learn and write. |
| 200 | +If you see possible improvements on the syntax or even features, please don't hesitate to [post an issue](https://github.com/azimuttapp/azimutt/issues), so we could improve it for everyone ❤️ |
| 201 | +If you like it or want to give feedback, we will be very pleased to hear about you. Please get in touch with us on Twitter: [@azimuttapp](https://twitter.com/azimuttapp). |
| 202 | + |
| 203 | +## 🔖 Full example |
| 204 | + |
| 205 | +Now everything has been explained, let's write a meaningful example to give you a larger view of what it looks like to use AML as schema definition. |
| 206 | + |
| 207 | +Let's define a hypothetical e-commerce shop: |
| 208 | + |
| 209 | + |
| 210 | + |
| 211 | +```aml |
| 212 | +# |
| 213 | +# Identity domain |
| 214 | +# |
| 215 | +
|
| 216 | +users |
| 217 | + id uuid pk |
| 218 | + slug varchar unique | user identifier in urls |
| 219 | + role user_role(customer, staff, admin) |
| 220 | + name varchar |
| 221 | + avatar url |
| 222 | + email varchar unique |
| 223 | + email_validated timestamp nullable |
| 224 | + phone varchar unique |
| 225 | + phone_validated timestamp nullable |
| 226 | + bio text nullable |
| 227 | + company varchar nullable |
| 228 | + locale locale(en, fr) |
| 229 | + created_at timestamp |
| 230 | + updated_at timestamp |
| 231 | + last_login timestamp |
| 232 | +
|
| 233 | +credentials |
| 234 | + provider_id provider(google, facebook, twitter, email) pk |
| 235 | + provider_key varchar pk | user id in provider system |
| 236 | + hasher hash_method(md5, sha1, sha256) |
| 237 | + password_hash varchar |
| 238 | + password_salt varchar |
| 239 | + user_id uuid fk users.id |
| 240 | +
|
| 241 | +social_profiles |
| 242 | + user_id uuid fk users.id |
| 243 | + platform social_platform(facebook, twitter, instagram, slack, github) |
| 244 | + platform_user varchar |
| 245 | + created_at timestamp |
| 246 | +
|
| 247 | +# |
| 248 | +# Catalog domain |
| 249 | +# |
| 250 | +
|
| 251 | +categories |
| 252 | + id uuid pk |
| 253 | + slug varchar unique | category identifier in urls |
| 254 | + name varchar |
| 255 | + description text |
| 256 | + tags varchar[] |
| 257 | + parent_category uuid fk categories.id |
| 258 | + created_at timestamp |
| 259 | + updated_at timestamp |
| 260 | +
|
| 261 | +products |
| 262 | + id uuid pk |
| 263 | + category_id uuid nullable fk categories.id |
| 264 | + title varchar |
| 265 | + picture varchar |
| 266 | + summary text |
| 267 | + description text |
| 268 | + price number | in Euro |
| 269 | + discount_type discount_type(none, percent, amount) |
| 270 | + discount_value number |
| 271 | + tags varchar[] |
| 272 | + created_at timestamp |
| 273 | + updated_at timestamp |
| 274 | +
|
| 275 | +reviews |
| 276 | + id uuid pk |
| 277 | + user_id uuid fk users.id |
| 278 | + product_id uuid fk products.id |
| 279 | + rating int index | between 1 and 5 |
| 280 | + comment text |
| 281 | + created_at timestamp |
| 282 | +
|
| 283 | +# |
| 284 | +# Cart domain |
| 285 | +# |
| 286 | +
|
| 287 | +carts |
| 288 | + id uuid pk |
| 289 | + status cart_status(active, ordered, abandonned) |
| 290 | + created_at timestamp=now |
| 291 | + created_by uuid fk users.id |
| 292 | + updated_at timestamp |
| 293 | +
|
| 294 | +cart_items |
| 295 | + cart_id uuid pk fk carts.id |
| 296 | + product_id uuid pk fk products.id |
| 297 | + price number |
| 298 | + quantity int check="quantity > 0" | should be > 0 |
| 299 | + created_at timestamp |
| 300 | +
|
| 301 | +# |
| 302 | +# Order domain |
| 303 | +# |
| 304 | +
|
| 305 | +orders |
| 306 | + id uuid pk |
| 307 | + user_id uuid fk users.id |
| 308 | + created_at timestamp |
| 309 | +
|
| 310 | +order_lines |
| 311 | + id uuid pk |
| 312 | + order_id uuid fk orders.id |
| 313 | + product_id uuid fk products.id | used as reference and for re-order by copy data at order time as they should not change |
| 314 | + price number | in Euro |
| 315 | + quantity int check="quantity > 0" | should be > 0 |
| 316 | +``` |
| 317 | + |
| 318 | +Hope you enjoyed AML, happy hacking on [Azimutt](https://azimutt.app)! |
0 commit comments