@igojs/db 6.0.0-beta.2 → 6.0.0-beta.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igojs/db",
3
- "version": "6.0.0-beta.2",
3
+ "version": "6.0.0-beta.3",
4
4
  "description": "Igo ORM - Database abstraction layer for MySQL and PostgreSQL",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Db.js CHANGED
@@ -81,11 +81,9 @@ class Db {
81
81
  return dialect.getRows(result);
82
82
 
83
83
  } catch (err) {
84
- if (options.silent) {
85
- return;
84
+ if (!options.silent) {
85
+ logQuery(sql, params, err);
86
86
  }
87
- // log & rethrow error
88
- logQuery(sql, params, err);
89
87
  throw err;
90
88
 
91
89
  } finally {
@@ -108,6 +108,12 @@ module.exports = class PaginatedOptimizedSql extends Sql {
108
108
 
109
109
  // WHERE de la table principale
110
110
  const params = [];
111
+
112
+ // Ajouter les params des jointures de tri (extraWhere)
113
+ if (this._sortJoinParams && this._sortJoinParams.length > 0) {
114
+ params.push(...this._sortJoinParams);
115
+ }
116
+
111
117
  sql += this.whereSQL(params);
112
118
 
113
119
  // WHERE NOT de la table principale
@@ -657,6 +663,7 @@ module.exports = class PaginatedOptimizedSql extends Sql {
657
663
  const { query, dialect } = this;
658
664
  const { esc } = dialect;
659
665
  const mainTable = query.table;
666
+ const params = [];
660
667
 
661
668
  let sql = '';
662
669
  const processedTables = new Set(); // Pour éviter les doublons
@@ -672,17 +679,29 @@ module.exports = class PaginatedOptimizedSql extends Sql {
672
679
  return;
673
680
  }
674
681
 
675
- const [, , AssociatedModel, src_column, ref_column] = association;
682
+ const [, , AssociatedModel, src_column, ref_column, extraWhere] = association;
676
683
 
677
684
  // Générer le LEFT JOIN (pour préserver toutes les lignes, même celles avec NULL)
678
- sql += `LEFT JOIN ${esc}${currentTableName}${esc} `;
679
- sql += `ON ${esc}${currentTableName}${esc}.${esc}${ref_column}${esc} = ${esc}${prevTable}${esc}.${esc}${src_column}${esc} `;
685
+ let joinSql = `LEFT JOIN ${esc}${currentTableName}${esc} `;
686
+ joinSql += `ON ${esc}${currentTableName}${esc}.${esc}${ref_column}${esc} = ${esc}${prevTable}${esc}.${esc}${src_column}${esc}`;
687
+
688
+ // Ajouter les conditions extraWhere si présentes
689
+ if (extraWhere) {
690
+ _.forOwn(extraWhere, (value, key) => {
691
+ joinSql += ` AND ${esc}${currentTableName}${esc}.${esc}${key}${esc} = ${dialect.param(this.i++)}`;
692
+ params.push(value);
693
+ });
694
+ }
695
+
696
+ sql += joinSql + ' ';
680
697
 
681
698
  processedTables.add(currentTableName);
682
699
  prevTable = currentTableName;
683
700
  });
684
701
  });
685
702
 
703
+ // Retourner à la fois le SQL et les params pour qu'ils soient ajoutés
704
+ this._sortJoinParams = params;
686
705
  return sql;
687
706
  }
688
707
 
@@ -775,7 +794,7 @@ module.exports = class PaginatedOptimizedSql extends Sql {
775
794
  const { esc } = dialect;
776
795
 
777
796
  const { association, conditions, operator, children } = node;
778
- const [, , AssociatedModel, src_column, ref_column] = association;
797
+ const [, , AssociatedModel, src_column, ref_column, extraWhere] = association;
779
798
  const joinTable = AssociatedModel.schema.table;
780
799
 
781
800
  // Ouvrir l'EXISTS
@@ -784,6 +803,14 @@ module.exports = class PaginatedOptimizedSql extends Sql {
784
803
  // Condition de jointure
785
804
  sql += `WHERE ${esc}${joinTable}${esc}.${esc}${ref_column}${esc} = ${esc}${parentTable}${esc}.${esc}${src_column}${esc} `;
786
805
 
806
+ // Ajouter les conditions extraWhere si présentes
807
+ if (extraWhere) {
808
+ _.forOwn(extraWhere, (value, key) => {
809
+ sql += `AND ${esc}${joinTable}${esc}.${esc}${key}${esc} = ${dialect.param(this.i++)} `;
810
+ params.push(value);
811
+ });
812
+ }
813
+
787
814
  // Ajouter les conditions de ce niveau (si présentes)
788
815
  if (conditions && !_.isEmpty(conditions)) {
789
816
  sql += this.buildConditions(conditions, joinTable, params, operator);
@@ -817,12 +844,20 @@ module.exports = class PaginatedOptimizedSql extends Sql {
817
844
  const { esc } = dialect;
818
845
 
819
846
  const { association, conditions, operator } = filterJoin;
820
- const [, , AssociatedModel, src_column, ref_column] = association;
847
+ const [, , AssociatedModel, src_column, ref_column, extraWhere] = association;
821
848
  const joinTable = AssociatedModel.schema.table;
822
849
 
823
850
  let sql = `EXISTS (SELECT 1 FROM ${esc}${joinTable}${esc} `;
824
851
  sql += `WHERE ${esc}${joinTable}${esc}.${esc}${ref_column}${esc} = ${esc}${parentTable}${esc}.${esc}${src_column}${esc} `;
825
852
 
853
+ // Ajouter les conditions extraWhere si présentes
854
+ if (extraWhere) {
855
+ _.forOwn(extraWhere, (value, key) => {
856
+ sql += `AND ${esc}${joinTable}${esc}.${esc}${key}${esc} = ${dialect.param(this.i++)} `;
857
+ params.push(value);
858
+ });
859
+ }
860
+
826
861
  if (conditions && !_.isEmpty(conditions)) {
827
862
  sql += this.buildConditions(conditions, joinTable, params, operator);
828
863
  }
@@ -869,13 +904,21 @@ module.exports = class PaginatedOptimizedSql extends Sql {
869
904
  const association = this._findAssociationByPath(path, query.schema);
870
905
 
871
906
  if (association) {
872
- const [, , AssociatedModel, src_column, ref_column] = association;
907
+ const [, , AssociatedModel, src_column, ref_column, extraWhere] = association;
873
908
  const joinTable = AssociatedModel.schema.table;
874
909
 
875
910
  // Construire l'EXISTS pour cette condition
876
911
  let existsSQL = `EXISTS (SELECT 1 FROM ${esc}${joinTable}${esc} `;
877
912
  existsSQL += `WHERE ${esc}${joinTable}${esc}.${esc}${ref_column}${esc} = ${esc}${parentTable}${esc}.${esc}${src_column}${esc} `;
878
913
 
914
+ // Ajouter les conditions extraWhere si présentes
915
+ if (extraWhere) {
916
+ _.forOwn(extraWhere, (ewValue, ewKey) => {
917
+ existsSQL += `AND ${esc}${joinTable}${esc}.${esc}${ewKey}${esc} = ${dialect.param(this.i++)} `;
918
+ params.push(ewValue);
919
+ });
920
+ }
921
+
879
922
  // Ajouter la condition sur la colonne
880
923
  const columnRef = `${esc}${joinTable}${esc}.${esc}${column}${esc}`;
881
924
 
package/src/Sql.js CHANGED
@@ -58,7 +58,7 @@ module.exports = class Sql {
58
58
  sql += `FROM ${esc}${query.table}${esc} `;
59
59
 
60
60
  // joins
61
- sql += this.addJoins();
61
+ sql += this.addJoins(params);
62
62
 
63
63
  // where
64
64
  sql += this.whereSQL(params);
@@ -101,7 +101,7 @@ module.exports = class Sql {
101
101
  sql += `FROM ${esc}${query.table}${esc} `;
102
102
 
103
103
  // joins
104
- sql += this.addJoins();
104
+ sql += this.addJoins(params);
105
105
 
106
106
  // where
107
107
  sql += this.whereSQL(params);
@@ -118,17 +118,24 @@ module.exports = class Sql {
118
118
  };
119
119
 
120
120
  // JOINS
121
- addJoins() {
121
+ addJoins(params) {
122
122
  const { query, dialect } = this;
123
123
  const { esc } = dialect;
124
124
 
125
125
  let sql = '';
126
126
  _.each(query.joins, join => {
127
127
  const { src_schema, type, association, src_alias } = join;
128
- const [ assoc_type, name, Obj, src_column, column] = association;
128
+ const [ assoc_type, name, Obj, src_column, column, extraWhere] = association;
129
129
  const src_table_alias = src_alias || src_schema.table;
130
130
  const table = Obj.schema.table;
131
- sql += `${type.toUpperCase()} JOIN ${esc}${table}${esc} AS ${esc}${name}${esc} ON ${esc}${name}${esc}.${esc}${column}${esc} = ${esc}${src_table_alias}${esc}.${esc}${src_column}${esc} `;
131
+ let joinSql = `${type.toUpperCase()} JOIN ${esc}${table}${esc} AS ${esc}${name}${esc} ON ${esc}${name}${esc}.${esc}${column}${esc} = ${esc}${src_table_alias}${esc}.${esc}${src_column}${esc}`;
132
+ if (extraWhere) {
133
+ _.forOwn(extraWhere, (value, key) => {
134
+ joinSql += ` AND ${esc}${name}${esc}.${esc}${key}${esc} = ${dialect.param(this.i++)}`;
135
+ params.push(value);
136
+ });
137
+ }
138
+ sql += joinSql + ' ';
132
139
  });
133
140
  return sql;
134
141
  }
package/test/JoinTest.js CHANGED
@@ -202,6 +202,37 @@ describe('includes', () => {
202
202
  assert.strictEqual(foundBook.library.collection, library.collection);
203
203
  assert.strictEqual(foundBook.library.details.description, library.details.description);
204
204
  });
205
+
206
+ it('should apply extraWhere conditions on join', async () => {
207
+ class BookWithExtraWhere extends Model({
208
+ table: 'books',
209
+ primary: ['id'],
210
+ columns: [
211
+ 'id',
212
+ 'code',
213
+ 'title',
214
+ 'library_id',
215
+ ],
216
+ associations: () => ([
217
+ ['belongs_to', 'library', Library, 'library_id', 'id', { collection: 'A' }],
218
+ ])
219
+ }) {}
220
+
221
+ const libraryA = await Library.create({ title: 'Library A', collection: 'A' });
222
+ const libraryB = await Library.create({ title: 'Library B', collection: 'B' });
223
+ const bookA = await BookWithExtraWhere.create({ library_id: libraryA.id });
224
+ const bookB = await BookWithExtraWhere.create({ library_id: libraryB.id });
225
+
226
+ // Book A should have its library joined (collection = 'A' matches extraWhere)
227
+ const foundBookA = await BookWithExtraWhere.join('library').find(bookA.id);
228
+ assert.strictEqual(foundBookA.id, bookA.id);
229
+ assert.strictEqual(foundBookA.library.id, libraryA.id);
230
+
231
+ // Book B should NOT have its library joined (collection = 'B' does not match extraWhere)
232
+ const foundBookB = await BookWithExtraWhere.join('library').find(bookB.id);
233
+ assert.strictEqual(foundBookB.id, bookB.id);
234
+ assert.strictEqual(foundBookB.library, null);
235
+ });
205
236
  });
206
237
 
207
238
  });
@@ -1180,4 +1180,147 @@ describe('db.PaginatedOptimizedQuery', function() {
1180
1180
  'FULL phase should find block association for simple column name in direct associations');
1181
1181
  });
1182
1182
  });
1183
+
1184
+ //
1185
+ describe('extraWhere support in PaginatedOptimizedQuery', function() {
1186
+ // Modèle avec extraWhere sur l'association
1187
+ class Library extends Model({
1188
+ table: 'libraries',
1189
+ primary: ['id'],
1190
+ columns: {
1191
+ id: 'integer',
1192
+ title: 'string',
1193
+ collection: 'string'
1194
+ }
1195
+ }) {}
1196
+
1197
+ class BookWithExtraWhere extends Model({
1198
+ table: 'books',
1199
+ primary: ['id'],
1200
+ columns: {
1201
+ id: 'integer',
1202
+ code: 'string',
1203
+ title: 'string',
1204
+ library_id: 'integer'
1205
+ },
1206
+ associations: () => ([
1207
+ ['belongs_to', 'library', Library, 'library_id', 'id', { collection: 'A' }]
1208
+ ])
1209
+ }) {}
1210
+
1211
+ it('should apply extraWhere in EXISTS clause for filterJoin', () => {
1212
+ const query = mockGetDb(new PaginatedOptimizedQuery(BookWithExtraWhere));
1213
+ query.query.verb = 'count';
1214
+ query.where({
1215
+ 'library.title': 'Test%'
1216
+ });
1217
+
1218
+ const sql = query.toSQL();
1219
+
1220
+ // Vérifier que EXISTS est présent
1221
+ assert.ok(sql.sql.includes('EXISTS'), 'SQL should contain EXISTS');
1222
+
1223
+ // Vérifier que extraWhere est inclus dans la clause EXISTS
1224
+ assert.ok(sql.sql.includes('`libraries`.`collection`'), 'SQL should include extraWhere column');
1225
+ assert.ok(sql.params.includes('A'), 'SQL params should include extraWhere value');
1226
+
1227
+ // Le SQL devrait ressembler à :
1228
+ // EXISTS (SELECT 1 FROM `libraries` WHERE `libraries`.`id` = `books`.`library_id` AND `libraries`.`collection` = ? AND `libraries`.`title` LIKE ?)
1229
+ });
1230
+
1231
+ it('should apply extraWhere in LEFT JOIN for sorting', () => {
1232
+ const query = mockGetDb(new PaginatedOptimizedQuery(BookWithExtraWhere));
1233
+ query.query.verb = 'select_ids';
1234
+ query
1235
+ .where({ code: 'ABC' })
1236
+ .order('library.title ASC')
1237
+ .limit(50);
1238
+
1239
+ const sql = query.toSQL();
1240
+
1241
+ // Vérifier que LEFT JOIN est présent
1242
+ assert.ok(sql.sql.includes('LEFT JOIN `libraries`'), 'SQL should contain LEFT JOIN');
1243
+
1244
+ // Vérifier que extraWhere est inclus dans la condition du JOIN
1245
+ assert.ok(sql.sql.includes('`libraries`.`collection` = ?'), 'SQL should include extraWhere in JOIN condition');
1246
+ assert.ok(sql.params.includes('A'), 'SQL params should include extraWhere value');
1247
+
1248
+ // Le SQL devrait ressembler à :
1249
+ // LEFT JOIN `libraries` ON `libraries`.`id` = `books`.`library_id` AND `libraries`.`collection` = ?
1250
+ });
1251
+
1252
+ it('should apply extraWhere in both EXISTS (filter) and LEFT JOIN (sort)', () => {
1253
+ const query = mockGetDb(new PaginatedOptimizedQuery(BookWithExtraWhere));
1254
+ query.query.verb = 'select_ids';
1255
+ query
1256
+ .where({
1257
+ code: 'ABC',
1258
+ 'library.title': 'Test%'
1259
+ })
1260
+ .order('library.title ASC')
1261
+ .limit(50);
1262
+
1263
+ const sql = query.toSQL();
1264
+
1265
+ // Vérifier que EXISTS et LEFT JOIN sont présents
1266
+ assert.ok(sql.sql.includes('EXISTS'), 'SQL should contain EXISTS');
1267
+ assert.ok(sql.sql.includes('LEFT JOIN'), 'SQL should contain LEFT JOIN');
1268
+
1269
+ // Vérifier que extraWhere apparaît 2 fois (une fois dans EXISTS, une fois dans JOIN)
1270
+ const collectionMatches = sql.sql.match(/`libraries`\.`collection`/g) || [];
1271
+ assert.strictEqual(collectionMatches.length, 2, 'extraWhere should appear twice (EXISTS + JOIN)');
1272
+
1273
+ // Vérifier que 'A' est dans les params deux fois
1274
+ const aParams = sql.params.filter(p => p === 'A');
1275
+ assert.strictEqual(aParams.length, 2, 'extraWhere value should appear twice in params');
1276
+ });
1277
+
1278
+ it('should NOT include extraWhere in COUNT (no JOIN needed)', () => {
1279
+ const query = mockGetDb(new PaginatedOptimizedQuery(BookWithExtraWhere));
1280
+ query.query.verb = 'count';
1281
+ query
1282
+ .where({ code: 'ABC' })
1283
+ .order('library.title ASC'); // ORDER BY ignored in COUNT
1284
+
1285
+ const sql = query.toSQL();
1286
+
1287
+ // COUNT ne devrait pas avoir de LEFT JOIN
1288
+ assert.ok(!sql.sql.includes('LEFT JOIN'), 'COUNT should not have LEFT JOIN');
1289
+
1290
+ // COUNT ne devrait pas avoir de ORDER BY
1291
+ assert.ok(!sql.sql.includes('ORDER BY'), 'COUNT should not have ORDER BY');
1292
+
1293
+ // Mais si on avait un filtre sur library, on aurait EXISTS avec extraWhere
1294
+ });
1295
+
1296
+ // Test avec plusieurs conditions extraWhere
1297
+ class BookWithMultipleExtraWhere extends Model({
1298
+ table: 'books_multi',
1299
+ primary: ['id'],
1300
+ columns: {
1301
+ id: 'integer',
1302
+ title: 'string',
1303
+ library_id: 'integer'
1304
+ },
1305
+ associations: () => ([
1306
+ ['belongs_to', 'library', Library, 'library_id', 'id', { collection: 'A', title: 'Main' }]
1307
+ ])
1308
+ }) {}
1309
+
1310
+ it('should apply multiple extraWhere conditions', () => {
1311
+ const query = mockGetDb(new PaginatedOptimizedQuery(BookWithMultipleExtraWhere));
1312
+ query.query.verb = 'count';
1313
+ query.where({
1314
+ 'library.title': 'Test%'
1315
+ });
1316
+
1317
+ const sql = query.toSQL();
1318
+
1319
+ // Vérifier que les deux conditions extraWhere sont présentes
1320
+ assert.ok(sql.sql.includes('`libraries`.`collection`'), 'SQL should include collection extraWhere');
1321
+ assert.ok(sql.sql.includes('`libraries`.`title`'), 'SQL should include title in filter condition');
1322
+ assert.ok(sql.params.includes('A'), 'SQL params should include collection value');
1323
+ assert.ok(sql.params.includes('Main'), 'SQL params should include title extraWhere value');
1324
+ });
1325
+ });
1183
1326
  });