@mlagie/sql-connector 1.2.4 → 1.3.1

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.4",
3
+ "version": "1.3.1",
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": {
@@ -52,6 +52,10 @@ function ifReservedKeywords(tableName) {
52
52
  }
53
53
 
54
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
+
55
59
  const fieldType = getFieldType(field);
56
60
  let colDef = "";
57
61
 
@@ -233,8 +237,116 @@ class Model {
233
237
  );
234
238
  let existingCols = columns.map(col => col.Field);
235
239
 
236
- // --- Warn si oldName est présent dans le schéma ---
237
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 avec plus de précision
249
+ let needModify = false;
250
+
251
+ // --- Vérification du TYPE ---
252
+ const fieldType = getFieldType(field);
253
+ let expectedTypeDef;
254
+
255
+ if (Array.isArray(field.enum) && field.enum.length > 0) {
256
+ // Pour les ENUM, on compare la définition complète
257
+ const enumValues = field.enum.map(v => `'${v.replace(/'/g, "''")}'`).join(", ");
258
+ expectedTypeDef = `enum(${enumValues})`;
259
+ } else {
260
+ // Pour les autres types
261
+ if (!sqlTypeMap[fieldType]) throw new Error(`Field ${fieldName} has unsupported type ${fieldType}.`);
262
+
263
+ expectedTypeDef = sqlTypeMap[fieldType];
264
+ if (sqlTypeMap[fieldType] === "VARCHAR" || sqlTypeMap[fieldType] === "INT") {
265
+ const length = field.length > 0 ? field.length : 255;
266
+ expectedTypeDef += `(${length})`;
267
+ }
268
+ }
269
+
270
+ // Normalisation pour la comparaison (minuscules, suppression des espaces)
271
+ const normalizeType = (typeStr) => {
272
+ return typeStr.toLowerCase().replace(/\s+/g, '').replace(/`/g, '');
273
+ };
274
+
275
+ const currentTypeNormalized = normalizeType(currentCol.Type);
276
+ const expectedTypeNormalized = normalizeType(expectedTypeDef);
277
+
278
+ if (currentTypeNormalized !== expectedTypeNormalized) {
279
+ logs(`Type mismatch for ${fieldName}: DB has '${currentCol.Type}', expected '${expectedTypeDef}'`);
280
+ needModify = true;
281
+ }
282
+
283
+ // --- Vérification NULL/NOT NULL ---
284
+ const shouldBeNotNull = field.required === true || field.primary_key === true;
285
+ const isNullableInDB = currentCol.Null === "YES";
286
+
287
+ if (shouldBeNotNull && isNullableInDB) {
288
+ logs(`Nullability mismatch for ${fieldName}: DB allows NULL, expected NOT NULL`);
289
+ needModify = true;
290
+ }
291
+ if (!shouldBeNotNull && !isNullableInDB) {
292
+ logs(`Nullability mismatch for ${fieldName}: DB is NOT NULL, expected NULL allowed`);
293
+ needModify = true;
294
+ }
295
+
296
+ // --- Vérification DEFAULT VALUE ---
297
+ const expectedDefault = field.default !== undefined ? field.default : null;
298
+ const currentDefault = currentCol.Default;
299
+
300
+ // Gestion spéciale pour les valeurs par défaut
301
+ if (expectedDefault !== null && currentDefault === null) {
302
+ logs(`Default value mismatch for ${fieldName}: DB has NULL, expected '${expectedDefault}'`);
303
+ needModify = true;
304
+ } else if (expectedDefault === null && currentDefault !== null) {
305
+ logs(`Default value mismatch for ${fieldName}: DB has '${currentDefault}', expected NULL`);
306
+ needModify = true;
307
+ } else if (expectedDefault !== null && currentDefault !== null) {
308
+ // Comparaison stringifiée pour éviter les problèmes de type
309
+ if (String(expectedDefault) !== String(currentDefault)) {
310
+ logs(`Default value mismatch for ${fieldName}: DB has '${currentDefault}', expected '${expectedDefault}'`);
311
+ needModify = true;
312
+ }
313
+ }
314
+
315
+ // --- Vérification UNIQUE constraint ---
316
+ const isUniqueInDB = indexes.some(idx => idx.Non_unique === 0 && idx.Key_name !== 'PRIMARY');
317
+ if (!!field.unique !== isUniqueInDB) {
318
+ logs(`Unique constraint mismatch for ${fieldName}: DB has unique=${isUniqueInDB}, expected=${!!field.unique}`);
319
+ needModify = true;
320
+ }
321
+
322
+ // --- Vérification PRIMARY KEY ---
323
+ const isPrimaryInDB = indexes.some(idx => idx.Key_name === 'PRIMARY');
324
+ if (!!field.primary_key !== isPrimaryInDB) {
325
+ logs(`Primary key mismatch for ${fieldName}: DB has PK=${isPrimaryInDB}, expected=${!!field.primary_key}`);
326
+ needModify = true;
327
+ }
328
+
329
+ // --- Vérification AUTO_INCREMENT ---
330
+ const isAutoIncrementInDB = currentCol.Extra.toLowerCase().includes('auto_increment');
331
+ if (!!field.auto_increment !== isAutoIncrementInDB) {
332
+ logs(`Auto increment mismatch for ${fieldName}: DB has AI=${isAutoIncrementInDB}, expected=${!!field.auto_increment}`);
333
+ needModify = true;
334
+ }
335
+
336
+ // 3. Si différence, on modifie
337
+ if (needModify) {
338
+ try {
339
+ const newColDef = getColumnDefinition(fieldName, field);
340
+ const alterSQL = `ALTER TABLE \`${model.name}\` MODIFY COLUMN ${newColDef}`;
341
+ logs(`Modifying column ${fieldName}: ${alterSQL}`);
342
+ await conn.promise().query(alterSQL);
343
+ logs(`Column ${fieldName} modified successfully`);
344
+ } catch (err) {
345
+ error(`Error modifying column ${fieldName}: ${err}`);
346
+ }
347
+ }
348
+ }
349
+ // --- Warn si oldName est présent dans le schéma ---
238
350
  if (field.oldName) {
239
351
  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.`);
240
352
  }