@mlagie/sql-connector 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -149,6 +149,30 @@ const transferSchema = new Schema({
149
149
  2. **De supprimer le backup** après restauration ou non.
150
150
  3. Si vous refusez la suppression, le backup est renommé en `.ignored` et ne sera plus proposé.
151
151
 
152
+ ### Synchronisation intelligente des colonnes
153
+
154
+ Lors de la synchronisation (`Model.syncAllTables()`), le module compare chaque colonne existante avec la définition du schéma JS :
155
+
156
+ - **Type** : Si le type SQL attendu diffère de celui en base, la colonne est modifiée.
157
+ - **Nullabilité** : Si la contrainte `NOT NULL` ou `NULL` diffère, la colonne est modifiée.
158
+ > ⚠️ Une colonne `primary_key` est toujours considérée comme `NOT NULL` même si `required` n'est pas précisé.
159
+ - **Valeur par défaut** : Si la valeur par défaut diffère, la colonne est modifiée.
160
+ - **Unique** : Si la contrainte `UNIQUE` diffère, la colonne est modifiée.
161
+ - **Primary key** : Si la contrainte `PRIMARY KEY` diffère, la colonne est modifiée.
162
+
163
+ Seules les différences réelles entraînent une modification SQL, ce qui évite les migrations inutiles.
164
+
165
+ > **Note :**
166
+ > Il est interdit de déclarer une colonne à la fois `primary_key: true` et `unique: true` dans le schéma JS.
167
+ > **Explication :** En SQL, une clé primaire (`PRIMARY KEY`) est déjà unique par définition et impose la contrainte `UNIQUE` et `NOT NULL` sur la colonne. Ajouter explicitement `unique: true` en plus de `primary_key: true` est redondant et provoque une erreur SQL ("Multiple primary key defined").
168
+ >
169
+ > **En résumé :**
170
+ > - Utilisez seulement `primary_key: true` pour une colonne qui doit être la clé primaire.
171
+ > - Utilisez `unique: true` pour une colonne qui doit être unique mais n'est pas la clé primaire.
172
+
173
+ ---
174
+
175
+
152
176
  #### Exemple d’utilisation
153
177
 
154
178
  ```js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlagie/sql-connector",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Le module sql-connector permet de gérer les connexions à une base de données MySQL, de définir des schémas de tables, et d'interagir avec les données de manière simple et efficace.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -6,6 +6,7 @@ const formatObject = require("../utils/formatObject");
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const glob = require('glob');
9
+ const { ModelInstance } = require("./ModelInstance");
9
10
 
10
11
  function getFieldType(field) {
11
12
  if (typeof field === "object") {
@@ -51,6 +52,10 @@ function ifReservedKeywords(tableName) {
51
52
  }
52
53
 
53
54
  function getColumnDefinition(fieldName, field) {
55
+ if (field.primary_key && field.unique) {
56
+ throw new Error(`Field '${fieldName}' cannot be both PRIMARY KEY and UNIQUE.`);
57
+ }
58
+
54
59
  const fieldType = getFieldType(field);
55
60
  let colDef = "";
56
61
 
@@ -232,8 +237,50 @@ class Model {
232
237
  );
233
238
  let existingCols = columns.map(col => col.Field);
234
239
 
235
- // --- Warn si oldName est présent dans le schéma ---
236
240
  for (const [fieldName, field] of Object.entries(model.schema.schemaDict)) {
241
+ if (existingCols.includes(fieldName)) {
242
+ // 1. Récupère infos colonne
243
+ const currentCol = columns.find(col => col.Field === fieldName);
244
+ const [indexes] = await conn.promise().query(
245
+ `SHOW INDEX FROM \`${model.name}\` WHERE Column_name = ?`, [fieldName]
246
+ );
247
+
248
+ // 2. Vérifie les propriétés principales
249
+ let needModify = false;
250
+
251
+ // Type
252
+ const expectedType = sqlTypeMap[getFieldType(field)]?.toUpperCase();
253
+ const dbType = currentCol.Type.toUpperCase().split('(')[0];
254
+ if (expectedType !== dbType)
255
+ needModify = true;
256
+
257
+ // Nullability check
258
+ const shouldBeNotNull = field.required === true || field.primary_key === true;
259
+ if ((shouldBeNotNull && currentCol.Null === "YES") || (!shouldBeNotNull && currentCol.Null === "NO"))
260
+ needModify = true;
261
+
262
+ // Default
263
+ if ((field.default ?? null) != (currentCol.Default ?? null))
264
+ needModify = true;
265
+
266
+ // Unique
267
+ const isUniqueInDB = indexes.some(idx => idx.Non_unique === 0 && idx.Key_name !== 'PRIMARY');
268
+ if (!!field.unique !== isUniqueInDB)
269
+ needModify = true;
270
+
271
+ // Primary key
272
+ const isPrimaryInDB = indexes.some(idx => idx.Key_name === 'PRIMARY');
273
+ if (!!field.primary_key !== isPrimaryInDB)
274
+ needModify = true;
275
+
276
+ // 3. Si différence, on modifie
277
+ if (needModify) {
278
+ const newColDef = getColumnDefinition(fieldName, field);
279
+ const alterSQL = `ALTER TABLE \`${model.name}\` MODIFY COLUMN ${newColDef}`;
280
+ await conn.promise().query(alterSQL);
281
+ }
282
+ }
283
+ // --- Warn si oldName est présent dans le schéma ---
237
284
  if (field.oldName) {
238
285
  logs(`⚠️ Pensez à retirer la propriété 'oldName' du champ '${fieldName}' dans le schéma JS de '${model.name}' pour éviter des renommages inutiles à l'avenir.`);
239
286
  }