@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
package/test/PerfTest.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require('./init');
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const Model = require('@igojs/db').Model;
|
|
5
|
+
|
|
6
|
+
const NB = 10000;
|
|
7
|
+
|
|
8
|
+
describe('PerfTest', function () {
|
|
9
|
+
const schema = {
|
|
10
|
+
table: 'books',
|
|
11
|
+
primary: ['id'],
|
|
12
|
+
columns: [
|
|
13
|
+
'id',
|
|
14
|
+
'code',
|
|
15
|
+
'title',
|
|
16
|
+
{ name: 'details_json', type: 'json', attr: 'details' },
|
|
17
|
+
{ name: 'tags_array', type: 'array', attr: 'tags' },
|
|
18
|
+
{ name: 'is_available', type: 'boolean' },
|
|
19
|
+
'library_id',
|
|
20
|
+
'created_at'
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
class Book extends Model(schema) {}
|
|
25
|
+
|
|
26
|
+
const createBook = async () => {
|
|
27
|
+
await Book.create({
|
|
28
|
+
code: 'AZER',
|
|
29
|
+
title: 'AZER AZERA AZER',
|
|
30
|
+
details: { qsdf: 1234, sdfq: 'azer', qzer: 'AZER YY' },
|
|
31
|
+
tags: ['hello', 'world', 'john'],
|
|
32
|
+
is_available: true
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('Write', function () {
|
|
37
|
+
it.skip('should create many objects', async function () {
|
|
38
|
+
const t0 = Date.now();
|
|
39
|
+
for (let i = 0; i < NB; i++) {
|
|
40
|
+
await createBook();
|
|
41
|
+
}
|
|
42
|
+
console.log(`t: ${Date.now() - t0}ms`);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Read', function () {
|
|
47
|
+
it.skip('should read many objects', async function () {
|
|
48
|
+
for (let i = 0; i < NB; i++) {
|
|
49
|
+
await createBook();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const t0 = Date.now();
|
|
53
|
+
const books = await Book.list();
|
|
54
|
+
console.log(`t: ${Date.now() - t0}ms`);
|
|
55
|
+
assert.strictEqual(books.length, NB);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
var assert = require('assert');
|
|
4
|
+
|
|
5
|
+
var Sql = require('@igojs/db').Sql;
|
|
6
|
+
|
|
7
|
+
const { dialect } = require('@igojs/db/src/drivers/postgresql');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
describe('db.PostgreSql', function() {
|
|
11
|
+
|
|
12
|
+
var query = {
|
|
13
|
+
table: 'books',
|
|
14
|
+
where: []
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//
|
|
18
|
+
describe('selectSQL', function() {
|
|
19
|
+
|
|
20
|
+
it('should return correct SQL', function() {
|
|
21
|
+
var selectSQL = new Sql(query, dialect).selectSQL();
|
|
22
|
+
assert.strictEqual('SELECT "books".* FROM "books"', selectSQL.sql);
|
|
23
|
+
assert.strictEqual(0, selectSQL.params.length);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should allow order by', function() {
|
|
27
|
+
query.order = [ '"title"' ];
|
|
28
|
+
var selectSQL = new Sql(query, dialect).selectSQL();
|
|
29
|
+
assert.strictEqual('SELECT "books".* FROM "books" ORDER BY "title"', selectSQL.sql);
|
|
30
|
+
assert.strictEqual(0, selectSQL.params.length);
|
|
31
|
+
delete query.order;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should allow limit', function() {
|
|
35
|
+
query.limit = 3;
|
|
36
|
+
var selectSQL = new Sql(query, dialect).selectSQL();
|
|
37
|
+
assert.strictEqual('SELECT "books".* FROM "books" LIMIT $2 OFFSET $1', selectSQL.sql);
|
|
38
|
+
assert.strictEqual(2, selectSQL.params.length);
|
|
39
|
+
assert.strictEqual(0, selectSQL.params[0]);
|
|
40
|
+
assert.strictEqual(3, selectSQL.params[1]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should allow distinct', function() {
|
|
44
|
+
query.distinct = [ 'type' ];
|
|
45
|
+
query.limit = null;
|
|
46
|
+
var selectSQL = new Sql(query, dialect).selectSQL();
|
|
47
|
+
assert.strictEqual('SELECT DISTINCT "type" FROM "books"', selectSQL.sql);
|
|
48
|
+
assert.strictEqual(0, selectSQL.params.length);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
//
|
|
53
|
+
describe('countSQL', function() {
|
|
54
|
+
it('should return correct SQL', function() {
|
|
55
|
+
var selectSQL = new Sql(query, dialect).countSQL();
|
|
56
|
+
assert.strictEqual('SELECT COUNT(0) as "count" FROM "books"', selectSQL.sql);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
//
|
|
61
|
+
describe('whereSQL', function() {
|
|
62
|
+
|
|
63
|
+
it('should allow string as query param', function() {
|
|
64
|
+
var params = [];
|
|
65
|
+
query.where = [[ 'field like ?', '%soon%' ]];
|
|
66
|
+
var sql = new Sql(query, dialect).whereSQL(params);
|
|
67
|
+
assert.strictEqual('WHERE field like ? ', sql);
|
|
68
|
+
assert.strictEqual('%soon%', params[0]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should allow integer as query param', function() {
|
|
72
|
+
var params = [];
|
|
73
|
+
query.where = [[ 'id=?', 12 ]];
|
|
74
|
+
var sql = new Sql(query, dialect).whereSQL(params);
|
|
75
|
+
assert.strictEqual('WHERE id=? ', sql);
|
|
76
|
+
assert.strictEqual(12, params[0]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should allow array as query param', function() {
|
|
80
|
+
var params = [];
|
|
81
|
+
query.where = [[ 'field like ?', ['%soon%'] ]];
|
|
82
|
+
var sql = new Sql(query, dialect).whereSQL(params);
|
|
83
|
+
assert.strictEqual('WHERE field like ? ', sql);
|
|
84
|
+
assert.strictEqual('%soon%', params[0]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should allow object as criterion', function() {
|
|
88
|
+
var params = [];
|
|
89
|
+
query.where = [{ id: 123 }];
|
|
90
|
+
var sql = new Sql(query, dialect).whereSQL(params);
|
|
91
|
+
assert.strictEqual('WHERE "books"."id" = $1 ', sql);
|
|
92
|
+
assert.strictEqual(123, params[0]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
require('./init');
|
|
4
|
+
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
|
|
7
|
+
const Query = require('@igojs/db').Query;
|
|
8
|
+
const Model = require('@igojs/db').Model;
|
|
9
|
+
|
|
10
|
+
//
|
|
11
|
+
describe('db.Query', function() {
|
|
12
|
+
|
|
13
|
+
class Book extends Model({
|
|
14
|
+
table: 'books',
|
|
15
|
+
primary: ['id']
|
|
16
|
+
}) {}
|
|
17
|
+
|
|
18
|
+
//
|
|
19
|
+
describe('first', function() {
|
|
20
|
+
it('should return correct SQL', async () => {
|
|
21
|
+
const query = new Query(Book)
|
|
22
|
+
await query.first();
|
|
23
|
+
const sql = 'SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT ?, ?';
|
|
24
|
+
assert.strictEqual(sql, query.query.generated.sql);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests pour la nouvelle syntaxe simplifiée de PaginatedOptimizedQuery
|
|
3
|
+
*
|
|
4
|
+
* Ces tests valident :
|
|
5
|
+
* - La détection automatique des chemins imbriqués
|
|
6
|
+
* - La génération correcte des filterJoins
|
|
7
|
+
* - Le support de tous les opérateurs ($like, $between, $gte, etc.)
|
|
8
|
+
* - Les opérateurs logiques ($and, $or)
|
|
9
|
+
* - La génération SQL correcte
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const assert = require('assert');
|
|
13
|
+
const Model = require('@igojs/db').Model;
|
|
14
|
+
|
|
15
|
+
// ============================
|
|
16
|
+
// MODÈLES DE TEST
|
|
17
|
+
// ============================
|
|
18
|
+
|
|
19
|
+
class Country extends Model({
|
|
20
|
+
table: 'countries',
|
|
21
|
+
columns: {
|
|
22
|
+
id: 'integer',
|
|
23
|
+
code: 'string',
|
|
24
|
+
name: 'string'
|
|
25
|
+
}
|
|
26
|
+
}) {}
|
|
27
|
+
|
|
28
|
+
class Company extends Model({
|
|
29
|
+
table: 'companies',
|
|
30
|
+
columns: {
|
|
31
|
+
id: 'integer',
|
|
32
|
+
name: 'string',
|
|
33
|
+
siret: 'string',
|
|
34
|
+
country_id: 'integer'
|
|
35
|
+
},
|
|
36
|
+
associations: [
|
|
37
|
+
['belongs_to', 'country', Country, 'country_id', 'id']
|
|
38
|
+
]
|
|
39
|
+
}) {}
|
|
40
|
+
|
|
41
|
+
class PmeFolder extends Model({
|
|
42
|
+
table: 'pme_folders',
|
|
43
|
+
columns: {
|
|
44
|
+
id: 'integer',
|
|
45
|
+
status: 'string',
|
|
46
|
+
company_id: 'integer'
|
|
47
|
+
},
|
|
48
|
+
associations: [
|
|
49
|
+
['belongs_to', 'company', Company, 'company_id', 'id']
|
|
50
|
+
]
|
|
51
|
+
}) {}
|
|
52
|
+
|
|
53
|
+
class Applicant extends Model({
|
|
54
|
+
table: 'applicants',
|
|
55
|
+
columns: {
|
|
56
|
+
id: 'integer',
|
|
57
|
+
first_name: 'string',
|
|
58
|
+
last_name: 'string',
|
|
59
|
+
email: 'string',
|
|
60
|
+
identity_number: 'string'
|
|
61
|
+
}
|
|
62
|
+
}) {}
|
|
63
|
+
|
|
64
|
+
class Folder extends Model({
|
|
65
|
+
table: 'folders',
|
|
66
|
+
columns: {
|
|
67
|
+
id: 'integer',
|
|
68
|
+
type: 'string',
|
|
69
|
+
status: 'string',
|
|
70
|
+
applicant_id: 'integer',
|
|
71
|
+
pme_folder_id: 'integer',
|
|
72
|
+
created_at: 'datetime'
|
|
73
|
+
},
|
|
74
|
+
associations: [
|
|
75
|
+
['belongs_to', 'applicant', Applicant, 'applicant_id', 'id'],
|
|
76
|
+
['belongs_to', 'pme_folder', PmeFolder, 'pme_folder_id', 'id']
|
|
77
|
+
]
|
|
78
|
+
}) {}
|
|
79
|
+
|
|
80
|
+
// ============================
|
|
81
|
+
// HELPERS
|
|
82
|
+
// ============================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Mock getDb pour la génération SQL
|
|
86
|
+
*/
|
|
87
|
+
function mockGetDb(query) {
|
|
88
|
+
query.getDb = () => ({
|
|
89
|
+
driver: {
|
|
90
|
+
dialect: {
|
|
91
|
+
esc: '`',
|
|
92
|
+
param: (i) => '?',
|
|
93
|
+
in: 'IN',
|
|
94
|
+
notin: 'NOT IN',
|
|
95
|
+
limit: () => 'LIMIT ?, ?'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Génère le SQL COUNT pour une query
|
|
103
|
+
*/
|
|
104
|
+
function generateCountSQL(query) {
|
|
105
|
+
mockGetDb(query);
|
|
106
|
+
query.query.verb = 'count';
|
|
107
|
+
return query.toSQL();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================
|
|
111
|
+
// TESTS
|
|
112
|
+
// ============================
|
|
113
|
+
|
|
114
|
+
describe('SimplifiedSyntax - PaginatedOptimizedQuery', () => {
|
|
115
|
+
|
|
116
|
+
describe('Détection des chemins imbriqués', () => {
|
|
117
|
+
|
|
118
|
+
it('devrait détecter un chemin simple (1 niveau)', () => {
|
|
119
|
+
const query = Folder.paginatedOptimized()
|
|
120
|
+
.where({
|
|
121
|
+
status: 'SUBMITTED',
|
|
122
|
+
'applicant.last_name': 'Dupont'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Vérifier que filterJoins a été créé
|
|
126
|
+
assert.ok(query.query.filterJoins.length > 0, 'filterJoins devrait contenir au moins un élément');
|
|
127
|
+
|
|
128
|
+
// Vérifier que le filtre sur la table principale est dans where
|
|
129
|
+
assert.ok(query.query.where.length > 0, 'where devrait contenir la condition sur status');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('devrait détecter un chemin imbriqué (3 niveaux)', () => {
|
|
133
|
+
const query = Folder.paginatedOptimized()
|
|
134
|
+
.where({
|
|
135
|
+
'pme_folder.company.country.code': 'FR'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Vérifier que filterJoins nested a été créé
|
|
139
|
+
assert.ok(query.query.filterJoins.length > 0, 'filterJoins devrait être créé');
|
|
140
|
+
assert.equal(query.query.filterJoins[0].type, 'nested', 'Le filterJoin devrait être de type nested');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('devrait regrouper les conditions sur la même table', () => {
|
|
144
|
+
const query = Folder.paginatedOptimized()
|
|
145
|
+
.where({
|
|
146
|
+
'applicant.last_name': 'Dupont',
|
|
147
|
+
'applicant.first_name': 'Jean',
|
|
148
|
+
'applicant.email': 'test@test.com'
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Vérifier qu'un seul filterJoin a été créé (regroupement)
|
|
152
|
+
assert.equal(query.query.filterJoins.length, 1, 'Devrait créer un seul filterJoin pour applicant');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Opérateurs de comparaison', () => {
|
|
158
|
+
|
|
159
|
+
it('devrait supporter $like', () => {
|
|
160
|
+
const query = Folder.paginatedOptimized()
|
|
161
|
+
.where({
|
|
162
|
+
'applicant.last_name': { $like: 'Dup%' }
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const sql = generateCountSQL(query);
|
|
166
|
+
|
|
167
|
+
assert.ok(sql.sql.includes('LIKE'), 'Le SQL devrait contenir LIKE');
|
|
168
|
+
assert.ok(sql.params.includes('Dup%'), 'Les paramètres devraient contenir la valeur du LIKE');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('devrait supporter $between', () => {
|
|
172
|
+
const query = Folder.paginatedOptimized()
|
|
173
|
+
.where({
|
|
174
|
+
created_at: { $between: ['2024-01-01', '2024-12-31'] }
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const sql = generateCountSQL(query);
|
|
178
|
+
|
|
179
|
+
assert.ok(sql.sql.includes('BETWEEN'), 'Le SQL devrait contenir BETWEEN');
|
|
180
|
+
assert.equal(sql.params.length, 2, 'Les paramètres devraient contenir les 2 dates');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('devrait supporter $gte (>=)', () => {
|
|
184
|
+
const query = Folder.paginatedOptimized()
|
|
185
|
+
.where({
|
|
186
|
+
'pme_folder.company.id': { $gte: 100 }
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const sql = generateCountSQL(query);
|
|
190
|
+
|
|
191
|
+
assert.ok(sql.sql.includes('>='), 'Le SQL devrait contenir >=');
|
|
192
|
+
assert.ok(sql.params.includes(100), 'Les paramètres devraient contenir 100');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('devrait supporter $lte (<=)', () => {
|
|
196
|
+
const query = Folder.paginatedOptimized()
|
|
197
|
+
.where({
|
|
198
|
+
'pme_folder.company.id': { $lte: 1000 }
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const sql = generateCountSQL(query);
|
|
202
|
+
|
|
203
|
+
assert.ok(sql.sql.includes('<='), 'Le SQL devrait contenir <=');
|
|
204
|
+
assert.ok(sql.params.includes(1000), 'Les paramètres devraient contenir 1000');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('devrait supporter $gt (>)', () => {
|
|
208
|
+
const query = Folder.paginatedOptimized()
|
|
209
|
+
.where({
|
|
210
|
+
'pme_folder.company.id': { $gt: 50 }
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const sql = generateCountSQL(query);
|
|
214
|
+
|
|
215
|
+
assert.ok(sql.sql.includes('>'), 'Le SQL devrait contenir >');
|
|
216
|
+
assert.ok(sql.params.includes(50), 'Les paramètres devraient contenir 50');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('devrait supporter $lt (<)', () => {
|
|
220
|
+
const query = Folder.paginatedOptimized()
|
|
221
|
+
.where({
|
|
222
|
+
'pme_folder.company.id': { $lt: 500 }
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const sql = generateCountSQL(query);
|
|
226
|
+
|
|
227
|
+
assert.ok(sql.sql.includes('<'), 'Le SQL devrait contenir <');
|
|
228
|
+
assert.ok(sql.params.includes(500), 'Les paramètres devraient contenir 500');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('devrait détecter automatiquement LIKE avec %', () => {
|
|
232
|
+
const query = Folder.paginatedOptimized()
|
|
233
|
+
.where({
|
|
234
|
+
'applicant.last_name': 'Dup%'
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const sql = generateCountSQL(query);
|
|
238
|
+
|
|
239
|
+
assert.ok(sql.sql.includes('LIKE'), 'Le SQL devrait contenir LIKE (détection auto du %)');
|
|
240
|
+
assert.ok(sql.params.includes('Dup%'), 'Les paramètres devraient contenir Dup%');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('devrait supporter IN avec tableau', () => {
|
|
244
|
+
const query = Folder.paginatedOptimized()
|
|
245
|
+
.where({
|
|
246
|
+
status: ['SUBMITTED', 'VALIDATED']
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const sql = generateCountSQL(query);
|
|
250
|
+
|
|
251
|
+
assert.ok(sql.sql.includes('IN'), 'Le SQL devrait contenir IN');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('Opérateurs logiques', () => {
|
|
257
|
+
|
|
258
|
+
it('devrait supporter $and', () => {
|
|
259
|
+
const query = Folder.paginatedOptimized()
|
|
260
|
+
.where({
|
|
261
|
+
$and: [
|
|
262
|
+
{ status: 'SUBMITTED' },
|
|
263
|
+
{ 'applicant.last_name': 'Dupont' }
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const sql = generateCountSQL(query);
|
|
268
|
+
|
|
269
|
+
// Vérifier que les deux conditions sont présentes
|
|
270
|
+
assert.ok(sql.sql.includes('status'), 'Le SQL devrait contenir la condition sur status');
|
|
271
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Le SQL devrait contenir EXISTS pour applicant');
|
|
272
|
+
assert.ok(sql.params.includes('SUBMITTED'), 'Les paramètres devraient contenir SUBMITTED');
|
|
273
|
+
assert.ok(sql.params.includes('Dupont'), 'Les paramètres devraient contenir Dupont');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('devrait supporter $or (optimisé en IN si même colonne)', () => {
|
|
277
|
+
// Note : Pour $or sur des colonnes différentes de la table principale,
|
|
278
|
+
// il est préférable d'utiliser IN ou plusieurs appels where()
|
|
279
|
+
// Le $or est principalement utile pour les tables jointes
|
|
280
|
+
const query = Folder.paginatedOptimized()
|
|
281
|
+
.where({
|
|
282
|
+
status: ['SUBMITTED', 'VALIDATED'] // Équivalent à OR
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const sql = generateCountSQL(query);
|
|
286
|
+
|
|
287
|
+
// Vérifier que IN est généré (équivalent à OR pour même colonne)
|
|
288
|
+
assert.ok(sql.sql.includes('status'), 'Le SQL devrait contenir status');
|
|
289
|
+
assert.ok(sql.sql.includes('IN'), 'Le SQL devrait contenir IN pour simuler OR');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('devrait supporter $and avec conditions sur tables jointes', () => {
|
|
293
|
+
const query = Folder.paginatedOptimized()
|
|
294
|
+
.where({
|
|
295
|
+
$and: [
|
|
296
|
+
{ 'applicant.last_name': { $like: 'Dup%' } },
|
|
297
|
+
{ 'applicant.first_name': { $like: 'Jean%' } },
|
|
298
|
+
{ 'pme_folder.company.siret': { $like: '123%' } }
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const sql = generateCountSQL(query);
|
|
303
|
+
|
|
304
|
+
// Vérifier que plusieurs EXISTS sont générés
|
|
305
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
306
|
+
assert.ok(existsCount >= 2, 'Le SQL devrait contenir au moins 2 EXISTS');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('devrait supporter combinaison AND avec conditions multiples', () => {
|
|
310
|
+
const query = Folder.paginatedOptimized()
|
|
311
|
+
.where({
|
|
312
|
+
$and: [
|
|
313
|
+
{ status: 'SUBMITTED' },
|
|
314
|
+
{ 'applicant.last_name': 'Dupont' },
|
|
315
|
+
{ 'applicant.first_name': 'Jean' }
|
|
316
|
+
]
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Vérifier que la query est construite sans erreur
|
|
320
|
+
assert.ok(query.query.where.length > 0, 'where devrait contenir des conditions');
|
|
321
|
+
assert.ok(query.query.filterJoins.length > 0, 'filterJoins devrait être créé');
|
|
322
|
+
|
|
323
|
+
const sql = generateCountSQL(query);
|
|
324
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Le SQL devrait contenir EXISTS pour les jointures');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('Génération SQL avec EXISTS', () => {
|
|
330
|
+
|
|
331
|
+
it('devrait générer un EXISTS pour un chemin simple', () => {
|
|
332
|
+
const query = Folder.paginatedOptimized()
|
|
333
|
+
.where({
|
|
334
|
+
'applicant.last_name': 'Dupont'
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const sql = generateCountSQL(query);
|
|
338
|
+
|
|
339
|
+
// Vérifier la structure EXISTS
|
|
340
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Le SQL devrait contenir EXISTS');
|
|
341
|
+
assert.ok(sql.sql.includes('SELECT 1 FROM'), 'Le SQL devrait contenir SELECT 1 FROM');
|
|
342
|
+
assert.ok(sql.sql.includes('applicants'), 'Le SQL devrait référencer la table applicants');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('devrait générer des EXISTS imbriqués pour un chemin à 3 niveaux', () => {
|
|
346
|
+
const query = Folder.paginatedOptimized()
|
|
347
|
+
.where({
|
|
348
|
+
'pme_folder.company.country.code': 'FR'
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const sql = generateCountSQL(query);
|
|
352
|
+
|
|
353
|
+
// Vérifier que des EXISTS imbriqués sont générés
|
|
354
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
355
|
+
assert.ok(existsCount >= 3, 'Le SQL devrait contenir au moins 3 EXISTS imbriqués');
|
|
356
|
+
|
|
357
|
+
// Vérifier que les tables sont mentionnées
|
|
358
|
+
assert.ok(sql.sql.includes('pme_folders'), 'Le SQL devrait référencer pme_folders');
|
|
359
|
+
assert.ok(sql.sql.includes('companies'), 'Le SQL devrait référencer companies');
|
|
360
|
+
assert.ok(sql.sql.includes('countries'), 'Le SQL devrait référencer countries');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('devrait optimiser en regroupant les conditions sur la même table', () => {
|
|
364
|
+
const query = Folder.paginatedOptimized()
|
|
365
|
+
.where({
|
|
366
|
+
'applicant.last_name': 'Dupont',
|
|
367
|
+
'applicant.first_name': 'Jean'
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const sql = generateCountSQL(query);
|
|
371
|
+
|
|
372
|
+
// Vérifier qu'un seul EXISTS est généré (optimisation)
|
|
373
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
374
|
+
assert.equal(existsCount, 1, 'Le SQL devrait contenir un seul EXISTS (conditions regroupées)');
|
|
375
|
+
|
|
376
|
+
// Vérifier que les deux conditions sont présentes
|
|
377
|
+
assert.ok(sql.params.includes('Dupont'), 'Les paramètres devraient contenir Dupont');
|
|
378
|
+
assert.ok(sql.params.includes('Jean'), 'Les paramètres devraient contenir Jean');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
describe('Cas complexes', () => {
|
|
385
|
+
|
|
386
|
+
it('devrait gérer une requête de recherche multi-champs', () => {
|
|
387
|
+
const token = 'test';
|
|
388
|
+
const query = Folder.paginatedOptimized()
|
|
389
|
+
.where({
|
|
390
|
+
$and: [
|
|
391
|
+
{ created_at: { $between: ['2024-01-01', '2024-12-31'] } },
|
|
392
|
+
{ 'applicant.last_name': { $like: `${token}%` } },
|
|
393
|
+
{ 'applicant.first_name': { $like: `${token}%` } },
|
|
394
|
+
{ 'applicant.email': token },
|
|
395
|
+
{ 'applicant.identity_number': { $gte: token } },
|
|
396
|
+
{ 'pme_folder.company.siret': { $like: `${token}%` } },
|
|
397
|
+
{ 'pme_folder.company.country.code': { $like: `${token}%` } }
|
|
398
|
+
]
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const sql = generateCountSQL(query);
|
|
402
|
+
|
|
403
|
+
// Vérifier que le SQL est généré sans erreur
|
|
404
|
+
assert.ok(sql.sql.length > 0, 'Le SQL devrait être généré');
|
|
405
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Le SQL devrait contenir EXISTS');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('devrait gérer les conditions mixtes (table principale + jointes)', () => {
|
|
409
|
+
const query = Folder.paginatedOptimized()
|
|
410
|
+
.where({
|
|
411
|
+
$and: [
|
|
412
|
+
{ status: 'SUBMITTED' },
|
|
413
|
+
{ type: ['agp', 'avt'] },
|
|
414
|
+
{ created_at: { $between: ['2024-01-01', '2024-12-31'] } },
|
|
415
|
+
{ 'applicant.last_name': 'Dupont' },
|
|
416
|
+
{ 'pme_folder.company.country.code': 'FR' }
|
|
417
|
+
]
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const sql = generateCountSQL(query);
|
|
421
|
+
|
|
422
|
+
// Vérifier que les conditions de la table principale sont dans WHERE
|
|
423
|
+
assert.ok(sql.sql.includes('status'), 'Le SQL devrait contenir status');
|
|
424
|
+
assert.ok(sql.sql.includes('type'), 'Le SQL devrait contenir type');
|
|
425
|
+
assert.ok(sql.sql.includes('created_at'), 'Le SQL devrait contenir created_at');
|
|
426
|
+
|
|
427
|
+
// Vérifier que les conditions des tables jointes sont dans EXISTS
|
|
428
|
+
assert.ok(sql.sql.includes('EXISTS'), 'Le SQL devrait contenir EXISTS');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('devrait gérer les chemins imbriqués multiples', () => {
|
|
432
|
+
const query = Folder.paginatedOptimized()
|
|
433
|
+
.where({
|
|
434
|
+
'applicant.last_name': 'Dupont',
|
|
435
|
+
'pme_folder.company.siret': '123%',
|
|
436
|
+
'pme_folder.company.country.code': 'FR'
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const sql = generateCountSQL(query);
|
|
440
|
+
|
|
441
|
+
// Vérifier que plusieurs EXISTS sont générés
|
|
442
|
+
const existsCount = (sql.sql.match(/EXISTS/g) || []).length;
|
|
443
|
+
assert.ok(existsCount >= 2, 'Le SQL devrait contenir plusieurs EXISTS');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('Validation et erreurs', () => {
|
|
449
|
+
|
|
450
|
+
it('devrait accepter un where vide', () => {
|
|
451
|
+
const query = Folder.paginatedOptimized()
|
|
452
|
+
.where({});
|
|
453
|
+
|
|
454
|
+
// Pas d'erreur attendue
|
|
455
|
+
assert.ok(query, 'La query devrait être créée sans erreur');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('devrait accepter des conditions null/undefined', () => {
|
|
459
|
+
const query = Folder.paginatedOptimized()
|
|
460
|
+
.where({
|
|
461
|
+
status: 'SUBMITTED',
|
|
462
|
+
deleted_at: null
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const sql = generateCountSQL(query);
|
|
466
|
+
|
|
467
|
+
// Vérifier que IS NULL est généré
|
|
468
|
+
assert.ok(sql.sql.includes('IS NULL'), 'Le SQL devrait contenir IS NULL');
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
});
|