@igojs/db 6.0.0-beta.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 +153 -0
- package/examples/PaginatedOptimizedQueryExample.js +936 -0
- package/index.js +27 -0
- package/package.json +27 -0
- package/src/CacheStats.js +33 -0
- package/src/CachedQuery.js +40 -0
- package/src/DataTypes.js +23 -0
- package/src/Db.js +147 -0
- package/src/Model.js +261 -0
- package/src/PaginatedOptimizedQuery.js +902 -0
- package/src/PaginatedOptimizedSql.js +1352 -0
- package/src/Query.js +584 -0
- package/src/Schema.js +52 -0
- package/src/Sql.js +311 -0
- package/src/context.js +12 -0
- package/src/dbs.js +26 -0
- package/src/drivers/mysql.js +74 -0
- package/src/drivers/postgresql.js +70 -0
- package/src/migrations.js +140 -0
- package/test/AssociationsTest.js +301 -0
- package/test/CacheStatsTest.js +40 -0
- package/test/CachedQueryTest.js +49 -0
- package/test/JoinTest.js +207 -0
- package/test/ModelTest.js +510 -0
- package/test/PaginatedOptimizedQueryTest.js +1183 -0
- package/test/PerfTest.js +58 -0
- package/test/PostgreSqlTest.js +95 -0
- package/test/QueryTest.js +27 -0
- package/test/SimplifiedSyntaxTest.js +473 -0
- package/test/SqlTest.js +95 -0
- package/test/init.js +2 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
// Tests unitaires sans dépendance sur la base de données
|
|
2
|
+
// On teste uniquement la génération SQL, pas l'exécution
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
|
|
6
|
+
const PaginatedOptimizedQuery = require('@igojs/db').PaginatedOptimizedQuery;
|
|
7
|
+
const Model = require('@igojs/db').Model;
|
|
8
|
+
|
|
9
|
+
// Helper pour mocker getDb
|
|
10
|
+
const mockGetDb = (query) => {
|
|
11
|
+
query.getDb = () => ({
|
|
12
|
+
driver: {
|
|
13
|
+
dialect: {
|
|
14
|
+
esc: '`',
|
|
15
|
+
param: (i) => '?',
|
|
16
|
+
in: 'IN',
|
|
17
|
+
notin: 'NOT IN',
|
|
18
|
+
limit: (offsetParam, limitParam) => `LIMIT ?, ?`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return query;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//
|
|
26
|
+
describe('db.PaginatedOptimizedQuery', function() {
|
|
27
|
+
|
|
28
|
+
// Définition des modèles de test
|
|
29
|
+
class Country extends Model({
|
|
30
|
+
table: 'countries',
|
|
31
|
+
columns: {
|
|
32
|
+
id: 'integer',
|
|
33
|
+
code: 'string',
|
|
34
|
+
name: 'string'
|
|
35
|
+
}
|
|
36
|
+
}) {}
|
|
37
|
+
|
|
38
|
+
class Company extends Model({
|
|
39
|
+
table: 'companies',
|
|
40
|
+
primary: ['id'],
|
|
41
|
+
columns: {
|
|
42
|
+
id: 'integer',
|
|
43
|
+
name: 'string',
|
|
44
|
+
siret: 'string',
|
|
45
|
+
country_id: 'integer'
|
|
46
|
+
},
|
|
47
|
+
associations: [
|
|
48
|
+
['belongs_to', 'country', Country, 'country_id', 'id']
|
|
49
|
+
]
|
|
50
|
+
}) {}
|
|
51
|
+
|
|
52
|
+
class PmeFolder extends Model({
|
|
53
|
+
table: 'pme_folders',
|
|
54
|
+
primary: ['id'],
|
|
55
|
+
columns: {
|
|
56
|
+
id: 'integer',
|
|
57
|
+
status: 'string',
|
|
58
|
+
company_id: 'integer',
|
|
59
|
+
company_name: 'string',
|
|
60
|
+
block_studies_id: 'integer'
|
|
61
|
+
},
|
|
62
|
+
associations: [
|
|
63
|
+
['belongs_to', 'company', Company, 'company_id', 'id']
|
|
64
|
+
]
|
|
65
|
+
}) {}
|
|
66
|
+
|
|
67
|
+
// Separate class to avoid circular dependencies in test setup
|
|
68
|
+
// This will be used for testing nested block paths
|
|
69
|
+
class PmeFolderWithBlocks extends PmeFolder {}
|
|
70
|
+
|
|
71
|
+
// Define associations after class declaration to handle forward references
|
|
72
|
+
PmeFolderWithBlocks.schema.columns.block_studies_id = 'integer';
|
|
73
|
+
PmeFolderWithBlocks.schema.columns.block_travel_wishes_id = 'integer';
|
|
74
|
+
PmeFolderWithBlocks.schema.colsByName = PmeFolderWithBlocks.schema.colsByName || {};
|
|
75
|
+
PmeFolderWithBlocks.schema.colsByName.block_studies_id = 'integer';
|
|
76
|
+
PmeFolderWithBlocks.schema.colsByName.block_travel_wishes_id = 'integer';
|
|
77
|
+
|
|
78
|
+
class Applicant extends Model({
|
|
79
|
+
table: 'applicants',
|
|
80
|
+
primary: ['id'],
|
|
81
|
+
columns: {
|
|
82
|
+
id: 'integer',
|
|
83
|
+
first_name: 'string',
|
|
84
|
+
last_name: 'string',
|
|
85
|
+
email: 'string'
|
|
86
|
+
}
|
|
87
|
+
}) {}
|
|
88
|
+
|
|
89
|
+
class Folder extends Model({
|
|
90
|
+
table: 'folders',
|
|
91
|
+
primary: ['id'],
|
|
92
|
+
columns: {
|
|
93
|
+
id: 'integer',
|
|
94
|
+
type: 'string',
|
|
95
|
+
status: 'string',
|
|
96
|
+
applicant_id: 'integer',
|
|
97
|
+
pme_folder_id: 'integer',
|
|
98
|
+
created_at: 'datetime'
|
|
99
|
+
},
|
|
100
|
+
associations: [
|
|
101
|
+
['belongs_to', 'applicant', Applicant, 'applicant_id', 'id'],
|
|
102
|
+
['belongs_to', 'pme_folder', PmeFolder, 'pme_folder_id', 'id']
|
|
103
|
+
]
|
|
104
|
+
}) {}
|
|
105
|
+
|
|
106
|
+
// Modèles de test pour les "blocks" (sous-tables)
|
|
107
|
+
class StudiesBlock extends Model({
|
|
108
|
+
table: 'block_studies',
|
|
109
|
+
primary: ['id'],
|
|
110
|
+
columns: [
|
|
111
|
+
'id',
|
|
112
|
+
'ine_number',
|
|
113
|
+
'student_status',
|
|
114
|
+
'studies_year',
|
|
115
|
+
'studies_field',
|
|
116
|
+
{ name: 'bac_year', type: 'integer' }
|
|
117
|
+
]
|
|
118
|
+
}) {}
|
|
119
|
+
|
|
120
|
+
class TravelWishesBlock extends Model({
|
|
121
|
+
table: 'block_travel_wishes',
|
|
122
|
+
primary: ['id'],
|
|
123
|
+
columns: [
|
|
124
|
+
'id',
|
|
125
|
+
{ name: 'departure_date', type: 'date' },
|
|
126
|
+
{ name: 'return_date', type: 'date' },
|
|
127
|
+
'destination'
|
|
128
|
+
]
|
|
129
|
+
}) {}
|
|
130
|
+
|
|
131
|
+
// Now add block associations to PmeFolderWithBlocks (must be after block classes are defined)
|
|
132
|
+
PmeFolderWithBlocks.schema.associations = [
|
|
133
|
+
['belongs_to', 'company', Company, 'company_id', 'id'],
|
|
134
|
+
['belongs_to', 'block_study', StudiesBlock, 'block_studies_id', 'id'],
|
|
135
|
+
['belongs_to', 'block_travel_wish', TravelWishesBlock, 'block_travel_wishes_id', 'id']
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// Folder variant with PmeFolderWithBlocks for testing nested block paths
|
|
139
|
+
class FolderWithNestedBlocks extends Model({
|
|
140
|
+
table: 'folders',
|
|
141
|
+
primary: ['id'],
|
|
142
|
+
columns: {
|
|
143
|
+
id: 'integer',
|
|
144
|
+
type: 'string',
|
|
145
|
+
status: 'string',
|
|
146
|
+
applicant_id: 'integer',
|
|
147
|
+
pme_folder_id: 'integer',
|
|
148
|
+
created_at: 'datetime'
|
|
149
|
+
},
|
|
150
|
+
associations: [
|
|
151
|
+
['belongs_to', 'applicant', Applicant, 'applicant_id', 'id'],
|
|
152
|
+
['belongs_to', 'pme_folder', PmeFolderWithBlocks, 'pme_folder_id', 'id']
|
|
153
|
+
]
|
|
154
|
+
}) {}
|
|
155
|
+
|
|
156
|
+
class PMEFolderWithBlocks extends Model({
|
|
157
|
+
table: 'pme_folders_with_blocks',
|
|
158
|
+
primary: ['id'],
|
|
159
|
+
columns: [
|
|
160
|
+
'id',
|
|
161
|
+
{ name: 'block_studies_id', type: 'integer' },
|
|
162
|
+
{ name: 'block_travel_wishes_id', type: 'integer' },
|
|
163
|
+
'professional_activity',
|
|
164
|
+
{ name: 'is_initial', type: 'boolean' }
|
|
165
|
+
],
|
|
166
|
+
associations: () => [
|
|
167
|
+
['belongs_to', 'studies', StudiesBlock, 'block_studies_id', 'id'],
|
|
168
|
+
['belongs_to', 'travel_wishes', TravelWishesBlock, 'block_travel_wishes_id', 'id']
|
|
169
|
+
]
|
|
170
|
+
}) {}
|
|
171
|
+
|
|
172
|
+
//
|
|
173
|
+
describe('Simplified Syntax - where() with nested paths', function() {
|
|
174
|
+
it('should detect simple path (1 level)', () => {
|
|
175
|
+
const query = new PaginatedOptimizedQuery(Folder);
|
|
176
|
+
query.where({
|
|
177
|
+
status: 'SUBMITTED',
|
|
178
|
+
'applicant.last_name': 'Dupont'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Vérifier que filterJoins a été créé
|
|
182
|
+
assert.ok(query.query.filterJoins.length > 0, 'filterJoins should contain at least one element');
|
|
183
|
+
|
|
184
|
+
// Vérifier que le filtre sur la table principale est dans where
|
|
185
|
+
assert.ok(query.query.where.length > 0, 'where should contain the condition on status');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should detect nested path (3 levels)', () => {
|
|
189
|
+
const query = new PaginatedOptimizedQuery(Folder);
|
|
190
|
+
query.where({
|
|
191
|
+
'pme_folder.company.country.code': 'FR'
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Vérifier que filterJoins nested a été créé
|
|
195
|
+
assert.ok(query.query.filterJoins.length > 0, 'filterJoins should be created');
|
|
196
|
+
assert.equal(query.query.filterJoins[0].type, 'nested', 'filterJoin should be of type nested');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should group conditions on the same table', () => {
|
|
200
|
+
const query = new PaginatedOptimizedQuery(Folder);
|
|
201
|
+
query.where({
|
|
202
|
+
'applicant.last_name': 'Dupont',
|
|
203
|
+
'applicant.first_name': 'Jean',
|
|
204
|
+
'applicant.email': 'test@test.com'
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Vérifier qu'un seul filterJoin a été créé (regroupement)
|
|
208
|
+
assert.equal(query.query.filterJoins.length, 1, 'Should create a single filterJoin for applicant');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
//
|
|
213
|
+
describe('countSQL with EXISTS', function() {
|
|
214
|
+
it('should generate COUNT SQL with EXISTS for nested paths', () => {
|
|
215
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
216
|
+
query.query.verb = 'count';
|
|
217
|
+
query.where({
|
|
218
|
+
type: ['agp', 'avt'],
|
|
219
|
+
'applicant.last_name': 'Dupont%'
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const sql = query.toSQL();
|
|
223
|
+
|
|
224
|
+
// Vérifier que le SQL contient EXISTS
|
|
225
|
+
assert.ok(sql.sql.includes('EXISTS'), 'SQL should contain EXISTS');
|
|
226
|
+
assert.ok(sql.sql.includes('SELECT 1 FROM'), 'SQL should contain SELECT 1 FROM');
|
|
227
|
+
assert.ok(sql.sql.includes('applicants'), 'SQL should reference applicants table');
|
|
228
|
+
assert.ok(sql.sql.includes('COUNT(0)'), 'SQL should contain COUNT(0)');
|
|
229
|
+
|
|
230
|
+
// Vérifier qu'il n'y a pas de LEFT JOIN
|
|
231
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'SQL should not contain LEFT JOIN');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should generate COUNT SQL with multiple EXISTS', () => {
|
|
235
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
236
|
+
query.query.verb = 'count';
|
|
237
|
+
query.where({
|
|
238
|
+
type: 'agp',
|
|
239
|
+
'applicant.last_name': 'Dupont%',
|
|
240
|
+
'pme_folder.status': 'ACTIVE'
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const sql = query.toSQL();
|
|
244
|
+
|
|
245
|
+
// Vérifier qu'il y a 2 EXISTS
|
|
246
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
247
|
+
assert.strictEqual(existsCount, 2, 'SQL should contain 2 EXISTS clauses');
|
|
248
|
+
|
|
249
|
+
// Vérifier les tables
|
|
250
|
+
assert.ok(sql.sql.includes('applicants'), 'SQL should reference applicants');
|
|
251
|
+
assert.ok(sql.sql.includes('pme_folders'), 'SQL should reference pme_folders');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should generate nested EXISTS for deep paths', () => {
|
|
255
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
256
|
+
query.query.verb = 'count';
|
|
257
|
+
query.where({
|
|
258
|
+
'pme_folder.company.country.code': 'FR'
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const sql = query.toSQL();
|
|
262
|
+
|
|
263
|
+
// Vérifier que des EXISTS imbriqués sont générés
|
|
264
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
265
|
+
assert.ok(existsCount >= 3, 'SQL should contain at least 3 nested EXISTS');
|
|
266
|
+
|
|
267
|
+
// Vérifier que les tables sont mentionnées
|
|
268
|
+
assert.ok(sql.sql.includes('pme_folders'), 'SQL should reference pme_folders');
|
|
269
|
+
assert.ok(sql.sql.includes('companies'), 'SQL should reference companies');
|
|
270
|
+
assert.ok(sql.sql.includes('countries'), 'SQL should reference countries');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
//
|
|
275
|
+
describe('idsSQL', function() {
|
|
276
|
+
it('should generate SELECT IDs SQL with EXISTS', () => {
|
|
277
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
278
|
+
query.query.verb = 'select_ids';
|
|
279
|
+
query.where({
|
|
280
|
+
type: 'agp',
|
|
281
|
+
'applicant.last_name': 'Dupont%'
|
|
282
|
+
})
|
|
283
|
+
.order('folders.created_at DESC')
|
|
284
|
+
.limit(50);
|
|
285
|
+
|
|
286
|
+
const sql = query.toSQL();
|
|
287
|
+
|
|
288
|
+
// Vérifier la structure
|
|
289
|
+
assert.ok(sql.sql.includes('SELECT'), 'SQL should contain SELECT');
|
|
290
|
+
assert.ok(sql.sql.includes('`folders`.`id`'), 'SQL should select folders.id');
|
|
291
|
+
assert.ok(sql.sql.includes('EXISTS'), 'SQL should contain EXISTS');
|
|
292
|
+
assert.ok(sql.sql.includes('ORDER BY'), 'SQL should contain ORDER BY');
|
|
293
|
+
assert.ok(sql.sql.includes('LIMIT'), 'SQL should contain LIMIT');
|
|
294
|
+
|
|
295
|
+
// Pas de LEFT JOIN
|
|
296
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'SQL should not contain LEFT JOIN');
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
//
|
|
301
|
+
describe('Model.paginatedOptimized()', function() {
|
|
302
|
+
it('should return PaginatedOptimizedQuery instance', () => {
|
|
303
|
+
const query = Folder.paginatedOptimized();
|
|
304
|
+
assert.ok(query instanceof PaginatedOptimizedQuery, 'Should return PaginatedOptimizedQuery instance');
|
|
305
|
+
assert.strictEqual(query.query.optimized, true, 'Query should be marked as optimized');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should support method chaining with new syntax', () => {
|
|
309
|
+
const query = Folder.paginatedOptimized()
|
|
310
|
+
.where({
|
|
311
|
+
type: 'agp',
|
|
312
|
+
'applicant.last_name': 'Dupont%'
|
|
313
|
+
})
|
|
314
|
+
.order('folders.created_at DESC');
|
|
315
|
+
|
|
316
|
+
assert.strictEqual(query.query.where.length, 1);
|
|
317
|
+
assert.strictEqual(query.query.filterJoins.length, 1);
|
|
318
|
+
assert.strictEqual(query.query.order.length, 1);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
//
|
|
323
|
+
describe('SQL generation patterns', function() {
|
|
324
|
+
it('should generate correct WHERE EXISTS clause', () => {
|
|
325
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
326
|
+
query.query.verb = 'count';
|
|
327
|
+
query.where({
|
|
328
|
+
status: 'SUBMITTED',
|
|
329
|
+
'applicant.last_name': 'Dupont%',
|
|
330
|
+
'applicant.email': 'test@example.com'
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const sql = query.toSQL();
|
|
334
|
+
|
|
335
|
+
// Vérifier la structure de EXISTS
|
|
336
|
+
assert.ok(sql.sql.includes('WHERE `folders`.`status`'), 'Should have WHERE on folders.status');
|
|
337
|
+
assert.ok(sql.sql.includes('AND EXISTS (SELECT 1 FROM `applicants`'), 'Should have EXISTS with applicants');
|
|
338
|
+
assert.ok(sql.sql.includes('WHERE `applicants`.`id` = `folders`.`applicant_id`'), 'Should have join condition');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should handle LIKE patterns with %', () => {
|
|
342
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
343
|
+
query.query.verb = 'count';
|
|
344
|
+
query.where({
|
|
345
|
+
'applicant.last_name': 'Dupont%'
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const sql = query.toSQL();
|
|
349
|
+
|
|
350
|
+
// Le % dans la valeur devrait générer un LIKE
|
|
351
|
+
assert.ok(sql.sql.includes('LIKE'), 'Should generate LIKE for patterns with %');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should handle IN arrays', () => {
|
|
355
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
356
|
+
query.query.verb = 'count';
|
|
357
|
+
query.where({
|
|
358
|
+
status: ['SUBMITTED', 'VALIDATED', 'APPROVED']
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const sql = query.toSQL();
|
|
362
|
+
|
|
363
|
+
// Les tableaux devraient générer un IN
|
|
364
|
+
assert.ok(sql.sql.includes('IN'), 'Should generate IN for arrays');
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
//
|
|
369
|
+
describe('Performance optimization verification', function() {
|
|
370
|
+
it('COUNT should not include LEFT JOIN', () => {
|
|
371
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
372
|
+
query
|
|
373
|
+
.where({
|
|
374
|
+
type: 'agp',
|
|
375
|
+
'applicant.last_name': 'Dupont%'
|
|
376
|
+
})
|
|
377
|
+
.join('pme_folder'); // Ce join ne doit pas apparaître dans COUNT
|
|
378
|
+
|
|
379
|
+
query.query.verb = 'count';
|
|
380
|
+
const sql = query.toSQL();
|
|
381
|
+
|
|
382
|
+
// Le COUNT ne doit pas avoir de LEFT JOIN
|
|
383
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'COUNT should not have LEFT JOIN');
|
|
384
|
+
|
|
385
|
+
// Mais doit avoir EXISTS pour le filtre
|
|
386
|
+
assert.ok(sql.sql.includes('EXISTS'), 'COUNT should have EXISTS for filter');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('IDS query should be minimal', () => {
|
|
390
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
391
|
+
query.query.verb = 'select_ids';
|
|
392
|
+
query.where({
|
|
393
|
+
type: 'agp',
|
|
394
|
+
'applicant.last_name': 'Dupont%'
|
|
395
|
+
})
|
|
396
|
+
.limit(50);
|
|
397
|
+
|
|
398
|
+
const sql = query.toSQL();
|
|
399
|
+
|
|
400
|
+
// Vérifier que seuls les IDs sont sélectionnés
|
|
401
|
+
assert.ok(sql.sql.includes('SELECT `folders`.`id`'), 'Should only select IDs');
|
|
402
|
+
|
|
403
|
+
// Pas de sélection d'autres colonnes
|
|
404
|
+
assert.ok(!sql.sql.includes('`folders`.*'), 'Should not select all columns');
|
|
405
|
+
|
|
406
|
+
// Pas de LEFT JOIN
|
|
407
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'Should not have LEFT JOIN');
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
//
|
|
412
|
+
describe('Complex scenarios', function() {
|
|
413
|
+
it('should handle $and with multiple conditions', () => {
|
|
414
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
415
|
+
query.query.verb = 'count';
|
|
416
|
+
query.where({
|
|
417
|
+
$and: [
|
|
418
|
+
{ status: 'SUBMITTED' },
|
|
419
|
+
{ 'applicant.last_name': 'Dupont%' },
|
|
420
|
+
{ 'applicant.first_name': 'Jean%' }
|
|
421
|
+
]
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const sql = query.toSQL();
|
|
425
|
+
|
|
426
|
+
// Vérifier que les conditions sont présentes
|
|
427
|
+
assert.ok(sql.sql.includes('status'), 'Should include status condition');
|
|
428
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Should include EXISTS for applicant');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should combine main table and joined table filters', () => {
|
|
432
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
433
|
+
query.query.verb = 'count';
|
|
434
|
+
query.where({
|
|
435
|
+
type: ['agp', 'avt', 'cga'],
|
|
436
|
+
status: 'SUBMITTED',
|
|
437
|
+
'applicant.last_name': 'Dupont%',
|
|
438
|
+
'pme_folder.status': 'ACTIVE'
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const sql = query.toSQL();
|
|
442
|
+
|
|
443
|
+
// Vérifier que tous les WHERE sont présents
|
|
444
|
+
assert.ok(sql.sql.includes('`folders`.`type`'), 'Should include type filter');
|
|
445
|
+
assert.ok(sql.sql.includes('`folders`.`status`'), 'Should include status filter');
|
|
446
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Should include EXISTS for filterJoin');
|
|
447
|
+
|
|
448
|
+
// Vérifier qu'il y a 2 EXISTS (applicant + pme_folder)
|
|
449
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
450
|
+
assert.ok(existsCount >= 2, 'Should have at least 2 EXISTS');
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
//
|
|
455
|
+
describe('Sorting on joined tables', function() {
|
|
456
|
+
it('should add INNER JOIN when sorting on joined table column', () => {
|
|
457
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
458
|
+
query.query.verb = 'select_ids';
|
|
459
|
+
query
|
|
460
|
+
.where({
|
|
461
|
+
type: ['agp', 'avt']
|
|
462
|
+
})
|
|
463
|
+
.order('applicants.last_name ASC')
|
|
464
|
+
.join('applicant')
|
|
465
|
+
.limit(50);
|
|
466
|
+
|
|
467
|
+
const sql = query.toSQL();
|
|
468
|
+
|
|
469
|
+
// Vérifier que l'INNER JOIN est présent
|
|
470
|
+
assert.ok(sql.sql.includes('LEFT JOIN'), 'SQL should contain LEFT JOIN for sorting on joined table');
|
|
471
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should INNER JOIN the applicants table');
|
|
472
|
+
assert.ok(sql.sql.includes('ON `applicants`.`id` = `folders`.`applicant_id`'), 'SQL should have correct join condition');
|
|
473
|
+
|
|
474
|
+
// Vérifier que le ORDER BY est présent
|
|
475
|
+
assert.ok(sql.sql.includes('ORDER BY applicants.last_name ASC'), 'SQL should have ORDER BY clause');
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should NOT add INNER JOIN when sorting on main table column', () => {
|
|
479
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
480
|
+
query.query.verb = 'select_ids';
|
|
481
|
+
query
|
|
482
|
+
.where({ type: 'agp' })
|
|
483
|
+
.order('folders.created_at DESC')
|
|
484
|
+
.limit(50);
|
|
485
|
+
|
|
486
|
+
const sql = query.toSQL();
|
|
487
|
+
|
|
488
|
+
// Vérifier qu'il n'y a PAS de JOIN
|
|
489
|
+
assert.ok(!sql.sql.includes('INNER JOIN'), 'SQL should not contain INNER JOIN when sorting on main table');
|
|
490
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'SQL should not contain LEFT JOIN when sorting on main table');
|
|
491
|
+
|
|
492
|
+
// Vérifier que le ORDER BY est présent
|
|
493
|
+
assert.ok(sql.sql.includes('ORDER BY'), 'SQL should have ORDER BY clause');
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should add INNER JOIN for nested sorted columns', () => {
|
|
497
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
498
|
+
query.query.verb = 'select_ids';
|
|
499
|
+
query
|
|
500
|
+
.where({
|
|
501
|
+
type: 'agp',
|
|
502
|
+
'pme_folder.company.country.code': 'FR'
|
|
503
|
+
})
|
|
504
|
+
.order('companies.name ASC') // Tri sur une table imbriquée
|
|
505
|
+
.join('pme_folder.company.country')
|
|
506
|
+
.limit(50);
|
|
507
|
+
|
|
508
|
+
const sql = query.toSQL();
|
|
509
|
+
|
|
510
|
+
// Vérifier que les INNER JOIN en cascade sont présents
|
|
511
|
+
assert.ok(sql.sql.includes('LEFT JOIN `pme_folders`'), 'SQL should INNER JOIN pme_folders');
|
|
512
|
+
assert.ok(sql.sql.includes('LEFT JOIN `companies`'), 'SQL should INNER JOIN companies');
|
|
513
|
+
|
|
514
|
+
// Vérifier le ORDER BY
|
|
515
|
+
assert.ok(sql.sql.includes('ORDER BY companies.name ASC'), 'SQL should sort by companies.name');
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('should combine INNER JOIN for sorting with EXISTS for filtering', () => {
|
|
519
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
520
|
+
query.query.verb = 'select_ids';
|
|
521
|
+
query
|
|
522
|
+
.where({
|
|
523
|
+
type: 'agp',
|
|
524
|
+
'applicant.email': '%@example.com'
|
|
525
|
+
})
|
|
526
|
+
.order('applicants.last_name ASC')
|
|
527
|
+
.join('applicant')
|
|
528
|
+
.limit(50);
|
|
529
|
+
|
|
530
|
+
const sql = query.toSQL();
|
|
531
|
+
|
|
532
|
+
// Vérifier l'INNER JOIN pour le tri
|
|
533
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should have INNER JOIN for sorting');
|
|
534
|
+
|
|
535
|
+
// Vérifier EXISTS pour le filtre
|
|
536
|
+
assert.ok(sql.sql.includes('EXISTS'), 'SQL should have EXISTS for filtering');
|
|
537
|
+
assert.ok(sql.sql.includes('email'), 'SQL should filter on email');
|
|
538
|
+
|
|
539
|
+
// Vérifier ORDER BY
|
|
540
|
+
assert.ok(sql.sql.includes('ORDER BY applicants.last_name ASC'), 'SQL should sort by applicants.last_name');
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('should NOT add INNER JOIN in COUNT phase even with sort on joined table', () => {
|
|
544
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
545
|
+
query.query.verb = 'count';
|
|
546
|
+
query
|
|
547
|
+
.where({ type: 'agp' })
|
|
548
|
+
.order('applicants.last_name ASC')
|
|
549
|
+
.join('applicant')
|
|
550
|
+
.limit(50);
|
|
551
|
+
|
|
552
|
+
const sql = query.toSQL();
|
|
553
|
+
|
|
554
|
+
// COUNT ne devrait PAS avoir de JOIN (même si on trie sur une table jointe)
|
|
555
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'COUNT should not have LEFT JOIN for sorting');
|
|
556
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'COUNT should not have LEFT JOIN');
|
|
557
|
+
|
|
558
|
+
// COUNT ne devrait PAS avoir de ORDER BY
|
|
559
|
+
assert.ok(!sql.sql.includes('ORDER BY'), 'COUNT should not have ORDER BY');
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('should transform association names to table names in ORDER BY (pmfp_folder.company case)', () => {
|
|
563
|
+
// Test pour vérifier que "pmfp_folder.company.name" devient "companies.name"
|
|
564
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
565
|
+
query.query.verb = 'select_ids';
|
|
566
|
+
query
|
|
567
|
+
.where({ type: 'agp' })
|
|
568
|
+
.order('pme_folder.company.name ASC')
|
|
569
|
+
.join('pme_folder.company')
|
|
570
|
+
.limit(50);
|
|
571
|
+
|
|
572
|
+
const sql = query.toSQL();
|
|
573
|
+
|
|
574
|
+
// Vérifier les INNER JOIN en cascade
|
|
575
|
+
assert.ok(sql.sql.includes('LEFT JOIN `pme_folders`'), 'SQL should INNER JOIN pme_folders');
|
|
576
|
+
assert.ok(sql.sql.includes('LEFT JOIN `companies`'), 'SQL should INNER JOIN companies');
|
|
577
|
+
|
|
578
|
+
// Vérifier que le ORDER BY utilise le nom de TABLE (companies) et non d'association (company)
|
|
579
|
+
assert.ok(sql.sql.includes('ORDER BY companies.name ASC'), 'SQL should use table name "companies" in ORDER BY');
|
|
580
|
+
assert.ok(!sql.sql.includes('company.name'), 'SQL should NOT use association name "company" in ORDER BY');
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
describe('ORDER BY avec Fonctions SQL', function() {
|
|
585
|
+
it('devrait gérer COALESCE avec plusieurs colonnes de la même table', () => {
|
|
586
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
587
|
+
query.query.verb = 'select_ids';
|
|
588
|
+
query
|
|
589
|
+
.where({ type: 'agp' })
|
|
590
|
+
.join('applicant')
|
|
591
|
+
.order('COALESCE(`applicant`.`last_name`, `applicant`.`first_name`) ASC')
|
|
592
|
+
.limit(50);
|
|
593
|
+
|
|
594
|
+
const sql = query.toSQL();
|
|
595
|
+
|
|
596
|
+
// Doit contenir l'INNER JOIN
|
|
597
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should INNER JOIN applicants');
|
|
598
|
+
|
|
599
|
+
// Doit conserver la fonction COALESCE dans ORDER BY
|
|
600
|
+
assert.ok(sql.sql.includes('ORDER BY COALESCE'), 'SQL should preserve COALESCE function');
|
|
601
|
+
assert.ok(sql.sql.includes('applicants.last_name'), 'SQL should include last_name column (transformed from association to table name)');
|
|
602
|
+
assert.ok(sql.sql.includes('applicants.first_name'), 'SQL should include first_name column (transformed from association to table name)');
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('devrait gérer IFNULL avec table imbriquée', () => {
|
|
606
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
607
|
+
query.query.verb = 'select_ids';
|
|
608
|
+
query
|
|
609
|
+
.where({ type: 'pme' })
|
|
610
|
+
.join('pme_folder.company')
|
|
611
|
+
.order('IFNULL(`companies`.`name`, "N/A") ASC')
|
|
612
|
+
.limit(50);
|
|
613
|
+
|
|
614
|
+
const sql = query.toSQL();
|
|
615
|
+
|
|
616
|
+
// Doit contenir les INNER JOIN pour la chaîne d'associations
|
|
617
|
+
assert.ok(sql.sql.includes('LEFT JOIN `pme_folders`'), 'SQL should INNER JOIN pme_folders');
|
|
618
|
+
assert.ok(sql.sql.includes('LEFT JOIN `companies`'), 'SQL should INNER JOIN companies');
|
|
619
|
+
|
|
620
|
+
// Doit conserver IFNULL dans ORDER BY
|
|
621
|
+
assert.ok(sql.sql.includes('ORDER BY IFNULL'), 'SQL should preserve IFNULL function');
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('devrait gérer CONCAT avec colonnes multiples', () => {
|
|
625
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
626
|
+
query.query.verb = 'select_ids';
|
|
627
|
+
query
|
|
628
|
+
.where({ type: 'agp' })
|
|
629
|
+
.join('applicant')
|
|
630
|
+
.order('CONCAT(`applicant`.`last_name`, " ", `applicant`.`first_name`) ASC')
|
|
631
|
+
.limit(50);
|
|
632
|
+
|
|
633
|
+
const sql = query.toSQL();
|
|
634
|
+
|
|
635
|
+
// Doit contenir l'INNER JOIN
|
|
636
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should INNER JOIN applicants');
|
|
637
|
+
|
|
638
|
+
// Doit conserver CONCAT dans ORDER BY
|
|
639
|
+
assert.ok(sql.sql.includes('ORDER BY CONCAT'), 'SQL should preserve CONCAT function');
|
|
640
|
+
assert.ok(sql.sql.includes('applicants.last_name'), 'SQL should include last_name column (transformed from association to table name)');
|
|
641
|
+
assert.ok(sql.sql.includes('applicants.first_name'), 'SQL should include first_name column (transformed from association to table name)');
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('devrait gérer COALESCE avec tables imbriquées', () => {
|
|
645
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
646
|
+
query.query.verb = 'select_ids';
|
|
647
|
+
query
|
|
648
|
+
.where({ type: 'pme' })
|
|
649
|
+
.join('applicant')
|
|
650
|
+
.join('pme_folder.company')
|
|
651
|
+
.order('COALESCE(`applicant`.`last_name`, `companies`.`name`) ASC')
|
|
652
|
+
.limit(50);
|
|
653
|
+
|
|
654
|
+
const sql = query.toSQL();
|
|
655
|
+
|
|
656
|
+
// Doit contenir les INNER JOIN
|
|
657
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should INNER JOIN applicants');
|
|
658
|
+
assert.ok(sql.sql.includes('LEFT JOIN `pme_folders`'), 'SQL should INNER JOIN pme_folders');
|
|
659
|
+
assert.ok(sql.sql.includes('LEFT JOIN `companies`'), 'SQL should INNER JOIN companies');
|
|
660
|
+
|
|
661
|
+
// Doit conserver COALESCE dans ORDER BY
|
|
662
|
+
assert.ok(sql.sql.includes('ORDER BY COALESCE'), 'SQL should preserve COALESCE function');
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('ne devrait pas ajouter de JOIN pour les fonctions sur la table principale', () => {
|
|
666
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
667
|
+
query.query.verb = 'select_ids';
|
|
668
|
+
query
|
|
669
|
+
.where({ type: 'agp' })
|
|
670
|
+
.order('UPPER(`folders`.`name`) ASC')
|
|
671
|
+
.limit(50);
|
|
672
|
+
|
|
673
|
+
const sql = query.toSQL();
|
|
674
|
+
|
|
675
|
+
// Ne doit pas contenir de INNER JOIN (seulement la table principale)
|
|
676
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'SQL should not contain LEFT JOIN for functions on main table');
|
|
677
|
+
|
|
678
|
+
// Doit conserver la fonction UPPER
|
|
679
|
+
assert.ok(sql.sql.includes('ORDER BY UPPER'), 'SQL should preserve UPPER function');
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should not double-transform paths in COALESCE (idempotence)', () => {
|
|
683
|
+
// Test pour vérifier qu'il n'y a pas de double-transformation
|
|
684
|
+
// Ce test vérifie le bug: applicant.last_name dans un COALESCE
|
|
685
|
+
// ne doit PAS être transformé 2 fois et devenir applicants.something.last_name
|
|
686
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
687
|
+
query.query.verb = 'select_ids';
|
|
688
|
+
query
|
|
689
|
+
.where({ type: 'agp' })
|
|
690
|
+
.join('applicant')
|
|
691
|
+
.join('pme_folder.company')
|
|
692
|
+
.order('COALESCE(applicant.last_name, applicant.first_name, pme_folder.company_name, companies.name) ASC')
|
|
693
|
+
.limit(25);
|
|
694
|
+
|
|
695
|
+
const sql = query.toSQL();
|
|
696
|
+
|
|
697
|
+
// Doit transformer correctement SANS double transformation
|
|
698
|
+
assert.ok(sql.sql.includes('applicants.last_name'), 'Should transform applicant to applicants');
|
|
699
|
+
assert.ok(sql.sql.includes('applicants.first_name'), 'Should transform applicant fields');
|
|
700
|
+
assert.ok(sql.sql.includes('pme_folders.company_name'), 'Should transform pme_folder to pme_folders');
|
|
701
|
+
assert.ok(sql.sql.includes('companies.name'), 'Should transform company to companies');
|
|
702
|
+
|
|
703
|
+
// NE DOIT PAS contenir de chemins avec 3 niveaux (double transformation)
|
|
704
|
+
assert.ok(!sql.sql.includes('.applicants.'), 'Should NOT have double-transformed paths like table.applicants.column');
|
|
705
|
+
assert.ok(!sql.sql.includes('.companies.'), 'Should NOT have double-transformed paths like table.companies.column');
|
|
706
|
+
assert.ok(!sql.sql.includes('.pme_folders.'), 'Should NOT have double-transformed paths like table.pme_folders.column');
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('transformation should be idempotent (_transformSinglePath)', () => {
|
|
710
|
+
// Test pour vérifier que transformer 2 fois donne le même résultat
|
|
711
|
+
const PaginatedOptimizedSql = require('@igojs/db').PaginatedOptimizedSql;
|
|
712
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
713
|
+
const sqlGenerator = new PaginatedOptimizedSql(query);
|
|
714
|
+
|
|
715
|
+
const original = 'applicant.last_name';
|
|
716
|
+
const transformed1 = sqlGenerator._transformSinglePath(original);
|
|
717
|
+
const transformed2 = sqlGenerator._transformSinglePath(transformed1);
|
|
718
|
+
|
|
719
|
+
assert.strictEqual(transformed1, 'applicants.last_name',
|
|
720
|
+
'First transformation should give correct result');
|
|
721
|
+
assert.strictEqual(transformed1, transformed2,
|
|
722
|
+
'Transforming twice should give the same result (idempotent)');
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('LEFT JOIN preserves rows with NULL', function() {
|
|
727
|
+
it('should use LEFT JOIN (not INNER JOIN) when sorting on joined table', () => {
|
|
728
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
729
|
+
query.query.verb = 'select_ids';
|
|
730
|
+
query
|
|
731
|
+
.where({ type: 'agp' })
|
|
732
|
+
.order('applicants.last_name ASC')
|
|
733
|
+
.limit(50);
|
|
734
|
+
|
|
735
|
+
const sql = query.toSQL();
|
|
736
|
+
|
|
737
|
+
// Doit utiliser LEFT JOIN, pas INNER JOIN
|
|
738
|
+
assert.ok(sql.sql.includes('LEFT JOIN'), 'SQL should use LEFT JOIN');
|
|
739
|
+
assert.ok(!sql.sql.includes('INNER JOIN'), 'SQL should not use INNER JOIN');
|
|
740
|
+
|
|
741
|
+
// Vérifier que c'est bien un LEFT JOIN sur applicants
|
|
742
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should LEFT JOIN applicants');
|
|
743
|
+
|
|
744
|
+
// Le ORDER BY doit être présent
|
|
745
|
+
assert.ok(sql.sql.includes('ORDER BY applicants.last_name ASC'), 'SQL should have ORDER BY');
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it('should use LEFT JOIN for nested table sorting', () => {
|
|
749
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
750
|
+
query.query.verb = 'select_ids';
|
|
751
|
+
query
|
|
752
|
+
.where({ type: 'pme' })
|
|
753
|
+
.order('pme_folder.company.name ASC')
|
|
754
|
+
.limit(50);
|
|
755
|
+
|
|
756
|
+
const sql = query.toSQL();
|
|
757
|
+
|
|
758
|
+
// Tous les JOIN doivent être des LEFT JOIN
|
|
759
|
+
assert.ok(sql.sql.includes('LEFT JOIN'), 'SQL should use LEFT JOIN');
|
|
760
|
+
assert.ok(!sql.sql.includes('INNER JOIN'), 'SQL should not use INNER JOIN');
|
|
761
|
+
|
|
762
|
+
// Vérifier les LEFT JOIN en cascade
|
|
763
|
+
const leftJoinCount = (sql.sql.match(/LEFT JOIN/g) || []).length;
|
|
764
|
+
assert.ok(leftJoinCount >= 2, 'SQL should have at least 2 LEFT JOINs for nested path');
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it('should use LEFT JOIN with SQL functions (COALESCE)', () => {
|
|
768
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
769
|
+
query.query.verb = 'select_ids';
|
|
770
|
+
query
|
|
771
|
+
.where({ type: 'agp' })
|
|
772
|
+
.join('applicant')
|
|
773
|
+
.order('COALESCE(`applicant`.`last_name`, "Unknown") ASC')
|
|
774
|
+
.limit(50);
|
|
775
|
+
|
|
776
|
+
const sql = query.toSQL();
|
|
777
|
+
|
|
778
|
+
// Doit utiliser LEFT JOIN même avec des fonctions SQL
|
|
779
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should LEFT JOIN applicants');
|
|
780
|
+
assert.ok(!sql.sql.includes('INNER JOIN'), 'SQL should not use INNER JOIN');
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it('should verify LEFT JOIN preserves all rows conceptually', () => {
|
|
784
|
+
// Ce test vérifie que le SQL généré utilise LEFT JOIN
|
|
785
|
+
// qui préserve toutes les lignes, même celles avec NULL
|
|
786
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
787
|
+
query.query.verb = 'select_ids';
|
|
788
|
+
query
|
|
789
|
+
.where({ type: 'agp' })
|
|
790
|
+
.order('applicants.last_name ASC')
|
|
791
|
+
.limit(50);
|
|
792
|
+
|
|
793
|
+
const sql = query.toSQL();
|
|
794
|
+
|
|
795
|
+
// Vérifier structure SQL complète
|
|
796
|
+
assert.ok(sql.sql.includes('SELECT'), 'SQL should have SELECT');
|
|
797
|
+
assert.ok(sql.sql.includes('FROM `folders`'), 'SQL should have FROM folders');
|
|
798
|
+
assert.ok(sql.sql.includes('LEFT JOIN `applicants`'), 'SQL should have LEFT JOIN applicants');
|
|
799
|
+
assert.ok(sql.sql.includes('WHERE `folders`.`type` = ?'), 'SQL should have WHERE clause');
|
|
800
|
+
assert.ok(sql.sql.includes('ORDER BY applicants.last_name ASC'), 'SQL should have ORDER BY');
|
|
801
|
+
assert.ok(sql.sql.includes('LIMIT'), 'SQL should have LIMIT');
|
|
802
|
+
|
|
803
|
+
// Avec LEFT JOIN, les folders sans applicant seront inclus avec last_name=NULL
|
|
804
|
+
// Ce comportement est correct et préserve toutes les lignes
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
//
|
|
809
|
+
describe('Advanced operators', function() {
|
|
810
|
+
it('should handle LIKE operator with % wildcard', () => {
|
|
811
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
812
|
+
query.query.verb = 'count';
|
|
813
|
+
query.where({
|
|
814
|
+
'applicant.last_name': 'Dupont%'
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
const sql = query.toSQL();
|
|
818
|
+
|
|
819
|
+
assert.ok(sql.sql.includes('LIKE'), 'Should generate LIKE for patterns with %');
|
|
820
|
+
assert.ok(sql.params.includes('Dupont%'), 'Should include the pattern in params');
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it('should handle BETWEEN operator', () => {
|
|
824
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
825
|
+
query.query.verb = 'count';
|
|
826
|
+
query.where({
|
|
827
|
+
created_at: { $between: ['2024-01-01', '2024-12-31'] }
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
const sql = query.toSQL();
|
|
831
|
+
|
|
832
|
+
assert.ok(sql.sql.includes('BETWEEN'), 'Should generate BETWEEN operator');
|
|
833
|
+
assert.ok(sql.params.includes('2024-01-01'), 'Should include start date in params');
|
|
834
|
+
assert.ok(sql.params.includes('2024-12-31'), 'Should include end date in params');
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it('should handle $gte operator', () => {
|
|
838
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
839
|
+
query.query.verb = 'count';
|
|
840
|
+
query.where({
|
|
841
|
+
created_at: { $gte: '2024-01-01' }
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
const sql = query.toSQL();
|
|
845
|
+
|
|
846
|
+
assert.ok(sql.sql.includes('>='), 'Should generate >= operator');
|
|
847
|
+
assert.ok(sql.params.includes('2024-01-01'), 'Should include date in params');
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it('should handle $lte operator', () => {
|
|
851
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
852
|
+
query.query.verb = 'count';
|
|
853
|
+
query.where({
|
|
854
|
+
created_at: { $lte: '2024-12-31' }
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const sql = query.toSQL();
|
|
858
|
+
|
|
859
|
+
assert.ok(sql.sql.includes('<='), 'Should generate <= operator');
|
|
860
|
+
assert.ok(sql.params.includes('2024-12-31'), 'Should include date in params');
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('should handle $like operator explicitly', () => {
|
|
864
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
865
|
+
query.query.verb = 'count';
|
|
866
|
+
query.where({
|
|
867
|
+
'applicant.last_name': { $like: 'Dup%' }
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const sql = query.toSQL();
|
|
871
|
+
|
|
872
|
+
assert.ok(sql.sql.includes('LIKE'), 'Should generate LIKE');
|
|
873
|
+
assert.ok(sql.params.includes('Dup%'), 'Should include pattern in params');
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it('should combine multiple advanced operators', () => {
|
|
877
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
878
|
+
query.query.verb = 'count';
|
|
879
|
+
query.where({
|
|
880
|
+
created_at: { $between: ['2024-01-01', '2024-12-31'] },
|
|
881
|
+
status: ['SUBMITTED', 'VALIDATED'],
|
|
882
|
+
'applicant.last_name': 'Dupont%',
|
|
883
|
+
'applicant.email': '%@example.com'
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
const sql = query.toSQL();
|
|
887
|
+
|
|
888
|
+
assert.ok(sql.sql.includes('LIKE'), 'Should generate LIKE');
|
|
889
|
+
assert.ok(sql.sql.includes('BETWEEN'), 'Should generate BETWEEN');
|
|
890
|
+
assert.ok(sql.sql.includes('IN'), 'Should generate IN');
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
//
|
|
895
|
+
describe('Edge cases', function() {
|
|
896
|
+
it('should handle null values', () => {
|
|
897
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
898
|
+
query.query.verb = 'count';
|
|
899
|
+
query.where({
|
|
900
|
+
status: 'SUBMITTED',
|
|
901
|
+
deleted_at: null
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
const sql = query.toSQL();
|
|
905
|
+
|
|
906
|
+
assert.ok(sql.sql.includes('IS NULL'), 'Should generate IS NULL for null values');
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it('should handle empty array', () => {
|
|
910
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
911
|
+
query.query.verb = 'count';
|
|
912
|
+
query.where({
|
|
913
|
+
status: []
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
const sql = query.toSQL();
|
|
917
|
+
|
|
918
|
+
// Un tableau vide devrait générer une condition qui est toujours fausse
|
|
919
|
+
// (Query.js le gère automatiquement)
|
|
920
|
+
assert.ok(sql.sql, 'Should generate SQL even with empty array');
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it('should handle empty where object', () => {
|
|
924
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
925
|
+
query.query.verb = 'count';
|
|
926
|
+
query.where({});
|
|
927
|
+
|
|
928
|
+
const sql = query.toSQL();
|
|
929
|
+
|
|
930
|
+
// Devrait générer un SQL valide sans conditions
|
|
931
|
+
assert.ok(sql.sql.includes('SELECT COUNT(0)'), 'Should generate COUNT SQL');
|
|
932
|
+
assert.ok(!sql.sql.includes('WHERE'), 'Should not have WHERE clause for empty conditions');
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
//
|
|
937
|
+
describe('Block tables (sub-tables) support', function() {
|
|
938
|
+
it('should detect ORDER BY on block column without prefix and add LEFT JOIN', () => {
|
|
939
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
940
|
+
query.query.verb = 'select_ids';
|
|
941
|
+
query
|
|
942
|
+
.where({ is_initial: true })
|
|
943
|
+
.order('studies_year DESC') // Colonne dans block_studies, sans préfixe
|
|
944
|
+
.limit(50);
|
|
945
|
+
|
|
946
|
+
const sql = query.toSQL();
|
|
947
|
+
|
|
948
|
+
// Doit ajouter un LEFT JOIN vers block_studies
|
|
949
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_studies`'), 'SQL should LEFT JOIN block_studies for sorting on block column');
|
|
950
|
+
assert.ok(sql.sql.includes('ON `block_studies`.`id` = `pme_folders_with_blocks`.`block_studies_id`'), 'SQL should have correct join condition');
|
|
951
|
+
|
|
952
|
+
// Doit transformer ORDER BY en block_studies.studies_year
|
|
953
|
+
assert.ok(sql.sql.includes('ORDER BY block_studies.studies_year DESC'), 'SQL should prefix column with table name');
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it('should handle multiple block columns in ORDER BY', () => {
|
|
957
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
958
|
+
query.query.verb = 'select_ids';
|
|
959
|
+
query
|
|
960
|
+
.where({ is_initial: true })
|
|
961
|
+
.order('studies_year DESC')
|
|
962
|
+
.order('bac_year ASC') // Autre colonne du même block
|
|
963
|
+
.limit(50);
|
|
964
|
+
|
|
965
|
+
const sql = query.toSQL();
|
|
966
|
+
|
|
967
|
+
// Un seul LEFT JOIN doit être ajouté (pas de duplication)
|
|
968
|
+
const leftJoinCount = (sql.sql.match(/LEFT JOIN `block_studies`/g) || []).length;
|
|
969
|
+
assert.strictEqual(leftJoinCount, 1, 'Should have exactly one LEFT JOIN to block_studies');
|
|
970
|
+
|
|
971
|
+
// Les deux colonnes doivent être préfixées
|
|
972
|
+
assert.ok(sql.sql.includes('ORDER BY block_studies.studies_year DESC'), 'Should prefix studies_year');
|
|
973
|
+
assert.ok(sql.sql.includes('block_studies.bac_year ASC'), 'Should prefix bac_year');
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
it('should handle ORDER BY on different blocks', () => {
|
|
977
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
978
|
+
query.query.verb = 'select_ids';
|
|
979
|
+
query
|
|
980
|
+
.where({ is_initial: true })
|
|
981
|
+
.order('studies_year DESC') // Colonne dans block_studies
|
|
982
|
+
.order('destination ASC') // Colonne dans block_travel_wishes
|
|
983
|
+
.limit(50);
|
|
984
|
+
|
|
985
|
+
const sql = query.toSQL();
|
|
986
|
+
|
|
987
|
+
// Doit ajouter deux LEFT JOIN (un pour chaque block)
|
|
988
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_studies`'), 'Should LEFT JOIN block_studies');
|
|
989
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_travel_wishes`'), 'Should LEFT JOIN block_travel_wishes');
|
|
990
|
+
|
|
991
|
+
// Les colonnes doivent être préfixées correctement
|
|
992
|
+
assert.ok(sql.sql.includes('ORDER BY block_studies.studies_year DESC'), 'Should prefix studies_year with block_studies');
|
|
993
|
+
assert.ok(sql.sql.includes('block_travel_wishes.destination ASC'), 'Should prefix destination with block_travel_wishes');
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it('should NOT add JOIN if column exists in main table', () => {
|
|
997
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
998
|
+
query.query.verb = 'select_ids';
|
|
999
|
+
query
|
|
1000
|
+
.where({ is_initial: true })
|
|
1001
|
+
.order('professional_activity DESC') // Colonne dans la table principale
|
|
1002
|
+
.limit(50);
|
|
1003
|
+
|
|
1004
|
+
const sql = query.toSQL();
|
|
1005
|
+
|
|
1006
|
+
// Ne doit PAS ajouter de LEFT JOIN
|
|
1007
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'Should not add LEFT JOIN for main table column');
|
|
1008
|
+
|
|
1009
|
+
// ORDER BY doit rester simple
|
|
1010
|
+
assert.ok(sql.sql.includes('ORDER BY professional_activity DESC'), 'Should keep simple ORDER BY for main table column');
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('should handle COUNT without JOIN for block ORDER BY', () => {
|
|
1014
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
1015
|
+
query.query.verb = 'count';
|
|
1016
|
+
query
|
|
1017
|
+
.where({ is_initial: true })
|
|
1018
|
+
.order('studies_year DESC') // ORDER BY est ignoré dans COUNT
|
|
1019
|
+
.limit(50);
|
|
1020
|
+
|
|
1021
|
+
const sql = query.toSQL();
|
|
1022
|
+
|
|
1023
|
+
// COUNT ne doit PAS avoir de LEFT JOIN
|
|
1024
|
+
assert.ok(!sql.sql.includes('LEFT JOIN'), 'COUNT should not have LEFT JOIN');
|
|
1025
|
+
|
|
1026
|
+
// COUNT ne doit PAS avoir de ORDER BY
|
|
1027
|
+
assert.ok(!sql.sql.includes('ORDER BY'), 'COUNT should not have ORDER BY');
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
it('should combine block ORDER BY with WHERE filters', () => {
|
|
1031
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
1032
|
+
query.query.verb = 'select_ids';
|
|
1033
|
+
query
|
|
1034
|
+
.where({
|
|
1035
|
+
is_initial: true,
|
|
1036
|
+
professional_activity: 'STUDENT'
|
|
1037
|
+
})
|
|
1038
|
+
.order('studies_year DESC')
|
|
1039
|
+
.limit(50);
|
|
1040
|
+
|
|
1041
|
+
const sql = query.toSQL();
|
|
1042
|
+
|
|
1043
|
+
// Doit avoir WHERE sur la table principale
|
|
1044
|
+
assert.ok(sql.sql.includes('WHERE'), 'Should have WHERE clause');
|
|
1045
|
+
assert.ok(sql.sql.includes('`pme_folders_with_blocks`.`is_initial`'), 'Should filter on is_initial');
|
|
1046
|
+
assert.ok(sql.sql.includes('`pme_folders_with_blocks`.`professional_activity`'), 'Should filter on professional_activity');
|
|
1047
|
+
|
|
1048
|
+
// Doit avoir LEFT JOIN pour le tri
|
|
1049
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_studies`'), 'Should LEFT JOIN for ORDER BY');
|
|
1050
|
+
|
|
1051
|
+
// Doit avoir ORDER BY
|
|
1052
|
+
assert.ok(sql.sql.includes('ORDER BY block_studies.studies_year DESC'), 'Should have ORDER BY on block column');
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it('should handle SQL functions with block columns', () => {
|
|
1056
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
1057
|
+
query.query.verb = 'select_ids';
|
|
1058
|
+
query
|
|
1059
|
+
.where({ is_initial: true })
|
|
1060
|
+
.order('COALESCE(`studies_year`, "N/A") DESC')
|
|
1061
|
+
.limit(50);
|
|
1062
|
+
|
|
1063
|
+
const sql = query.toSQL();
|
|
1064
|
+
|
|
1065
|
+
// Doit ajouter LEFT JOIN pour block_studies
|
|
1066
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_studies`'), 'Should LEFT JOIN block_studies');
|
|
1067
|
+
|
|
1068
|
+
// Doit transformer studies_year en block_studies.studies_year dans la fonction
|
|
1069
|
+
assert.ok(sql.sql.includes('COALESCE'), 'Should preserve COALESCE function');
|
|
1070
|
+
assert.ok(sql.sql.includes('block_studies.studies_year'), 'Should prefix column in function with table name');
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
it('should work with explicit join() on block associations', () => {
|
|
1074
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
1075
|
+
query.query.verb = 'select_ids';
|
|
1076
|
+
query
|
|
1077
|
+
.where({ is_initial: true })
|
|
1078
|
+
.join('studies') // Join explicite sur l'association
|
|
1079
|
+
.order('studies_year DESC')
|
|
1080
|
+
.limit(50);
|
|
1081
|
+
|
|
1082
|
+
const sql = query.toSQL();
|
|
1083
|
+
|
|
1084
|
+
// Même comportement : LEFT JOIN ajouté
|
|
1085
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_studies`'), 'Should LEFT JOIN block_studies');
|
|
1086
|
+
assert.ok(sql.sql.includes('ORDER BY block_studies.studies_year DESC'), 'Should prefix column with table name');
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it('should transform nested association paths in ORDER BY for phase FULL', () => {
|
|
1090
|
+
// Test pour vérifier que les chemins d'associations imbriqués sont transformés
|
|
1091
|
+
// dans la phase FULL (qui utilise Query standard, pas PaginatedOptimizedSql)
|
|
1092
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
1093
|
+
query.query.verb = 'select_ids';
|
|
1094
|
+
query
|
|
1095
|
+
.where({ type: 'pme' })
|
|
1096
|
+
.join('pme_folder.company')
|
|
1097
|
+
.order('pme_folder.company.name ASC') // Chemin d'association imbriqué
|
|
1098
|
+
.limit(50);
|
|
1099
|
+
|
|
1100
|
+
const sql = query.toSQL();
|
|
1101
|
+
|
|
1102
|
+
// Dans la phase IDS, le ORDER BY doit être transformé
|
|
1103
|
+
assert.ok(sql.sql.includes('ORDER BY companies.name ASC'), 'Should transform nested path to table name in IDS phase');
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
it('should transform nested block paths with dot notation (association.block.column)', () => {
|
|
1107
|
+
// Test pour vérifier que les chemins imbriqués vers des blocks sont transformés
|
|
1108
|
+
// Exemple : pme_folder.company.name doit être transformé en companies.name
|
|
1109
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(Folder));
|
|
1110
|
+
query.query.verb = 'select_ids';
|
|
1111
|
+
query
|
|
1112
|
+
.where({ type: 'pme' })
|
|
1113
|
+
.join('pme_folder.company')
|
|
1114
|
+
.order('pme_folder.company.name ASC')
|
|
1115
|
+
.limit(50);
|
|
1116
|
+
|
|
1117
|
+
const sql = query.toSQL();
|
|
1118
|
+
|
|
1119
|
+
// Le ORDER BY doit être transformé
|
|
1120
|
+
assert.ok(sql.sql.includes('ORDER BY companies.name ASC'),
|
|
1121
|
+
'Should transform pme_folder.company.name to companies.name in ORDER BY');
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
it('should handle nested path to block with 3 levels (folder.pme_folder.block_study.column)', () => {
|
|
1125
|
+
// Test pour la hiérarchie complète : Folder -> PMEFolder -> StudiesBlock
|
|
1126
|
+
// Ce test correspond au cas réel de l'utilisateur
|
|
1127
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(FolderWithNestedBlocks));
|
|
1128
|
+
query.query.verb = 'select_ids';
|
|
1129
|
+
query
|
|
1130
|
+
.where({ type: ['agp', 'avt'] })
|
|
1131
|
+
.order('pme_folder.block_study.bac_year DESC') // Chemin imbriqué vers un block
|
|
1132
|
+
.limit(50);
|
|
1133
|
+
|
|
1134
|
+
const sql = query.toSQL();
|
|
1135
|
+
|
|
1136
|
+
// Doit ajouter les LEFT JOIN nécessaires
|
|
1137
|
+
assert.ok(sql.sql.includes('LEFT JOIN `pme_folders`'), 'Should LEFT JOIN pme_folders');
|
|
1138
|
+
assert.ok(sql.sql.includes('LEFT JOIN `block_studies`'), 'Should LEFT JOIN block_studies');
|
|
1139
|
+
|
|
1140
|
+
// Doit avoir les bonnes conditions de jointure
|
|
1141
|
+
assert.ok(sql.sql.includes('ON `pme_folders`.`id` = `folders`.`pme_folder_id`'),
|
|
1142
|
+
'Should have correct join condition for pme_folders');
|
|
1143
|
+
assert.ok(sql.sql.includes('ON `block_studies`.`id` = `pme_folders`.`block_studies_id`'),
|
|
1144
|
+
'Should have correct join condition for block_studies');
|
|
1145
|
+
|
|
1146
|
+
// Le ORDER BY doit être transformé
|
|
1147
|
+
assert.ok(sql.sql.includes('ORDER BY block_studies.bac_year DESC'),
|
|
1148
|
+
'Should transform pme_folder.block_study.bac_year to block_studies.bac_year');
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
it('should transform ORDER BY correctly for FULL phase with block columns', () => {
|
|
1152
|
+
// Test pour vérifier que la transformation pour la phase FULL utilise les alias (noms d'associations)
|
|
1153
|
+
// au lieu des noms de tables
|
|
1154
|
+
const PaginatedOptimizedSql = require('@igojs/db').PaginatedOptimizedSql;
|
|
1155
|
+
const query = mockGetDb(new PaginatedOptimizedQuery(FolderWithNestedBlocks));
|
|
1156
|
+
|
|
1157
|
+
query
|
|
1158
|
+
.where({ type: ['agp', 'avt'] })
|
|
1159
|
+
.order('pme_folder.block_study.bac_year') // Chemin imbriqué
|
|
1160
|
+
.order('created_at DESC') // Colonne de la table principale
|
|
1161
|
+
.limit(50);
|
|
1162
|
+
|
|
1163
|
+
const sqlGenerator = new PaginatedOptimizedSql(query);
|
|
1164
|
+
|
|
1165
|
+
// Test de transformation pour phase IDS (noms de tables)
|
|
1166
|
+
const transformedForIDS = sqlGenerator._transformOrderClause('pme_folder.block_study.bac_year');
|
|
1167
|
+
assert.strictEqual(transformedForIDS, 'block_studies.bac_year',
|
|
1168
|
+
'IDS phase should use table name (block_studies)');
|
|
1169
|
+
|
|
1170
|
+
// Test de transformation pour phase FULL (noms d'associations = alias)
|
|
1171
|
+
const transformedForFULL = sqlGenerator._transformOrderClauseForFullQuery('pme_folder.block_study.bac_year');
|
|
1172
|
+
assert.strictEqual(transformedForFULL, 'block_study.bac_year',
|
|
1173
|
+
'FULL phase should use association name as alias (block_study)');
|
|
1174
|
+
|
|
1175
|
+
// Test avec colonne simple de block (uniquement pour modèles avec associations directes)
|
|
1176
|
+
const queryWithBlocks = mockGetDb(new PaginatedOptimizedQuery(PMEFolderWithBlocks));
|
|
1177
|
+
const sqlGeneratorWithBlocks = new PaginatedOptimizedSql(queryWithBlocks);
|
|
1178
|
+
const transformedSimpleForFULL = sqlGeneratorWithBlocks._transformOrderClauseForFullQuery('studies_year');
|
|
1179
|
+
assert.strictEqual(transformedSimpleForFULL, 'studies.studies_year',
|
|
1180
|
+
'FULL phase should find block association for simple column name in direct associations');
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
});
|