
# Index et contraintes

Au-delà des champs, une table peut déclarer une clé primaire, des index pour les performances des requêtes et l'unicité, du sucre syntaxique pour les contraintes d'unicité, des contraintes CHECK pour les règles inter-champs et des clés étrangères composites.

## Clé primaire

Définissez `primaryKey` pour contrôler la façon dont chaque ligne est identifiée de manière unique. Lorsqu'elle est omise, une colonne `id` générée automatiquement est la clé primaire.

| Propriété | Description                                                                                                                               |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `type`    | Stratégie de génération : `auto-increment` (entiers séquentiels), `uuid` (identifiants uniques aléatoires) ou `composite` (multi-champs). |
| `field`   | Nom du champ pour une clé mono-colonne. Utilisé uniquement avec `auto-increment` ou `uuid`. Doit correspondre à `^[a-z][a-z0-9_]*`.       |
| `fields`  | Tableau de noms de champs pour une clé primaire composite.                                                                                |

```yaml
primaryKey: { type: auto-increment, field: id }
```

```yaml
# Clé primaire composite
primaryKey: { type: composite, fields: [tenant_id, slug] }
```

## Index

`indexes` est un tableau de définitions d'index. Les noms d'index doivent être uniques au sein de la table.

| Propriété | Description                                                                                                                           |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `name`    | Nom de l'index. Doit correspondre à `^[a-z][a-z0-9_]*`. Utilisez des noms descriptifs comme `idx_users_email`.                        |
| `fields`  | Tableau de noms de champs couverts par l'index. Au moins un requis.                                                                   |
| `unique`  | Booléen. Lorsqu'il vaut `true`, applique l'unicité sur les champs indexés.                                                            |
| `where`   | Clause SQL `WHERE` pour un **index partiel** — indexe uniquement les lignes satisfaisant la condition (par ex. `deleted_at IS NULL`). |

```yaml
indexes:
  - { name: idx_users_email, fields: [email], unique: true }
  - { name: idx_orders_status, fields: [status] }
  # Index unique partiel — unicité uniquement sur les lignes non supprimées
  - { name: idx_active_slug, fields: [slug], unique: true, where: 'deleted_at IS NULL' }
```

## Contraintes d'unicité (`unique` de premier niveau)

Le tableau `unique` au niveau de la table est du sucre syntaxique pour déclarer l'unicité sur un ou plusieurs champs. Les entrées mono-champ se replient dans l'équivalent de `field.unique = true` ; les entrées multi-champs deviennent un index btree unique.

```yaml
unique:
  - { fields: [slug] } # unicité mono-champ
  - { fields: [tenant_id, slug] } # unicité composite
```

## Contraintes CHECK

`constraints` applique des règles métier complexes au niveau de la base de données avec des expressions booléennes SQL. Les noms de contraintes doivent être uniques au sein de la table et correspondre à `^[a-z][a-z0-9_]*`.

| Propriété | Description                                                                       |
| --------- | --------------------------------------------------------------------------------- |
| `name`    | Nom de contrainte unique (minuscules, alphanumérique, underscores).               |
| `check`   | Expression booléenne PostgreSQL devant s'évaluer à TRUE pour des données valides. |

```yaml
constraints:
  - { name: chk_price_positive, check: 'price > 0' }
  - { name: chk_end_after_start, check: 'end_date > start_date' }
  - { name: chk_active_members_have_email, check: '(is_active = false) OR (email IS NOT NULL)' }
```

## Clés étrangères composites

Les clés étrangères mono-colonne sont créées automatiquement à partir des champs [`relationship`](/fr/docs/relational-fields). Utilisez `foreignKeys` pour des références **multi-colonnes** vers une clé primaire composite d'une autre table.

| Propriété          | Description                                                                               |
| ------------------ | ----------------------------------------------------------------------------------------- |
| `name`             | Nom de la contrainte (minuscules, underscores, 63 caractères max).                        |
| `fields`           | Colonnes locales formant la clé étrangère.                                                |
| `referencedTable`  | Table parente contenant les colonnes référencées.                                         |
| `referencedFields` | Colonnes de la table parente qui sont référencées.                                        |
| `onDelete`         | Action référentielle à la suppression : `cascade`, `set-null`, `restrict` ou `no-action`. |
| `onUpdate`         | Action référentielle à la mise à jour : `cascade`, `set-null`, `restrict` ou `no-action`. |

```yaml
foreignKeys:
  - name: fk_permissions_tenant_user
    fields: [tenant_id, user_id]
    referencedTable: tenant_users
    referencedFields: [tenant_id, user_id]
    onDelete: cascade
    onUpdate: cascade
```

:::callout
**Index vs contraintes.** Utilisez `indexes` pour les performances des requêtes et l'unicité mono/multi-champs ; utilisez `constraints` (CHECK) pour la validation conditionnelle et inter-champs que l'unicité ne peut exprimer. Les deux sont validés par rapport aux noms de champs de la table au moment de la configuration.
:::
