# Une base de données et la mise en place de MySQL : tables, clés, Docker et un pilote<no value>

## Dans cette leçon

Cette leçon se compose de deux chapitres, dans le même ordre que les diapositives :

1. **Chapitre 1** — ce qu'est une base de données : table, ligne, colonne, Primary Key et types de données
2. **Chapitre 2** — la mise en place de MySQL : Docker, Database Client et un pilote

## Chapitre 1 : Ce qu'est une base de données — table, ligne, colonne, Primary Key et types de données

### Qu'allons-nous apprendre ?

- **Pourquoi** on a besoin d'une base de données (Database).
- Ce qu'est une **base de données relationnelle** (Relational Database).
- **Table**, **Row** (ligne), **Column** (colonne).
- **La clé primaire** (Primary Key).
- Les **types de données** (Data Types) de base.

{{< box "tip" "Ce que nous n'apprendrons pas ici (volontairement)" >}}
Ici, nous n'apprenons **que les bases nécessaires au CRUD** : une connexion, une seule table, et créer, lire, mettre à jour et supprimer. Nous n'apprendrons **pas** JOIN, GROUP BY, HAVING, les sous-requêtes, la normalisation avancée, les index ni les procédures stockées. Vous les apprendrez en profondeur plus tard dans le parcours.
{{< /box >}}

### Pourquoi en avons-nous besoin ?

Dans le thème des fichiers, nous avons enregistré des données dans un fichier JSON. Cela fonctionnait pour un seul programme tournant sur notre ordinateur. Sur un vrai serveur, cela casse :

| Un problème avec un fichier JSON | Ce qui se passe |
|---|---|
| **Tout ou rien à la lecture** | Pour trouver un étudiant parmi un million, on charge **tout** le fichier en RAM. |
| **Tout ou rien à l'écriture** | Pour modifier un champ, on réécrit **tout** le fichier. |
| **Deux utilisateurs à la fois** | Deux requêtes lisent le fichier, chacune modifie quelque chose, et chacune écrit. **La modification de la première est effacée.** |
| **Un plantage au milieu de l'écriture** | Le fichier reste à moitié écrit, et corrompu. |
| **Pas de règles** | Rien n'empêche deux étudiants avec le même e-mail, ou un champ manquant. |
| **Les requêtes** | « Tous les étudiants de Haïfa de plus de 20 ans » = une boucle dans notre code, sur toutes les données. |

Une **base de données** résout tout cela : lire et écrire **exactement** ce qui est nécessaire, gérer de nombreux utilisateurs en même temps, une protection contre les plantages, des règles imposées **dans la base elle-même**, et un langage (SQL) pour les requêtes.

### Explication à partir des bases

#### Qu'est-ce qu'une base de données ?

**Base de données** = un système de stockage **organisé** des données, qui permet de les enregistrer, de les trouver, de les mettre à jour et de les supprimer de façon efficace et fiable. Le logiciel qui la gère s'appelle un **SGBD** (DBMS, Database Management System) : MySQL, PostgreSQL, SQLite...

#### La base de données relationnelle — des tables

Le type le plus répandu est la **base de données relationnelle** : les données sont organisées en **tables**, comme une feuille Excel avec des règles strictes.

```text
Table: students
┌────┬────────┬─────┬────────────────┐
│ id │ name   │ age │ email          │   ← columns (the structure)
├────┼────────┼─────┼────────────────┤
│  1 │ Dana   │  24 │ dana@mail.com  │   ← row = one student
│  2 │ Avi    │  31 │ avi@mail.com   │   ← row
│  3 │ Noa    │  22 │ noa@mail.com   │   ← row
└────┴────────┴─────┴────────────────┘
  ↑
  primary key
```

| Terme | Ce que c'est | En Python (ce que nous connaissons déjà) |
|---|---|---|
| **Table** | Une collection d'enregistrements du même type | Une classe (le modèle) + une liste de tous les objets |
| **Column** (colonne) | Un champ, avec un **nom** et un **type** fixes | Un attribut |
| **Row** (ligne, enregistrement) | Un élément | Un objet / un dict |
| **Primary Key** | Une colonne qui identifie chaque ligne **de façon unique** | `student_id` |

{{< box "analogy" "Analogie : une armoire à dossiers dans un bureau" >}}
**Base de données** = l'armoire. **Table** = un tiroir (« étudiants »). **Colonnes** = le formulaire imprimé sur lequel chaque dossier est construit (nom, âge, e-mail), identique pour tous les dossiers. **Ligne** = un dossier, d'un étudiant. **Primary Key** = le numéro au dos du dossier : il ne se répète jamais, et grâce à lui on sort un dossier sans les parcourir tous.
{{< /box >}}

#### La clé primaire (Primary Key)

**Primary Key** = une colonne (généralement `id`) dont la valeur est :

1. **Unique** — deux lignes n'ont pas la même valeur.
2. **Non vide** — chaque ligne doit avoir une valeur.
3. **Invariable** — un identifiant est pour toute la vie de la ligne.

**Pourquoi ne pas utiliser le nom ?** Parce qu'il y a deux « Dana ». **Et l'e-mail ?** Parce que les gens changent d'e-mail. Un identifiant numérique que la base de données produit elle-même est le choix sûr. (Vous vous souvenez du `Shop` qui produisait des identifiants avec `max + 1` ? La base de données le fait pour nous, et de façon plus sûre.)

Et c'est exactement le `{student_id}` du chemin de l'API : `GET /students/7` = « apporte la ligne dont la Primary Key vaut 7 ».

#### Data Types — les types de données de base

Chaque colonne a un **type**. Dans **MySQL** (la base de données que nous utiliserons), il existe de nombreux types, mais voici ce dont nous aurons besoin :

| Type SQL | Ce qui est stocké | Python | Exemple |
|---|---|---|---|
| **INT** | Un nombre entier | `int` | `id`, `age`, `quantity` |
| **DECIMAL(10, 2)** | Un nombre décimal exact — 2 chiffres après la virgule | `Decimal` (comme `float`, mais exact) | `price` |
| **VARCHAR(100)** | Du texte — jusqu'à 100 caractères | `str` | `name`, `email` |
| (pas de vrai BOOLEAN) | Stocker 0/1 dans un INT | `bool` | `is_active` |
| **NULL** | « Aucune valeur » | `None` | Un champ facultatif vide |

#### Les contraintes de base (Constraints)

Des règles que la **base de données** impose. Même s'il y a un bug dans le code, la base refusera :

| Contrainte | Signification |
|---|---|
| `PRIMARY KEY` | L'identifiant unique de la ligne. |
| `AUTO_INCREMENT` | La base produit l'id elle-même, dans l'ordre croissant. |
| `NOT NULL` | Doit être rempli. |
| `UNIQUE` | Pas deux lignes avec la même valeur (par exemple un e-mail). |

C'est comme la validation dans `__init__` (le thème POO), sauf que c'est **la base de données elle-même** qui l'impose : une couche de protection supplémentaire.

#### SQL — le langage

**SQL** (Structured Query Language, prononcé « sequel » ou « S-Q-L ») = le langage dans lequel on parle à une base de données relationnelle. **Requête** (Query) = une commande en SQL. Nous ne rencontrerons que quatre ou cinq commandes :

```text
CREATE TABLE   → create the structure      (day 3)
INSERT         → C - add a row             (day 3)
SELECT         → R - read rows             (day 3)
UPDATE         → U - change rows           (day 4)
DELETE         → D - remove rows           (day 4)
```

### Syntaxe

**Une conception de table** (le produit de l'étape de conception, avant tout code). Voici à quoi ressemble la conception de la table `students` :

```text
Table: students
| column | type         | constraints                 |
|--------|--------------|-----------------------------|
| id     | INT          | AUTO_INCREMENT PRIMARY KEY  |
| name   | VARCHAR(100) | NOT NULL                    |
| age    | INT          | NOT NULL                    |
| email  | VARCHAR(100) | NOT NULL UNIQUE             |
```

Et voici la commande SQL qui la crée (nous l'écrirons et l'exécuterons dans les leçons suivantes ; ici, c'est seulement pour voir où nous allons) :

```sql
CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

### Premier exemple de code

**D'une classe Python à une conception de table.** La classe `Product` du thème POO :

```python
class Product:
    def __init__(self, sku, name, price, stock=0):
        ...
```

**La traduction :**

| Attribut en Python | Colonne | Type | Contraintes | Pourquoi |
|---|---|---|---|---|
| — | `id` | INT | AUTO_INCREMENT PRIMARY KEY | Un identifiant interne stable |
| `sku` | `sku` | VARCHAR(20) | NOT NULL UNIQUE | Unique, obligatoire |
| `name` | `name` | VARCHAR(100) | NOT NULL | Obligatoire |
| `price` | `price` | DECIMAL(10, 2) | NOT NULL | Un nombre décimal exact |
| `stock` | `stock` | INT | NOT NULL DEFAULT 0 | Un nombre entier, 0 par défaut |

### Analyse du code ligne par ligne

| Décision | Raisonnement |
|---|---|
| Ajouter `id` alors qu'il y a un `sku` | `sku` est un identifiant **métier** qui peut changer (changement de marque). `id` est un identifiant **technique** qui ne change jamais. Il est courant de garder les deux. |
| `sku` avec `UNIQUE` | La règle « le SKU est unique » est imposée dans la base de données, même si un autre code oublie de la vérifier. |
| `price` en DECIMAL(10, 2) | Un prix avec des agorot (les centimes du shekel). `DECIMAL` stocke le nombre **exactement** — sans les problèmes d'arrondi de `float` (`0.1 + 0.2`). |
| Ce qui n'entre **pas** dans la table | Les méthodes (`take`, `restock`) sont du **comportement**, et elles vivent dans le code. La base de données ne stocke que des **données**. |

### Un autre exemple

**De dicts à une table, et des types « désordonnés ».** Des données arrivées d'un fichier JSON :

```python
students = [
    {"name": "Dana", "age": 24, "email": "dana@mail.com", "phone": "050-1234567"},
    {"name": "Avi", "age": "31", "email": "avi@mail.com"},
    {"name": "Noa", "age": 22, "email": "dana@mail.com", "phone": None},
]
```

**Des questions de conception :**

1. **`phone` n'apparaît que dans certaines lignes.** → une colonne **sans** `NOT NULL`. Quand il n'y a pas de valeur, **NULL** est stocké (`None` en Python).
2. **L'`age` d'Avi vaut `"31"` (une chaîne).** → la colonne est `INT`. **Dans le code**, nous convertirons avec `int(...)` avant d'enregistrer (ou Pydantic convertira, dans le thème de l'API des étudiants).
3. **Noa et Dana ont le même e-mail.** → avec `UNIQUE` sur `email`, **la base refusera** d'enregistrer Noa. C'est exactement ce que nous voulons (et nous gérerons cette erreur dans les leçons suivantes).
4. **Il n'y a pas d'`id`.** → `AUTO_INCREMENT` : la base donnera 1, 2, 3.

```text
| column | type         | constraints                |
|--------|--------------|----------------------------|
| id     | INT          | AUTO_INCREMENT PRIMARY KEY |
| name   | VARCHAR(100) | NOT NULL                   |
| age    | INT          | NOT NULL                   |
| email  | VARCHAR(100) | NOT NULL UNIQUE            |
| phone  | VARCHAR(20)  | (nullable)                 |
```

**Pourquoi `phone` est-il un VARCHAR et non un INT ?** Parce que `050-1234567` contient un tiret, et un numéro de téléphone qui commence par 0 perdrait le zéro en INT. **Règle :** un « nombre » sur lequel on ne fait pas de calcul (un téléphone, un code postal, un numéro d'identité) → VARCHAR.

### Erreur courante

**Erreur 1 : une Primary Key « naturelle » qui peut changer ou se répéter.** Un nom, un e-mail, un numéro de téléphone. Le jour où Dana change d'e-mail, tous ceux qui pointaient vers elle par e-mail la « perdent ». **Un id numérique** produit par la base de données.

**Erreur 2 : stocker une liste dans une seule colonne.**

```text
| id | name | grades     |
|  1 | Dana | "90,85,70" |
```

On ne peut pas calculer une moyenne en SQL, on ne peut pas chercher « tous ceux qui ont eu 90 », et il est difficile de mettre à jour une seule note. Dans le monde relationnel, les notes sont **une table séparée** (`grades`, avec un `student_id`). Vous l'apprendrez en profondeur plus tard dans le parcours (les relations entre tables et JOIN). Ici, nous travaillerons avec **une seule table** et des champs simples.

**Erreur 3 : renoncer à `NOT NULL` et `UNIQUE`, « parce que le code vérifie ».** Le code vérifie **aujourd'hui**. Demain, quelqu'un écrira un script d'import qui contourne le code, ou il y aura un bug. La base de données est la dernière ligne de défense.

**Erreur 4 : croire que SQL est « un programme ».** SQL est un langage **déclaratif** : on dit **ce que** l'on veut (« tous les étudiants de Haïfa »), et non **comment** le trouver (pas de boucles). C'est la base de données qui décide comment l'exécuter efficacement.

### Résumé

- Une **base de données** résout les problèmes du fichier JSON : lecture et écriture ciblées, de nombreux utilisateurs en même temps, résistance aux plantages, règles et requêtes.
- **Une base relationnelle** = des tables. **Table** ≈ une classe + tous les objets. **Column** ≈ un attribut (avec un type). **Row** ≈ un objet.
- **Primary Key** = un identifiant unique, non vide et invariable. Généralement `id INT AUTO_INCREMENT PRIMARY KEY`.
- Les types dans MySQL : **INT**, **DECIMAL**, **VARCHAR**, et **NULL** (= `None`). Un booléen se stocke en 0/1. Un téléphone et un code postal se stockent en VARCHAR.
- Les contraintes : **NOT NULL**, **UNIQUE**. La base impose les règles même quand le code se trompe.
- **SQL** = un langage déclaratif. Ici : CREATE TABLE, INSERT, SELECT, UPDATE, DELETE, et c'est tout.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

### Documentation officielle

- [MySQL — Creating and Using a Database](https://dev.mysql.com/doc/refman/8.4/en/database-use.html)
- [MySQL — Data Types](https://dev.mysql.com/doc/refman/8.4/en/data-types.html)
- [MySQL — CREATE TABLE](https://dev.mysql.com/doc/refman/8.4/en/create-table.html)

<!-- docs:end -->

## Chapitre 2 : La mise en place de MySQL — Docker, Database Client et un pilote

### Qu'allons-nous apprendre ?

- Ce qu'est un **serveur de base de données**, et pourquoi nous travaillerons avec **MySQL**.
- Ce qu'est un **conteneur**, et comment faire tourner MySQL dans **Docker** — avec une seule commande.
- Comment voir la base de données depuis VS Code avec l'extension **Database Client**.
- Ce qu'est un **pilote** (Driver), et comment Python se connecte à MySQL.
- Où garder les **informations de connexion** — et pourquoi pas dans le code.

### Pourquoi en avons-nous besoin ?

Avant d'écrire un seul `INSERT`, il faut répondre à des questions de « plomberie » : où tourne la base de données ? Comment notre code y accède-t-il ? Quelles sont les informations de connexion ? Dans un vrai travail, la première chose que l'on reçoit dans un nouveau projet est exactement cela : « voici les informations de connexion à la base de données ». Il faut savoir quoi en faire.

### Explication à partir des bases

#### Une base de données est un serveur

**MySQL** est un **serveur de base de données** : un programme séparé (un processus — souvenez-vous du thème sur le système d'exploitation !) qui tourne en permanence et **écoute sur un port** — pour MySQL, c'est le port **3306**. Notre code n'ouvre pas un fichier — il **se connecte par le réseau** au serveur, exactement comme un navigateur qui se connecte à un site web (le thème des réseaux : IP + port).

| | **MySQL** (un serveur de base de données) |
|---|---|
| Où | Un processus séparé qui écoute sur le port **3306** |
| Comment on se connecte | Par le réseau : **hôte, port, utilisateur, mot de passe** et le nom de la base |
| Qui se connecte | Plusieurs programmes et serveurs — **en même temps** |
| Où on le trouve dans le monde | Les sites web et les systèmes de toutes tailles. PostgreSQL et SQL Server fonctionnent aussi ainsi |

#### Pourquoi Docker ?

Installer MySQL directement sur l'ordinateur est un long processus : un assistant d'installation, des mots de passe, un service qui tourne en arrière-plan, et des réglages différents entre Windows et Mac. Et ce qui fonctionne pour l'un — ne fonctionne pas toujours pour l'autre.

**Docker** (que nous avons installé la première semaine) résout cela : il fait tourner des programmes prêts à l'emploi dans un **conteneur**.

- **Image** — le modèle : « MySQL version 8.4, prêt à l'emploi ». On la télécharge une fois.
- **Conteneur** (Container) — une copie **en cours d'exécution** de l'image. Isolé du reste de l'ordinateur, avec tout ce dont le programme a besoin.

{{< box "analogy" "Analogie : un conteneur dans un port" >}}
Un conteneur maritime a le même aspect sur chaque navire et dans chaque port — quel que soit son contenu. Il en va de même pour un conteneur Docker : le même MySQL, avec les mêmes réglages, tourne exactement de la même façon pour tous les étudiants, sur tout système d'exploitation.
{{< /box >}}

#### Database Client — la base de données avec une interface

On peut voir et utiliser MySQL sans une seule ligne de code, avec une extension de VS Code nommée **Database Client** (de Weijan Chen). Elle affiche un **arbre** des bases et des tables, ouvre une table comme un tableur, et exécute du SQL en affichant le résultat **sous forme de tableau**. Nous l'utiliserons tout au long de ces thèmes pour **voir** ce que notre code a fait dans la base de données.

#### Le pilote — l'interprète

Notre code est écrit en Python. MySQL parle son propre « protocole » (le thème des réseaux : protocole = règles de communication). Il faut **un intermédiaire** qui traduit : un **pilote** (Driver, aussi appelé Database Connector) = une bibliothèque qui sait :

1. **Se connecter** à un serveur de base de données d'un certain type.
2. **Lui envoyer** des commandes SQL.
3. **Renvoyer** les résultats sous forme de données Python (tuples, int, str).

Pour MySQL, nous utiliserons le pilote officiel : **`mysql-connector-python`**. On l'installe avec `pip`, et dans le code on écrit `import mysql.connector`.

{{< box "analogy" "Analogie : un adaptateur électrique à l'étranger" >}}
Votre appareil (le code Python) a une prise israélienne, et la prise murale (la base de données) est américaine. **Le pilote** = l'adaptateur. Chaque type de prise a son propre adaptateur, mais l'appareil lui-même ne change pas.
{{< /box >}}

#### Les informations de connexion — et pas dans le code !

Pour se connecter à MySQL, il faut **cinq informations** :

| Information | La nôtre | Ce que c'est |
|---|---|---|
| `host` | `localhost` | Où se trouve le serveur — notre ordinateur |
| `port` | `3306` | Le port de MySQL |
| `user` | `root` | Le nom d'utilisateur |
| `password` | `kodkod` | Le mot de passe que nous avons défini dans le conteneur |
| `database` | `school` | Quelle base de données à l'intérieur du serveur |

Dans un vrai projet, il y a ici un **vrai mot de passe**. S'il est écrit dans le code et que le code est monté sur GitHub, **il a fuité** (souvenez-vous des secrets du thème Git !). La pratique professionnelle :

- Les informations de connexion sont conservées dans des **variables d'environnement** (Environment Variables), ou dans un fichier `.env` qui est dans **`.gitignore`**.
- Le code **les lit** : `os.environ.get("DB_PASSWORD")`.

### Syntaxe

**Faire tourner MySQL dans un conteneur** — une seule commande, une seule fois (dans PowerShell ; sur Mac, remplacez le `` ` `` par `\`) :

```powershell
docker run --name kodkod-mysql `
  -e MYSQL_ROOT_PASSWORD=kodkod `
  -e MYSQL_DATABASE=school `
  -p 3306:3306 -d mysql:8.4
```

| Partie | Signification |
|---|---|
| `docker run` | Crée et démarre un nouveau **conteneur** |
| `--name kodkod-mysql` | Un nom pour le conteneur, pour pouvoir y faire référence |
| `-e MYSQL_ROOT_PASSWORD=kodkod` | Une variable d'environnement : le mot de passe de l'utilisateur `root` |
| `-e MYSQL_DATABASE=school` | Une variable d'environnement : une base nommée `school` créée automatiquement |
| `-p 3306:3306` | Le port 3306 de notre ordinateur mène au MySQL à l'intérieur du conteneur |
| `-d` | Tourne en arrière-plan (detached), et le terminal reste libre |
| `mysql:8.4` | L'**image** : MySQL version 8.4 |

**Des commandes utiles :**

```powershell
docker ps                     # which containers are running now
docker start kodkod-mysql     # start it again (after a restart of the computer)
docker stop kodkod-mysql      # stop it (the data is kept)
```

**Se connecter depuis Python :**

```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password="kodkod",
    database="school",
)
connection.close()
```

- `mysql.connector.connect(...)` — ouvre une connexion réseau au serveur. Si le serveur ne tourne pas — une erreur.
- `port=3306` est la valeur par défaut, on peut l'omettre.

### Premier exemple de code

**Mettre en place la base de données de ces thèmes**, étape par étape.

**1. Lancez MySQL** (Docker Desktop doit être ouvert) :

```powershell
docker run --name kodkod-mysql `
  -e MYSQL_ROOT_PASSWORD=kodkod `
  -e MYSQL_DATABASE=school `
  -p 3306:3306 -d mysql:8.4
docker ps
```

Au premier lancement, Docker télécharge l'image (quelques minutes). `docker ps` montre que le conteneur tourne :

```text
CONTAINER ID   IMAGE       STATUS         PORTS                    NAMES
ffaa661f5047   mysql:8.4   Up 10 seconds  0.0.0.0:3306->3306/tcp   kodkod-mysql
```

**2. Connectez-vous depuis VS Code :**

1. L'onglet **Extensions** → cherchez **Database Client** (de Weijan Chen) → **Install**.
2. Une icône de base de données apparaît dans la barre latérale. Cliquez dessus → **+** (Create Connection) → choisissez **MySQL**.
3. Remplissez : Host `127.0.0.1`, Port `3306`, Username `root`, Password `kodkod`, Database `school` → **Connect**.
4. La connexion apparaît dans l'arbre, et à l'intérieur la base `school` — encore sans tables.

**3. Un projet Python :**

```powershell
mkdir ~\dev\kodkod6\week-4\school-db
cd ~\dev\kodkod6\week-4\school-db
git init
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install mysql-connector-python
```

`.gitignore` :

```text
__pycache__/
.venv/
.env
```

`setup_db.py` :

```python
import os

import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password=os.environ.get("DB_PASSWORD", "kodkod"),
    database="school",
)
print("Connected:", connection.is_connected())
print("Server version:", connection.server_info)
connection.close()
print("Connected after close:", connection.is_connected())
```

**Sortie :**

```text
Connected: True
Server version: 8.4.11
Connected after close: False
```

### Analyse du code ligne par ligne

| Code | Explication |
|---|---|
| `import mysql.connector` | Le **pilote**. Installé avec `pip install mysql-connector-python`. |
| `host="localhost"`, `port=3306` | **Où** est le serveur : notre ordinateur, sur le port que nous avons ouvert avec `-p 3306:3306`. |
| `user="root"` | L'utilisateur principal de MySQL. |
| `os.environ.get("DB_PASSWORD", "kodkod")` | Le mot de passe — **depuis une variable d'environnement** si elle a été définie, sinon la valeur par défaut de l'environnement d'apprentissage. |
| `database="school"` | La base créée avec `-e MYSQL_DATABASE=school`. |
| `connection.is_connected()` | `True` — la connexion est ouverte. |
| `connection.server_info` | La version du serveur MySQL. Votre numéro peut être différent. |
| `connection.close()` | Ferme la connexion (dans la leçon suivante : pourquoi c'est important). |
| Pas de `*.db` dans `.gitignore` | Les données se trouvent **dans le conteneur**, pas dans le dossier du projet. Seul **le code** qui crée la structure entre dans Git. |

### Un autre exemple

#### Le conteneur après le redémarrage de l'ordinateur

Après un redémarrage, Docker Desktop se lance — mais le conteneur ne tourne **pas**. Relancer `docker run` ? **Non** — le nom `kodkod-mysql` est déjà pris :

```text
docker: Error response from daemon: Conflict. The container name "/kodkod-mysql" is already in use by container ...
```

Le conteneur existe, il est seulement arrêté. Démarrez-le :

```powershell
docker start kodkod-mysql
```

**Toutes les données ont été conservées** — les tables et les lignes restent dans le conteneur jusqu'à ce que vous le supprimiez (`docker rm`).

#### Quand le serveur ne tourne pas

Si Docker Desktop est fermé ou si le conteneur est arrêté, la connexion depuis Python échoue :

```text
mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on 'localhost:3306' (Errno 10061: No connection could be made because the target machine actively refused it)
```

**`Can't connect`** = personne n'écoute sur le port 3306. Vérifiez avec `docker ps`, et démarrez-le avec `docker start kodkod-mysql`.

### Erreur courante

**Erreur 1 : Docker Desktop n'est pas ouvert.**

```text
failed to connect to the docker API ... The system cannot find the file specified.
```

Les commandes `docker` communiquent avec Docker Desktop. Ouvrez-le, attendez la fin du démarrage, et réessayez.

**Erreur 2 : le port est occupé.**

```text
Bind for 0.0.0.0:3306 failed: port is already allocated
```

Quelque chose tourne déjà sur le port 3306 — généralement un MySQL installé autrefois directement sur l'ordinateur. Vous pouvez utiliser un autre port sur notre ordinateur : `-p 3307:3306`, puis vous connecter avec `port=3307`.

**Erreur 3 : un mot de passe de base de données dans du code qui monte sur GitHub.**

```python
connection = mysql.connector.connect(host="prod-db.company.com", user="admin", password="MyRealPassword123")
```

Même si vous le supprimez dans le Commit suivant, le mot de passe reste dans l'historique (souvenez-vous du thème Git !). **Des variables d'environnement / un `.env` dans `.gitignore`.** (Notre mot de passe `kodkod` ne sert qu'à un environnement d'apprentissage local.)

### Résumé

- **MySQL** = un serveur de base de données : un processus qui écoute sur le port **3306**, et auquel on se connecte par le réseau.
- **Docker** fait tourner MySQL dans un **conteneur** — avec une seule commande, et la même pour tous. `docker run` une fois, puis `docker start kodkod-mysql`.
- **Database Client** (une extension de VS Code) = une interface vers la base : un arbre des tables, l'exécution de SQL et les résultats sous forme de tableau.
- **Pilote** = une bibliothèque qui traduit entre Python et la base. Pour MySQL : **`mysql-connector-python`** (`import mysql.connector`).
- Cinq informations de connexion : `host`, `port`, `user`, `password`, `database`.
- Les mots de passe ne s'écrivent pas dans le code : des variables d'environnement ou un `.env` dans `.gitignore`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

### Documentation officielle

- [Docker Hub — MySQL official image](https://hub.docker.com/_/mysql)
- [Database Client for VS Code](https://marketplace.visualstudio.com/items?itemName=cweijan.vscode-database-client2)
- [Connector/Python — Installation](https://dev.mysql.com/doc/connector-python/en/connector-python-installation.html)

<!-- docs:end -->

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Exercices

Les exercices n'utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d'abord seul, et n'ouvrez la solution qu'ensuite.

### Exercice 1: Table, ligne, colonne

```text
id | name | age | email
---+------+-----+---------------
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
```

1. Combien de lignes et combien de colonnes y a-t-il dans la table ?
2. Que décrit chaque ligne, et que décrit chaque colonne ?
3. Quelle colonne convient pour être la Primary Key, et pourquoi pas `name` ?

{{< solution >}}
1. **2 lignes** et **4 colonnes**.
2. Chaque **ligne** (Row) est **un étudiant** — un enregistrement. Chaque **colonne** (Column) est un **champ**, d'un type fixe, que possèdent tous les étudiants.
3. **`id`** — il est unique, ne change jamais, et est créé automatiquement. `name` ne convient pas : il peut y avoir deux étudiantes nommées Dana, et un nom peut changer.
{{< /solution >}}

### Exercice 2: Types et contraintes

Écrivez la commande SQL qui crée une table `students` avec :

- `id` — un nombre entier, une Primary Key, créé automatiquement ;
- `name` — du texte, obligatoire ;
- `age` — un nombre entier, obligatoire ;
- `email` — du texte, obligatoire et unique.

{{< solution >}}
```sql
CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

- `INT` / `VARCHAR(100)` — le type de chaque colonne : un nombre entier, et un texte de 100 caractères au plus.
- `NOT NULL` — ne peut pas rester vide.
- `UNIQUE` — pas deux étudiants avec le même e-mail.
- `AUTO_INCREMENT` — la base choisit l'`id` elle-même.
{{< /solution >}}

### Exercice 3: Qu'est-ce que la base détecterait ?

```text
students = [
    {"name": "Dana", "age": 24, "email": "d@x.com"},
    {"name": "Avi", "age": 24, "email": None},
    {"name": "Noa", "age": 22, "email": "d@x.com"},
]
```

Lesquelles de ces données la table `students` de l'exercice précédent rejetterait-elle, et à cause de quelle contrainte ? Et pourquoi personne ne les a arrêtées dans la liste Python ?

{{< solution >}}
- **Avi** — l'e-mail vaut `None` → rejeté à cause de **`NOT NULL`**.
- **Noa** — le même e-mail que Dana → rejetée à cause de **`UNIQUE`**.
- **Dana** — valide.

Une liste Python n'a **aucune règle** : n'importe quelle valeur y entre. Dans une base de données, les règles sont définies **une seule fois**, dans la table, et s'appliquent à tous ceux qui y écrivent — depuis n'importe quel programme.
{{< /solution >}}

### Exercice 4: Démarrer MySQL

1. Que fait chaque partie de la commande suivante ?
2. Comment vérifier que le conteneur tourne ?
3. Comment le redémarrer après le redémarrage de l'ordinateur — sans en créer un nouveau ?

```text
docker run --name kodkod-mysql -e MYSQL_ROOT_PASSWORD=kodkod -e MYSQL_DATABASE=school -p 3306:3306 -d mysql:8.4
```

{{< solution >}}
| Partie | Signification |
|---|---|
| `docker run` | Crée et démarre un nouveau **conteneur** |
| `--name kodkod-mysql` | Un nom pour le conteneur, pour pouvoir y faire référence |
| `-e MYSQL_ROOT_PASSWORD=kodkod` | Le mot de passe de l'utilisateur `root` |
| `-e MYSQL_DATABASE=school` | Une base nommée `school` créée automatiquement |
| `-p 3306:3306` | Le port 3306 de notre ordinateur mène au MySQL du conteneur |
| `-d` | Tourne en arrière-plan, et le terminal reste libre |
| `mysql:8.4` | L'**image** : MySQL version 8.4 |

2. `docker ps` — affiche les conteneurs qui tournent en ce moment.
3. `docker start kodkod-mysql` — démarre le conteneur existant. Ses données sont conservées. Relancer `docker run` échouerait, car le nom est déjà pris.
{{< /solution >}}

### Exercice 5: Une première connexion

Écrivez un code qui se connecte à la base `school` du conteneur, affiche si la connexion est active, la ferme, et affiche à nouveau.

**Résultat attendu :**

```text
Connected: True
Connected after close: False
```

{{< solution >}}
```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password="kodkod",
    database="school",
)
print("Connected:", connection.is_connected())
connection.close()
print("Connected after close:", connection.is_connected())
```

```text
Connected: True
Connected after close: False
```

`mysql.connector.connect` ouvre une connexion réseau au serveur MySQL du conteneur. Si le conteneur ne tourne pas — vous obtenez une erreur : `Can't connect to MySQL server`.
{{< /solution >}}

<!-- exercises:end -->
