@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,902 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const Query = require('./Query');
|
|
3
|
+
const context = require('./context');
|
|
4
|
+
const PaginatedOptimizedSql = require('./PaginatedOptimizedSql');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PaginatedOptimizedQuery - Implémentation du pattern COUNT/IDS/FULL pour optimiser les requêtes avec jointures
|
|
8
|
+
*
|
|
9
|
+
* Ce module remplace la logique traditionnelle des LEFT JOIN par un pattern en 3 phases :
|
|
10
|
+
*
|
|
11
|
+
* 1. COUNT avec EXISTS : Compte les lignes sans faire de jointures, utilise EXISTS pour les filtres sur tables jointes
|
|
12
|
+
* 2. SELECT IDS : Sélectionne uniquement les IDs de la table principale avec filtres, tris et pagination
|
|
13
|
+
* 3. SELECT FULL : Récupère les données complètes avec LEFT JOIN uniquement pour les IDs trouvés
|
|
14
|
+
*
|
|
15
|
+
* Ce pattern améliore drastiquement les performances sur les grosses tables avec de nombreuses jointures.
|
|
16
|
+
*
|
|
17
|
+
* Exemple d'utilisation :
|
|
18
|
+
*
|
|
19
|
+
* const query = Folder.paginatedOptimized()
|
|
20
|
+
* .where({ type: ['agp', 'avt'] })
|
|
21
|
+
* .filterJoin('applicant', { last_name: 'Dupont%' }) // sera transformé en EXISTS
|
|
22
|
+
* .join('pme_folder') // sera un LEFT JOIN dans la phase FULL
|
|
23
|
+
* .order('folders.created_at DESC')
|
|
24
|
+
* .page(1, 50);
|
|
25
|
+
*
|
|
26
|
+
* const result = await query.execute(); // { pagination: {...}, rows: [...] }
|
|
27
|
+
*/
|
|
28
|
+
module.exports = class PaginatedOptimizedQuery extends Query {
|
|
29
|
+
|
|
30
|
+
constructor(modelClass, verb = 'select') {
|
|
31
|
+
super(modelClass, verb);
|
|
32
|
+
|
|
33
|
+
// Nouvelle propriété pour distinguer les joins de filtrage (→ EXISTS) des joins de données (→ LEFT JOIN)
|
|
34
|
+
this.query.filterJoins = [];
|
|
35
|
+
|
|
36
|
+
// Flag pour activer le mode optimisé
|
|
37
|
+
this.query.optimized = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Override de join() pour supporter la notation pointée
|
|
42
|
+
*
|
|
43
|
+
* Permet d'utiliser la notation pointée pour les joins imbriqués :
|
|
44
|
+
* - .join('applicant') → join simple
|
|
45
|
+
* - .join('pme_folder.company.country') → join imbriqué
|
|
46
|
+
* - .join(['applicant', 'pme_folder.company']) → plusieurs joins
|
|
47
|
+
*
|
|
48
|
+
* @param {string|array|object} associations - Associations à joindre
|
|
49
|
+
* @returns {PaginatedOptimizedQuery} this (pour chaînage)
|
|
50
|
+
*/
|
|
51
|
+
join(associations) {
|
|
52
|
+
// Si c'est un objet, utiliser la syntaxe standard (Query parent)
|
|
53
|
+
if (_.isObject(associations) && !_.isArray(associations)) {
|
|
54
|
+
return super.join(associations);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Si c'est une string ou un tableau, détecter la notation pointée
|
|
58
|
+
const assocs = _.isArray(associations) ? associations : [associations];
|
|
59
|
+
const transformed = [];
|
|
60
|
+
|
|
61
|
+
_.forEach(assocs, (assoc) => {
|
|
62
|
+
if (_.isString(assoc) && assoc.includes('.')) {
|
|
63
|
+
// Notation pointée détectée : transformer en structure imbriquée
|
|
64
|
+
transformed.push(this._transformDottedJoinPath(assoc));
|
|
65
|
+
} else {
|
|
66
|
+
// Pas de notation pointée : garder tel quel
|
|
67
|
+
transformed.push(assoc);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Si on a transformé des chemins, reconstruire la structure
|
|
72
|
+
if (transformed.length > 0) {
|
|
73
|
+
return super.join(this._mergeJoinStructures(transformed));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return super.join(associations);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Transforme un chemin pointé en structure imbriquée
|
|
81
|
+
*
|
|
82
|
+
* @param {string} path - Chemin pointé (ex: 'pme_folder.company.country')
|
|
83
|
+
* @returns {object} Structure imbriquée
|
|
84
|
+
*
|
|
85
|
+
* Exemple :
|
|
86
|
+
* 'pme_folder.company.country' → { pme_folder: { company: ['country'] } }
|
|
87
|
+
*/
|
|
88
|
+
_transformDottedJoinPath(path) {
|
|
89
|
+
const parts = path.split('.');
|
|
90
|
+
|
|
91
|
+
// Si un seul niveau, retourner tel quel
|
|
92
|
+
if (parts.length === 1) {
|
|
93
|
+
return parts[0];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Construire la structure imbriquée de droite à gauche
|
|
97
|
+
let result = [parts[parts.length - 1]];
|
|
98
|
+
|
|
99
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
100
|
+
result = { [parts[i]]: result };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Fusionne plusieurs structures de join
|
|
108
|
+
*
|
|
109
|
+
* @param {array} structures - Tableau de structures de join
|
|
110
|
+
* @returns {array|object} Structure fusionnée
|
|
111
|
+
*/
|
|
112
|
+
_mergeJoinStructures(structures) {
|
|
113
|
+
// Séparer les strings simples des objets
|
|
114
|
+
const simples = [];
|
|
115
|
+
const nested = {};
|
|
116
|
+
|
|
117
|
+
_.forEach(structures, (struct) => {
|
|
118
|
+
if (_.isString(struct)) {
|
|
119
|
+
simples.push(struct);
|
|
120
|
+
} else if (_.isObject(struct)) {
|
|
121
|
+
// Fusionner les objets imbriqués
|
|
122
|
+
_.merge(nested, struct);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Si on a des objets imbriqués et des simples
|
|
127
|
+
if (!_.isEmpty(nested) && simples.length > 0) {
|
|
128
|
+
return [...simples, nested];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Si seulement des simples
|
|
132
|
+
if (simples.length > 0 && _.isEmpty(nested)) {
|
|
133
|
+
return simples;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Si seulement des imbriqués
|
|
137
|
+
if (_.isEmpty(simples) && !_.isEmpty(nested)) {
|
|
138
|
+
return nested;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return structures;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Override de where() pour détecter automatiquement les conditions sur tables jointes
|
|
146
|
+
*
|
|
147
|
+
* Cette méthode analyse les conditions pour détecter les chemins imbriqués (ex: 'applicant.last_name')
|
|
148
|
+
* et les convertit automatiquement en filterJoins optimisés avec EXISTS.
|
|
149
|
+
*
|
|
150
|
+
* Syntaxe supportée :
|
|
151
|
+
* - Table principale : { status: 'ACTIVE' }
|
|
152
|
+
* - Table jointe (1 niveau) : { 'applicant.last_name': 'Dupont' }
|
|
153
|
+
* - Tables imbriquées : { 'pme_folder.company.country.code': 'FR' }
|
|
154
|
+
*
|
|
155
|
+
* Opérateurs supportés :
|
|
156
|
+
* - Égalité : { 'applicant.email': 'test@test.com' }
|
|
157
|
+
* - LIKE : { 'applicant.last_name': { $like: 'Dup%' } }
|
|
158
|
+
* - IN : { 'applicant.status': ['ACTIVE', 'PENDING'] }
|
|
159
|
+
* - BETWEEN : { created_at: { $between: ['2024-01-01', '2024-12-31'] } }
|
|
160
|
+
* - Comparaisons : { amount: { $gte: 100 } }
|
|
161
|
+
*
|
|
162
|
+
* Opérateurs logiques :
|
|
163
|
+
* - $and : { $and: [{ status: 'ACTIVE' }, { 'applicant.last_name': 'Dupont' }] }
|
|
164
|
+
* - $or : { $or: [{ status: 'ACTIVE' }, { status: 'PENDING' }] }
|
|
165
|
+
*
|
|
166
|
+
* @param {object|string} where - Conditions de filtrage
|
|
167
|
+
* @param {any} params - Paramètres (pour compatibilité avec Query parent)
|
|
168
|
+
* @returns {PaginatedOptimizedQuery} this (pour chaînage)
|
|
169
|
+
*/
|
|
170
|
+
where(where, params) {
|
|
171
|
+
// Appeler le parent pour gérer les cas simples (string avec params)
|
|
172
|
+
if (params !== undefined) {
|
|
173
|
+
return super.where(where, params);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Cas spécial : $or avec des conditions sur tables jointes
|
|
177
|
+
// Ces conditions doivent être transformées en filterJoin avec OR
|
|
178
|
+
if (where.$or && _.isArray(where.$or)) {
|
|
179
|
+
this._handleOrWithJoinedTables(where.$or);
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Analyser et transformer les conditions
|
|
184
|
+
const { mainConditions, joinConditions } = this._parseWhereConditions(where);
|
|
185
|
+
|
|
186
|
+
// Ajouter les conditions sur la table principale
|
|
187
|
+
// mainConditions est maintenant un tableau de conditions
|
|
188
|
+
if (mainConditions && mainConditions.length > 0) {
|
|
189
|
+
_.forEach(mainConditions, (cond) => {
|
|
190
|
+
// Cas 1 : c'est un array d'objets (vient d'un $or)
|
|
191
|
+
// Il faut le transformer en SQL avec OR car Query ne gère pas $or
|
|
192
|
+
if (_.isArray(cond) && cond.length > 0) {
|
|
193
|
+
// Construire manuellement la clause OR
|
|
194
|
+
const orClauses = [];
|
|
195
|
+
const orParams = [];
|
|
196
|
+
|
|
197
|
+
_.forEach(cond, (orCond) => {
|
|
198
|
+
if (_.isObject(orCond)) {
|
|
199
|
+
const condClauses = [];
|
|
200
|
+
_.forOwn(orCond, (value, key) => {
|
|
201
|
+
const columnRef = `\`${this.query.table}\`.\`${key}\``;
|
|
202
|
+
|
|
203
|
+
if (value === null || value === undefined) {
|
|
204
|
+
condClauses.push(`${columnRef} IS NULL`);
|
|
205
|
+
} else if (_.isArray(value)) {
|
|
206
|
+
if (value.length === 0) {
|
|
207
|
+
condClauses.push('FALSE');
|
|
208
|
+
} else {
|
|
209
|
+
condClauses.push(`${columnRef} IN ($?)`);
|
|
210
|
+
orParams.push(value);
|
|
211
|
+
}
|
|
212
|
+
} else if (_.isString(value) && value.includes('%')) {
|
|
213
|
+
// Pattern LIKE détecté
|
|
214
|
+
condClauses.push(`${columnRef} LIKE $?`);
|
|
215
|
+
orParams.push(value);
|
|
216
|
+
} else {
|
|
217
|
+
condClauses.push(`${columnRef} = $?`);
|
|
218
|
+
orParams.push(value);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (condClauses.length > 0) {
|
|
223
|
+
orClauses.push(condClauses.length > 1 ? `(${condClauses.join(' AND ')})` : condClauses[0]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (orClauses.length > 0) {
|
|
229
|
+
const orSQL = `(${orClauses.join(' OR ')})`;
|
|
230
|
+
super.where(orSQL, orParams);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Cas 2 : c'est un objet avec des valeurs SQL transformées
|
|
234
|
+
else if (_.isObject(cond) && !_.isArray(cond)) {
|
|
235
|
+
const normalizedCond = {};
|
|
236
|
+
_.forOwn(cond, (value, key) => {
|
|
237
|
+
// Si la valeur est un tableau [sql, params] avec $? dans le SQL
|
|
238
|
+
if (_.isArray(value) && value.length === 2 && _.isString(value[0]) && value[0].includes('$?')) {
|
|
239
|
+
super.where(value[0], value[1]);
|
|
240
|
+
} else {
|
|
241
|
+
normalizedCond[key] = value;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Ajouter les conditions normales si présentes
|
|
246
|
+
if (!_.isEmpty(normalizedCond)) {
|
|
247
|
+
super.where(normalizedCond);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Cas 3 : autre format (ne devrait pas arriver)
|
|
251
|
+
else {
|
|
252
|
+
super.where(cond);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Générer automatiquement les filterJoins pour les conditions sur tables jointes
|
|
258
|
+
if (joinConditions && Object.keys(joinConditions).length > 0) {
|
|
259
|
+
this._buildFilterJoinsFromPaths(joinConditions);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* _addSimpleFilterJoin - Ajoute un filtre sur une table jointe (méthode interne)
|
|
267
|
+
*
|
|
268
|
+
* Cette méthode est utilisée en interne par where() pour ajouter des filtres EXISTS.
|
|
269
|
+
* Les utilisateurs ne doivent pas l'appeler directement.
|
|
270
|
+
*
|
|
271
|
+
* @private
|
|
272
|
+
* @param {string} associationName - Nom de l'association (ex: 'applicant', 'pme_folder')
|
|
273
|
+
* @param {object} conditions - Conditions de filtrage
|
|
274
|
+
* @param {string} operator - Opérateur SQL ('AND' ou 'OR')
|
|
275
|
+
*/
|
|
276
|
+
_addSimpleFilterJoin(associationName, conditions, operator = 'AND') {
|
|
277
|
+
const association = this._findAssociation(associationName, this.schema);
|
|
278
|
+
|
|
279
|
+
this.query.filterJoins.push({
|
|
280
|
+
association,
|
|
281
|
+
conditions,
|
|
282
|
+
operator,
|
|
283
|
+
src_schema: this.schema
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* _addNestedFilterJoin - Ajoute des filtres imbriqués (méthode interne)
|
|
289
|
+
*
|
|
290
|
+
* Cette méthode est utilisée en interne par where() pour ajouter des filtres EXISTS imbriqués.
|
|
291
|
+
* Les utilisateurs ne doivent pas l'appeler directement.
|
|
292
|
+
*
|
|
293
|
+
* @private
|
|
294
|
+
* @param {object} nestedConfig - Configuration des filtres imbriqués
|
|
295
|
+
*/
|
|
296
|
+
_addNestedFilterJoin(nestedConfig) {
|
|
297
|
+
// Construire la hiérarchie complète d'associations
|
|
298
|
+
const buildHierarchy = (config, currentSchema, parentPath = null) => {
|
|
299
|
+
const results = [];
|
|
300
|
+
|
|
301
|
+
_.each(config, (value, associationName) => {
|
|
302
|
+
const association = this._findAssociation(associationName, currentSchema);
|
|
303
|
+
const [, , AssociatedModel] = association;
|
|
304
|
+
|
|
305
|
+
// Créer un noeud de hiérarchie
|
|
306
|
+
const node = {
|
|
307
|
+
association,
|
|
308
|
+
conditions: value.conditions || null,
|
|
309
|
+
operator: value.operator || 'AND',
|
|
310
|
+
src_schema: currentSchema,
|
|
311
|
+
parent: parentPath,
|
|
312
|
+
children: []
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Traiter récursivement les enfants
|
|
316
|
+
if (value.nested) {
|
|
317
|
+
node.children = buildHierarchy(value.nested, AssociatedModel.schema, node);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
results.push(node);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return results;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Construire l'arbre complet
|
|
327
|
+
const hierarchy = buildHierarchy(nestedConfig, this.schema);
|
|
328
|
+
|
|
329
|
+
// Stocker dans filterJoins avec une structure hiérarchique
|
|
330
|
+
this.query.filterJoins.push({
|
|
331
|
+
type: 'nested',
|
|
332
|
+
hierarchy: hierarchy
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Phase 1 : COUNT optimisé avec EXISTS
|
|
338
|
+
*
|
|
339
|
+
* Génère une requête COUNT(0) sans LEFT JOIN. Les filtres sur tables jointes
|
|
340
|
+
* sont convertis en sous-requêtes EXISTS.
|
|
341
|
+
*
|
|
342
|
+
* @returns {Promise<number>} Le nombre total de lignes correspondant aux filtres
|
|
343
|
+
*/
|
|
344
|
+
async count() {
|
|
345
|
+
const countQuery = new PaginatedOptimizedQuery(this.modelClass);
|
|
346
|
+
countQuery.query = _.cloneDeep(this.query);
|
|
347
|
+
countQuery.query.verb = 'count';
|
|
348
|
+
countQuery.query.limit = 1;
|
|
349
|
+
delete countQuery.query.page;
|
|
350
|
+
delete countQuery.query.nb;
|
|
351
|
+
delete countQuery.query.order; // Pas besoin de ORDER BY pour un COUNT
|
|
352
|
+
|
|
353
|
+
const rows = await countQuery.runQuery();
|
|
354
|
+
const count = rows && rows[0] && Number(rows[0].count) || 0;
|
|
355
|
+
return count;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Phase 2 : SELECT IDS avec filtres et pagination
|
|
360
|
+
*
|
|
361
|
+
* Sélectionne uniquement les IDs de la table principale en appliquant :
|
|
362
|
+
* - Tous les filtres (WHERE + EXISTS pour les filterJoins)
|
|
363
|
+
* - Les tris (ORDER BY)
|
|
364
|
+
* - La pagination (LIMIT/OFFSET)
|
|
365
|
+
*
|
|
366
|
+
* @returns {Promise<Array<number>>} Liste des IDs trouvés
|
|
367
|
+
*/
|
|
368
|
+
async selectIds() {
|
|
369
|
+
const idsQuery = new PaginatedOptimizedQuery(this.modelClass);
|
|
370
|
+
idsQuery.query = _.cloneDeep(this.query);
|
|
371
|
+
idsQuery.query.verb = 'select_ids';
|
|
372
|
+
|
|
373
|
+
// Ne sélectionner que l'ID (ou clés primaires)
|
|
374
|
+
const primaryKeys = this.schema.primary || ['id'];
|
|
375
|
+
idsQuery.query.select = primaryKeys.map(key => `\`${this.schema.table}\`.\`${key}\``).join(', ');
|
|
376
|
+
|
|
377
|
+
const rows = await idsQuery.runQuery();
|
|
378
|
+
|
|
379
|
+
// Retourner un tableau d'IDs (ou objets de clés composites)
|
|
380
|
+
if (primaryKeys.length === 1) {
|
|
381
|
+
return rows.map(row => row[primaryKeys[0]]);
|
|
382
|
+
}
|
|
383
|
+
return rows.map(row => _.pick(row, primaryKeys));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Phase 3 : SELECT FULL avec LEFT JOIN sur les IDs trouvés
|
|
388
|
+
*
|
|
389
|
+
* Récupère les données complètes avec les LEFT JOIN uniquement pour les IDs
|
|
390
|
+
* retournés par la phase 2. Cela limite drastiquement le nombre de lignes à joindre.
|
|
391
|
+
*
|
|
392
|
+
* @param {Array<number|object>} ids - Liste des IDs à récupérer
|
|
393
|
+
* @returns {Promise<Array<Object>>} Données complètes avec associations
|
|
394
|
+
*/
|
|
395
|
+
async selectFull(ids) {
|
|
396
|
+
if (!ids || ids.length === 0) {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const fullQuery = new Query(this.modelClass); // Utiliser Query standard pour le SELECT final
|
|
401
|
+
fullQuery.query = _.cloneDeep(this.query);
|
|
402
|
+
fullQuery.query.verb = 'select';
|
|
403
|
+
|
|
404
|
+
// Conserver uniquement les "vrais" joins (pas les filterJoins)
|
|
405
|
+
// Les filterJoins ne sont utilisés que pour COUNT et IDS
|
|
406
|
+
fullQuery.query.filterJoins = [];
|
|
407
|
+
|
|
408
|
+
// Remplacer les filtres par WHERE id IN (...)
|
|
409
|
+
const primaryKeys = this.schema.primary || ['id'];
|
|
410
|
+
if (primaryKeys.length === 1) {
|
|
411
|
+
fullQuery.query.where = [{ [primaryKeys[0]]: ids }];
|
|
412
|
+
} else {
|
|
413
|
+
// Clés composites : générer des conditions OR
|
|
414
|
+
const compositeConditions = ids.map(idObj => idObj);
|
|
415
|
+
fullQuery.query.where = compositeConditions;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
fullQuery.query.whereNot = [];
|
|
419
|
+
|
|
420
|
+
// Supprimer la pagination (déjà appliquée dans selectIds)
|
|
421
|
+
delete fullQuery.query.limit;
|
|
422
|
+
delete fullQuery.query.offset;
|
|
423
|
+
delete fullQuery.query.page;
|
|
424
|
+
delete fullQuery.query.nb;
|
|
425
|
+
|
|
426
|
+
// Conserver l'ORDER BY pour maintenir l'ordre
|
|
427
|
+
// (Important car IN (...) ne garantit pas l'ordre)
|
|
428
|
+
// Transformer les chemins d'associations en noms d'associations (alias) pour la Query standard
|
|
429
|
+
if (fullQuery.query.order && fullQuery.query.order.length > 0) {
|
|
430
|
+
const sqlGenerator = new PaginatedOptimizedSql(this); // Utiliser 'this' (PaginatedOptimizedQuery) au lieu de 'fullQuery'
|
|
431
|
+
fullQuery.query.order = fullQuery.query.order.map(orderClause => {
|
|
432
|
+
return sqlGenerator._transformOrderClauseForFullQuery(orderClause);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const rows = await fullQuery.execute();
|
|
437
|
+
return rows;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Orchestrateur principal : exécute les 3 phases
|
|
442
|
+
*
|
|
443
|
+
* Cette méthode est appelée automatiquement par execute() quand le mode optimisé est activé.
|
|
444
|
+
*
|
|
445
|
+
* @returns {Promise<Object|Array>} Résultat de la requête (avec ou sans pagination)
|
|
446
|
+
*/
|
|
447
|
+
async executeOptimized() {
|
|
448
|
+
const { query } = this;
|
|
449
|
+
|
|
450
|
+
// Si pas de pagination demandée, on peut simplifier
|
|
451
|
+
if (!query.page) {
|
|
452
|
+
// Phase 1 : COUNT (optionnel si pas de pagination)
|
|
453
|
+
// On peut le sauter pour gagner du temps
|
|
454
|
+
|
|
455
|
+
// Phase 2 : SELECT IDS
|
|
456
|
+
const ids = await this.selectIds();
|
|
457
|
+
|
|
458
|
+
// Phase 3 : SELECT FULL
|
|
459
|
+
const rows = await this.selectFull(ids);
|
|
460
|
+
|
|
461
|
+
if (query.limit === 1) {
|
|
462
|
+
return rows[0] || null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return rows;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Avec pagination : les 3 phases complètes
|
|
469
|
+
|
|
470
|
+
// Phase 1 : COUNT
|
|
471
|
+
const count = await this.count();
|
|
472
|
+
|
|
473
|
+
// Calculer la pagination
|
|
474
|
+
const nb_pages = Math.ceil(count / query.nb);
|
|
475
|
+
query.page = Math.min(query.page, nb_pages);
|
|
476
|
+
query.page = Math.max(query.page, 1);
|
|
477
|
+
query.offset = (query.page - 1) * query.nb;
|
|
478
|
+
query.limit = query.nb;
|
|
479
|
+
|
|
480
|
+
// Phase 2 : SELECT IDS
|
|
481
|
+
const ids = await this.selectIds();
|
|
482
|
+
|
|
483
|
+
// Phase 3 : SELECT FULL
|
|
484
|
+
const rows = await this.selectFull(ids);
|
|
485
|
+
|
|
486
|
+
// Construire l'objet pagination
|
|
487
|
+
const page = query.page;
|
|
488
|
+
const links = [];
|
|
489
|
+
const start = Math.max(1, page - 5);
|
|
490
|
+
for (let i = 0; i < 10; i++) {
|
|
491
|
+
const p = start + i;
|
|
492
|
+
if (p <= nb_pages) {
|
|
493
|
+
links.push({ page: p, current: page === p });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const pagination = {
|
|
498
|
+
page: query.page,
|
|
499
|
+
nb: query.nb,
|
|
500
|
+
previous: page > 1 ? page - 1 : null,
|
|
501
|
+
next: page < nb_pages ? page + 1 : null,
|
|
502
|
+
start: query.offset + 1,
|
|
503
|
+
end: query.offset + Math.min(query.nb, count - query.offset),
|
|
504
|
+
nb_pages,
|
|
505
|
+
count,
|
|
506
|
+
links,
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
return { pagination, rows };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Override de execute() pour utiliser executeOptimized() en mode optimisé
|
|
514
|
+
*/
|
|
515
|
+
async execute() {
|
|
516
|
+
if (this.query.optimized && this.query.verb === 'select') {
|
|
517
|
+
return await this.executeOptimized();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Fallback vers l'implémentation standard pour les autres verbes (update, delete, etc.)
|
|
521
|
+
return await super.execute();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Gère les conditions $or qui contiennent des tables jointes
|
|
526
|
+
*
|
|
527
|
+
* Crée un filterJoin spécial de type 'or_group' qui combine toutes les
|
|
528
|
+
* conditions avec OR au lieu de AND.
|
|
529
|
+
*
|
|
530
|
+
* @param {Array} orConditions - Array de conditions du $or
|
|
531
|
+
*/
|
|
532
|
+
_handleOrWithJoinedTables(orConditions) {
|
|
533
|
+
// Séparer les conditions principales et jointes
|
|
534
|
+
const mainTableConditions = [];
|
|
535
|
+
const joinedTableConditions = [];
|
|
536
|
+
|
|
537
|
+
_.forEach(orConditions, (cond) => {
|
|
538
|
+
const hasJoinedTable = _.some(_.keys(cond), key => key.includes('.'));
|
|
539
|
+
|
|
540
|
+
if (hasJoinedTable) {
|
|
541
|
+
// Condition sur table jointe
|
|
542
|
+
joinedTableConditions.push(cond);
|
|
543
|
+
} else {
|
|
544
|
+
// Condition sur table principale
|
|
545
|
+
mainTableConditions.push(cond);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Traiter les conditions sur table principale avec OR
|
|
550
|
+
if (mainTableConditions.length > 0) {
|
|
551
|
+
const orClauses = [];
|
|
552
|
+
const orParams = [];
|
|
553
|
+
|
|
554
|
+
_.forEach(mainTableConditions, (cond) => {
|
|
555
|
+
_.forOwn(cond, (value, key) => {
|
|
556
|
+
const columnRef = `\`${this.query.table}\`.\`${key}\``;
|
|
557
|
+
|
|
558
|
+
if (value === null || value === undefined) {
|
|
559
|
+
orClauses.push(`${columnRef} IS NULL`);
|
|
560
|
+
} else if (_.isArray(value)) {
|
|
561
|
+
if (value.length === 0) {
|
|
562
|
+
orClauses.push('FALSE');
|
|
563
|
+
} else {
|
|
564
|
+
orClauses.push(`${columnRef} IN ($?)`);
|
|
565
|
+
orParams.push(value);
|
|
566
|
+
}
|
|
567
|
+
} else if (_.isString(value) && value.includes('%')) {
|
|
568
|
+
orClauses.push(`${columnRef} LIKE $?`);
|
|
569
|
+
orParams.push(value);
|
|
570
|
+
} else {
|
|
571
|
+
orClauses.push(`${columnRef} = $?`);
|
|
572
|
+
orParams.push(value);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
if (orClauses.length > 0) {
|
|
578
|
+
const orSQL = `(${orClauses.join(' OR ')})`;
|
|
579
|
+
super.where(orSQL, orParams);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Traiter les conditions sur tables jointes
|
|
584
|
+
// Créer un filterJoin spécial qui sera traité comme un groupe OR
|
|
585
|
+
if (joinedTableConditions.length > 0) {
|
|
586
|
+
this.query.filterJoins.push({
|
|
587
|
+
type: 'or_group',
|
|
588
|
+
conditions: joinedTableConditions
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Parse les conditions where pour séparer :
|
|
595
|
+
* - Les conditions sur la table principale
|
|
596
|
+
* - Les conditions sur les tables jointes (chemins avec points)
|
|
597
|
+
*
|
|
598
|
+
* @param {object} conditions - Objet de conditions
|
|
599
|
+
* @returns {object} { mainConditions, joinConditions }
|
|
600
|
+
*
|
|
601
|
+
* Exemple :
|
|
602
|
+
* Input: {
|
|
603
|
+
* $and: [
|
|
604
|
+
* { status: 'ACTIVE' },
|
|
605
|
+
* { 'applicant.last_name': 'Dupont' },
|
|
606
|
+
* { 'pme_folder.company.country.code': 'FR' }
|
|
607
|
+
* ]
|
|
608
|
+
* }
|
|
609
|
+
*
|
|
610
|
+
* Output: {
|
|
611
|
+
* mainConditions: [{ status: 'ACTIVE' }], // Tableau de conditions
|
|
612
|
+
* joinConditions: {
|
|
613
|
+
* 'applicant.last_name': { value: 'Dupont', path: ['applicant'], column: 'last_name' },
|
|
614
|
+
* 'pme_folder.company.country.code': { value: 'FR', path: ['pme_folder', 'company', 'country'], column: 'code' }
|
|
615
|
+
* }
|
|
616
|
+
* }
|
|
617
|
+
*/
|
|
618
|
+
_parseWhereConditions(conditions) {
|
|
619
|
+
const mainConditions = [];
|
|
620
|
+
const joinConditions = {};
|
|
621
|
+
|
|
622
|
+
// Gérer les opérateurs logiques $and et $or
|
|
623
|
+
if (conditions.$and) {
|
|
624
|
+
// $and : aplatir les conditions sur la table principale
|
|
625
|
+
_.forEach(conditions.$and, (cond) => {
|
|
626
|
+
const parsed = this._parseConditionObject(cond);
|
|
627
|
+
if (parsed.main && !_.isEmpty(parsed.main)) {
|
|
628
|
+
mainConditions.push(parsed.main);
|
|
629
|
+
}
|
|
630
|
+
Object.assign(joinConditions, parsed.join);
|
|
631
|
+
});
|
|
632
|
+
} else if (conditions.$or) {
|
|
633
|
+
// $or : gérer les conditions imbriquées
|
|
634
|
+
// Note: Si toutes les conditions du $or sont sur la table principale,
|
|
635
|
+
// on peut les regrouper. Sinon, on doit les traiter séparément.
|
|
636
|
+
const orMainConditions = [];
|
|
637
|
+
_.forEach(conditions.$or, (cond) => {
|
|
638
|
+
const parsed = this._parseConditionObject(cond);
|
|
639
|
+
if (parsed.main && !_.isEmpty(parsed.main)) {
|
|
640
|
+
orMainConditions.push(parsed.main);
|
|
641
|
+
}
|
|
642
|
+
Object.assign(joinConditions, parsed.join);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Si on a des conditions sur la table principale dans le $or
|
|
646
|
+
if (orMainConditions.length > 0) {
|
|
647
|
+
mainConditions.push(orMainConditions);
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
// Cas simple : objet de conditions
|
|
651
|
+
const parsed = this._parseConditionObject(conditions);
|
|
652
|
+
if (parsed.main && !_.isEmpty(parsed.main)) {
|
|
653
|
+
mainConditions.push(parsed.main);
|
|
654
|
+
}
|
|
655
|
+
Object.assign(joinConditions, parsed.join);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return { mainConditions, joinConditions };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Parse un objet de conditions pour séparer les colonnes principales et jointes
|
|
663
|
+
*
|
|
664
|
+
* @param {object} conditionObj - Objet de conditions { column: value, ... }
|
|
665
|
+
* @returns {object} { main: {...}, join: {...} }
|
|
666
|
+
*/
|
|
667
|
+
_parseConditionObject(conditionObj) {
|
|
668
|
+
const main = {};
|
|
669
|
+
const join = {};
|
|
670
|
+
|
|
671
|
+
_.forOwn(conditionObj, (value, key) => {
|
|
672
|
+
// Détecter si la clé contient un point (chemin vers table jointe)
|
|
673
|
+
if (key.includes('.')) {
|
|
674
|
+
// Extraire le chemin et la colonne finale
|
|
675
|
+
const parts = key.split('.');
|
|
676
|
+
const column = parts[parts.length - 1];
|
|
677
|
+
const path = parts.slice(0, -1); // ['applicant'] ou ['pme_folder', 'company', 'country']
|
|
678
|
+
|
|
679
|
+
join[key] = {
|
|
680
|
+
value,
|
|
681
|
+
path,
|
|
682
|
+
column
|
|
683
|
+
};
|
|
684
|
+
} else {
|
|
685
|
+
// Condition sur la table principale
|
|
686
|
+
// Transformer les opérateurs spéciaux en SQL avant de les ajouter
|
|
687
|
+
main[key] = this._transformOperator(key, value);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
return { main, join };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Transforme les opérateurs spéciaux en SQL
|
|
696
|
+
*
|
|
697
|
+
* @param {string} column - Nom de la colonne
|
|
698
|
+
* @param {any} value - Valeur (peut contenir des opérateurs)
|
|
699
|
+
* @returns {any} Valeur transformée ou SQL string avec params
|
|
700
|
+
*/
|
|
701
|
+
_transformOperator(column, value) {
|
|
702
|
+
// Si la valeur n'est pas un objet, la retourner telle quelle
|
|
703
|
+
if (!_.isObject(value) || _.isArray(value) || _.isDate(value)) {
|
|
704
|
+
return value;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Transformer les opérateurs spéciaux en SQL
|
|
708
|
+
if (value.$between && _.isArray(value.$between) && value.$between.length === 2) {
|
|
709
|
+
return [`\`${this.schema.table}\`.\`${column}\` BETWEEN $? AND $?`, value.$between];
|
|
710
|
+
} else if (value.$gte !== undefined) {
|
|
711
|
+
return [`\`${this.schema.table}\`.\`${column}\` >= $?`, value.$gte];
|
|
712
|
+
} else if (value.$lte !== undefined) {
|
|
713
|
+
return [`\`${this.schema.table}\`.\`${column}\` <= $?`, value.$lte];
|
|
714
|
+
} else if (value.$gt !== undefined) {
|
|
715
|
+
return [`\`${this.schema.table}\`.\`${column}\` > $?`, value.$gt];
|
|
716
|
+
} else if (value.$lt !== undefined) {
|
|
717
|
+
return [`\`${this.schema.table}\`.\`${column}\` < $?`, value.$lt];
|
|
718
|
+
} else if (value.$like !== undefined) {
|
|
719
|
+
return [`\`${this.schema.table}\`.\`${column}\` LIKE $?`, value.$like];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Sinon, retourner la valeur telle quelle
|
|
723
|
+
return value;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Construit automatiquement les filterJoins à partir des chemins détectés
|
|
728
|
+
*
|
|
729
|
+
* Cette méthode regroupe les conditions par chemin d'association et génère
|
|
730
|
+
* les filterJoinNested appropriés.
|
|
731
|
+
*
|
|
732
|
+
* @param {object} joinConditions - Conditions sur les tables jointes
|
|
733
|
+
*
|
|
734
|
+
* Exemple :
|
|
735
|
+
* Input: {
|
|
736
|
+
* 'applicant.last_name': { value: 'Dupont', path: ['applicant'], column: 'last_name' },
|
|
737
|
+
* 'applicant.email': { value: 'test@test.com', path: ['applicant'], column: 'email' },
|
|
738
|
+
* 'pme_folder.company.country.code': { value: 'FR', path: ['pme_folder', 'company', 'country'], column: 'code' }
|
|
739
|
+
* }
|
|
740
|
+
*
|
|
741
|
+
* Output: Appelle filterJoinNested() avec :
|
|
742
|
+
* {
|
|
743
|
+
* applicant: {
|
|
744
|
+
* conditions: { last_name: 'Dupont', email: 'test@test.com' }
|
|
745
|
+
* },
|
|
746
|
+
* pme_folder: {
|
|
747
|
+
* nested: {
|
|
748
|
+
* company: {
|
|
749
|
+
* nested: {
|
|
750
|
+
* country: {
|
|
751
|
+
* conditions: { code: 'FR' }
|
|
752
|
+
* }
|
|
753
|
+
* }
|
|
754
|
+
* }
|
|
755
|
+
* }
|
|
756
|
+
* }
|
|
757
|
+
* }
|
|
758
|
+
*/
|
|
759
|
+
_buildFilterJoinsFromPaths(joinConditions) {
|
|
760
|
+
// Regrouper les conditions par racine d'association
|
|
761
|
+
const groupedByRoot = {};
|
|
762
|
+
|
|
763
|
+
_.forOwn(joinConditions, ({ value, path, column }, fullPath) => {
|
|
764
|
+
const root = path[0];
|
|
765
|
+
|
|
766
|
+
if (!groupedByRoot[root]) {
|
|
767
|
+
groupedByRoot[root] = [];
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
groupedByRoot[root].push({
|
|
771
|
+
path: path.slice(1), // Enlever la racine
|
|
772
|
+
column,
|
|
773
|
+
value,
|
|
774
|
+
fullPath
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// Construire les filterJoinNested pour chaque racine
|
|
779
|
+
_.forOwn(groupedByRoot, (conditions, root) => {
|
|
780
|
+
// Si toutes les conditions sont au niveau racine (path vide), utiliser filterJoin simple
|
|
781
|
+
const allAtRoot = _.every(conditions, c => c.path.length === 0);
|
|
782
|
+
|
|
783
|
+
if (allAtRoot) {
|
|
784
|
+
// Filtre simple (1 niveau)
|
|
785
|
+
const simpleConditions = {};
|
|
786
|
+
_.forEach(conditions, ({ column, value }) => {
|
|
787
|
+
simpleConditions[column] = value;
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
this._addSimpleFilterJoin(root, simpleConditions);
|
|
791
|
+
} else {
|
|
792
|
+
// Filtre imbriqué (plusieurs niveaux)
|
|
793
|
+
const nestedConfig = this._buildNestedConfig(conditions);
|
|
794
|
+
this._addNestedFilterJoin({ [root]: nestedConfig });
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Construit la configuration imbriquée pour filterJoinNested
|
|
801
|
+
*
|
|
802
|
+
* @param {Array} conditions - Liste des conditions à imbriquer
|
|
803
|
+
* @returns {object} Configuration imbriquée
|
|
804
|
+
*
|
|
805
|
+
* Exemple :
|
|
806
|
+
* Input: [
|
|
807
|
+
* { path: ['company', 'country'], column: 'code', value: 'FR' },
|
|
808
|
+
* { path: ['company'], column: 'siret', value: '123%' }
|
|
809
|
+
* ]
|
|
810
|
+
*
|
|
811
|
+
* Output: {
|
|
812
|
+
* nested: {
|
|
813
|
+
* company: {
|
|
814
|
+
* conditions: { siret: '123%' },
|
|
815
|
+
* nested: {
|
|
816
|
+
* country: {
|
|
817
|
+
* conditions: { code: 'FR' }
|
|
818
|
+
* }
|
|
819
|
+
* }
|
|
820
|
+
* }
|
|
821
|
+
* }
|
|
822
|
+
* }
|
|
823
|
+
*/
|
|
824
|
+
_buildNestedConfig(conditions) {
|
|
825
|
+
const config = {
|
|
826
|
+
conditions: {},
|
|
827
|
+
nested: {}
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// Séparer les conditions : celles au niveau actuel vs celles à imbriquer
|
|
831
|
+
const currentLevelConditions = [];
|
|
832
|
+
const nestedConditions = {};
|
|
833
|
+
|
|
834
|
+
_.forEach(conditions, (cond) => {
|
|
835
|
+
if (cond.path.length === 0) {
|
|
836
|
+
// Condition au niveau actuel
|
|
837
|
+
currentLevelConditions.push(cond);
|
|
838
|
+
} else {
|
|
839
|
+
// Condition à imbriquer
|
|
840
|
+
const nextLevel = cond.path[0];
|
|
841
|
+
if (!nestedConditions[nextLevel]) {
|
|
842
|
+
nestedConditions[nextLevel] = [];
|
|
843
|
+
}
|
|
844
|
+
nestedConditions[nextLevel].push({
|
|
845
|
+
path: cond.path.slice(1),
|
|
846
|
+
column: cond.column,
|
|
847
|
+
value: cond.value
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// Ajouter les conditions au niveau actuel
|
|
853
|
+
_.forEach(currentLevelConditions, ({ column, value }) => {
|
|
854
|
+
config.conditions[column] = value;
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Si pas de conditions au niveau actuel, supprimer la clé
|
|
858
|
+
if (_.isEmpty(config.conditions)) {
|
|
859
|
+
delete config.conditions;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Construire récursivement les niveaux imbriqués
|
|
863
|
+
_.forOwn(nestedConditions, (nestedConds, assocName) => {
|
|
864
|
+
config.nested[assocName] = this._buildNestedConfig(nestedConds);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// Si pas de nested, supprimer la clé
|
|
868
|
+
if (_.isEmpty(config.nested)) {
|
|
869
|
+
delete config.nested;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return config;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Génère le SQL pour la requête optimisée
|
|
877
|
+
*
|
|
878
|
+
* Cette méthode override toSQL() pour générer le SQL approprié selon le verb.
|
|
879
|
+
*/
|
|
880
|
+
toSQL() {
|
|
881
|
+
const { query } = this;
|
|
882
|
+
const db = this.getDb();
|
|
883
|
+
|
|
884
|
+
// Utiliser PaginatedOptimizedSql pour les requêtes optimisées
|
|
885
|
+
if (query.optimized && (query.verb === 'count' || query.verb === 'select_ids')) {
|
|
886
|
+
// Ajouter le schema au query object pour que PaginatedOptimizedSql puisse y accéder
|
|
887
|
+
query.schema = this.schema;
|
|
888
|
+
|
|
889
|
+
const PaginatedOptimizedSql = require('./PaginatedOptimizedSql');
|
|
890
|
+
const sql = new PaginatedOptimizedSql(query, db.driver.dialect);
|
|
891
|
+
|
|
892
|
+
if (query.verb === 'count') {
|
|
893
|
+
return sql.countSQL();
|
|
894
|
+
} else if (query.verb === 'select_ids') {
|
|
895
|
+
return sql.idsSQL();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Sinon, utiliser Sql standard
|
|
900
|
+
return super.toSQL();
|
|
901
|
+
}
|
|
902
|
+
};
|