# Script `cliMajSql2.php` — mode `--majsql` (mise à jour automatique de la BDD)

> **Note de version.** Le code de référence est désormais **`cli/cliMajSql2.php`**
> (≈ 841 lignes), évolution de l'ancien `cliMajSql.php`. Ce document remplace la
> description de l'ancienne version. Validé sous **PHP 7.4** (cible) et **PHP 5.6**.

## Contexte

Le script disposait historiquement d'un unique mode `--maj` qui exécute un fichier
SQL fourni en paramètre (`--file=...` ou stdin) et refuse les fichiers de plus de
100 lignes sans `--force`.

Le fichier `majsql.txt` (≈ 12 756 lignes, **2 924 commandes SQL**) concentre toutes
les évolutions de schéma/données du projet mais devait être découpé manuellement
pour appliquer uniquement les commandes nouvelles sur chaque BDD. Ce processus
manuel était source d'oublis et d'erreurs.

## Objectif

Le mode `--majsql` :

- lit automatiquement `./majsql.txt`,
- détermine l'état courant de la BDD via une table de suivi,
- applique **uniquement les commandes manquantes** (y compris les « trous »),
- trace précisément chaque commande appliquée dans la BDD elle-même,
- sécurise la première exécution sur une base déjà à jour manuellement.

## Invocation

```bash
php74 cli/cliMajSql2.php --majsql [--start=N] [--simul=1] [--force] [--debug=1|2]
```

Le mode `--maj` existant (exécution d'un fichier SQL ponctuel) est inchangé.

## Première utilisation : reprise de l'existant sans rejouer le SQL

À la **première exécution** sur une base donnée, `--start=N` est **obligatoire**.
Il sert exactement au cas « la base a déjà été mise à jour manuellement avec
l'ancienne méthode » :

- `N` = numéro de **ligne** de `majsql.txt` jusqu'auquel les commandes sont
  considérées comme **déjà appliquées**.
- Toutes les commandes dont la ligne de fin (`line_end`) est **≤ N** sont
  **enregistrées dans `_majsql_version` (`applied_by='init'`) SANS être exécutées**.
- Les commandes au-delà de la ligne `N` seront exécutées au run suivant
  (`--majsql` sans `--start`).

| Valeur | Signification |
|--------|---------------|
| `--start=0`              | Rien n'a été appliqué → **tout sera exécuté** |
| `--start=<dernière ligne>` | Base déjà totalement à jour → rien à exécuter |
| `--start=N` (intermédiaire) | Commandes jusqu'à la ligne `N` déjà appliquées manuellement |

Une fois la table initialisée, repasser `--start` est refusé (sauf `--force`, qui
fait `TRUNCATE` puis ré-initialise — **destructif**).

## Table de suivi `_majsql_version`

Créée automatiquement (`CREATE TABLE IF NOT EXISTS`) dans la BDD courante :

| Colonne      | Type        | Rôle |
|--------------|-------------|------|
| `cmd_index`  | INT (PK)    | N° de la commande dans `majsql.txt` (1..N). **L'index `0` est un marqueur d'initialisation** (voir ci-dessous). |
| `cmd_hash`   | VARCHAR(32) | md5 de la commande SQL normalisée |
| `line_start` | INT         | Ligne de début dans `majsql.txt` |
| `line_end`   | INT         | Ligne de fin dans `majsql.txt` |
| `applied_at` | DATETIME    | Horodatage d'insertion |
| `applied_by` | VARCHAR(16) | `init` (via `--start`) ou `majsql` (exécution réelle) |

Une ligne par commande → diagnostic fin et détection de divergences.

### Marqueur d'initialisation (index 0)

À l'initialisation, une ligne `cmd_index=0` est insérée pour matérialiser « base
initialisée ». Elle permet de distinguer **« jamais initialisée »** de
**« initialisée à `--start=0` »** : sans elle, `--start=0` n'insérait aucune ligne
et le run suivant rebouclait indéfiniment sur le garde-fou de première exécution.
Le comptage des commandes réelles et la vérification d'intégrité ignorent
`cmd_index < 1` (rétrocompatible avec les bases déjà suivies, qui n'ont que des
index ≥ 1).

## Parseur SQL : tokeniseur (`parseSqlCommands`)

Découpe `majsql.txt` en instructions sur le **délimiteur réel**, en respectant :

- les littéraux chaîne `'...'` et `"..."` (avec échappement `\`),
- les identifiants entre backticks `` `...` ``,
- les commentaires `-- ` (ligne), `#` (ligne) et `/* ... */` (bloc),
- la directive `DELIMITER`.

Les espaces hors chaîne sont **normalisés** (collapse) → le `md5` est stable et
insensible au reformatage. Pour chaque commande il conserve : le texte normalisé,
les bornes de lignes, le contexte (dernier commentaire `-- jj-mm-aaaa ...`), et un
indicateur `unterminated` si la dernière instruction n'a pas de délimiteur final.

> Validation parseur : **2 924 commandes** sur le `majsql.txt` réel, **0 résidu**.

## Exécution incrémentale (table déjà initialisée, sans `--start`)

1. Chargement de **l'ensemble** des index déjà enregistrés et de leur hash.
2. **Vérification d'intégrité de TOUTES les commandes enregistrées** : un hash
   divergent (fichier modifié au-dessus d'une commande appliquée) ou un index
   hors fichier (`majsql.txt` tronqué) → arrêt, sauf `--force`.
3. **Commandes manquantes = tout index 1..N absent de la table.** Cela ré-essaie
   automatiquement les **trous** laissés par d'anciens échecs `--force` (pas de
   reprise sur le seul `MAX(cmd_index)`).
4. Exécution une par une ; chaque succès est immédiatement tracé. Si le traçage
   échoue après une DDL réussie, un **avertissement explicite** est émis (risque
   de ré-exécution au prochain run).
5. **Arrêt sur première erreur SQL** (diagnostic). Avec `--force`, la commande
   échouée n'est **pas** enregistrée → elle sera ré-essayée au run suivant.

## Arbre de décision `majSqlAuto`

```
┌─ Table _majsql_version initialisée ? (≥ 1 ligne, marqueur index 0 inclus)
│   ├─ NON (première exécution)
│   │   ├─ --start absent  → ERREUR + aide (arrêt)
│   │   └─ --start=N       → INIT : marqueur index 0 + cmds (line_end ≤ N) en
│   │                         applied_by='init' (AUCUN SQL exécuté)
│   └─ OUI
│       ├─ --start fourni
│       │   ├─ pas --force → ERREUR (arrêt)
│       │   ├─ --force + --simul → décrit la purge, NE purge PAS, return (pas de récursion)
│       │   └─ --force     → TRUNCATE puis relance (retombe en « non initialisée »)
│       └─ --start absent   → MAJ normale
│                             ├─ vérif hash de TOUTES les cmds enregistrées
│                             │   ├─ OK → exécute les index manquants (trous inclus)
│                             │   └─ KO → arrêt (sauf --force)
│                             └─ arrêt sur première erreur SQL (sauf --force)
```

## Connexion BDD (`connectBdd`)

Lecture **structurée** de la configuration : `require` de
`config/autoload/global.php` puis `config/autoload/local.php`, fusionnés par
`array_replace_recursive`. La clé standard `['db']` est privilégiée (DSN contenant
`dbname=`), avec repli sur une recherche récursive d'un nœud
`dsn`/`username`/`password`. Cela **élimine la collision** possible avec d'autres
clés `username`/`password` du fichier (ex. SFTP). Handler PDO en
`ERRMODE_EXCEPTION`. Le DSN de connexion force `host=localhost` (socket système).

## Fonctions

| Fonction                | Rôle |
|-------------------------|------|
| `parseSqlCommands`      | Tokeniseur : `majsql.txt` → liste de commandes + bornes + contexte |
| `initTrackingTable`     | `CREATE TABLE IF NOT EXISTS _majsql_version` |
| `getTrackingCount`      | Nombre de commandes **réelles** enregistrées (`cmd_index ≥ 1`) |
| `isTrackingInitialized` | La table a-t-elle été initialisée ? (≥ 1 ligne, marqueur inclus) |
| `insertTracking`        | `INSERT` d'une ligne dans la table de suivi |
| `connectBdd`            | Connexion PDO factorisée (lecture structurée de la config) |
| `findDbConfigRec`       | Recherche récursive du nœud de config BDD |
| `majSqlAuto`            | Orchestration du mode `--majsql` (cas 1/2/3) |
| `logprintf`             | `printf` miroir stdout + fichier de log |

> `getLastTracking` subsiste mais n'est plus utilisé par la logique incrémentale.

## Paramètres récapitulatifs

| Paramètre      | Effet en mode `--majsql` |
|----------------|---------------------------|
| `--simul=1`    | Aucune écriture BDD (ni tracking, ni DDL/DML). En `--force`+`--start`, décrit la purge sans l'exécuter (pas de récursion) |
| `--force`      | Ignore divergence de hash / index hors fichier, permet re-`--start` (TRUNCATE), continue sur erreur SQL (commande non tracée → ré-essayée) |
| `--start=N`    | Entier ≥ 0. Obligatoire en première exécution, interdit ensuite (sauf `--force`) |
| `--debug=1\|2` | Affichage de la ligne de commande et de la connexion |

## Traçabilité

- Dossier `cli/histo/` créé automatiquement.
- Log horodaté `cli/histo/cliMajSql_YYYYMMDD_HHhMM` en parallèle de stdout
  (`logprintf`). Le contexte (`-- jj-mm-aaaa ...`) est affiché à chaque changement.

## État / Tests

**Matrice de test dynamique validée le 2026-06-05 : 39 assertions PASS / 0 FAIL,
sous PHP 7.4 ET PHP 5.6.**

Environnement : instance MariaDB jetable isolée (datadir `/tmp`, socket et port
3307 dédiés), sans aucun impact sur la base de production. La connexion du script
(`host=localhost`) est redirigée vers l'instance de test via
`php -d pdo_mysql.default_socket=…`.

| # | Scénario | Vérifie |
|---|----------|---------|
| S1 | Garde-fou première exécution | `--majsql` sans `--start` → message + sortie en erreur, rien créé |
| S2 | Initialisation partielle `--start=5` | Commandes ≤ ligne 5 marquées `init`, **sans exécution SQL** |
| S3 | Application complète | `--start=0` puis `--majsql` → 6/6 commandes appliquées (accents, `ALTER`, `UPDATE` inclus) |
| S4 | Idempotence | Re-run → « à jour », aucune ré-exécution |
| S5 | Trou `--force` ré-essayé | Échec passé en `--force` laisse un trou ; corrigé, il est ré-appliqué au run suivant |
| S6 | Divergence de hash | `majsql.txt` modifié au-dessus d'une commande appliquée → arrêt, contourné par `--force` |
| S7 | `--simul` + non-récursion | Simulation n'écrit rien ; `--force`+`--start`+`--simul` décrit sans boucler ni purger |
| S8 | Arrêt sur erreur | Erreur SQL sans `--force` → arrêt, commandes suivantes non tentées |

Bug corrigé par cette campagne : `--start=0` ne posait aucune marque et rebouclait
sur le garde-fou (cf. marqueur d'initialisation index 0). Lint OK PHP 7.4 + PHP 5.6.

## Fichiers concernés

- `cli/cliMajSql2.php` : code du mode `--majsql` (et `--maj` historique).
- `majsql.txt` : 3 `;` manquants ajoutés (commandes auparavant fusionnées → SQL invalide).
- `cli/histo/` : dossier de logs, créé à la première exécution `--majsql`.
- `_majsql_version` : table de suivi, créée à la première exécution dans la BDD courante.
