@igojs/db 6.0.0-beta.7 → 6.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igojs/db",
3
- "version": "6.0.0-beta.7",
3
+ "version": "6.0.0-beta.8",
4
4
  "description": "Igo ORM - Database abstraction layer for MySQL and PostgreSQL",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Model.js CHANGED
@@ -40,7 +40,7 @@ module.exports = function(schema) {
40
40
  await this.beforeUpdate(values);
41
41
 
42
42
  await newQuery(this.constructor, 'update')
43
- .unscoped()
43
+ .unscope()
44
44
  .values(values)
45
45
  .where(this.primaryObject())
46
46
  .execute();
@@ -56,14 +56,14 @@ module.exports = function(schema) {
56
56
 
57
57
  // reload
58
58
  async reload(includes) {
59
- const query = this.constructor.unscoped();
59
+ const query = this.constructor.unscope();
60
60
  includes && query.includes(includes);
61
61
  return await query.find(this.id);
62
62
  }
63
63
 
64
64
  // delete
65
65
  delete() {
66
- return newQuery(this.constructor, 'delete').unscoped().where(this.primaryObject()).execute();
66
+ return newQuery(this.constructor, 'delete').unscope().where(this.primaryObject()).execute();
67
67
  }
68
68
 
69
69
  async beforeCreate() { }
@@ -101,9 +101,9 @@ module.exports = function(schema) {
101
101
 
102
102
  const { insertId } = result;
103
103
  if (insertId) {
104
- return _this.unscoped().find(insertId);
104
+ return _this.unscope().find(insertId);
105
105
  }
106
- return _this.unscoped().find(obj.primaryObject());
106
+ return _this.unscope().find(obj.primaryObject());
107
107
  };
108
108
 
109
109
  return await create();
@@ -177,12 +177,12 @@ module.exports = function(schema) {
177
177
 
178
178
  // delete
179
179
  static delete(id, ) {
180
- return newQuery(this, 'delete').unscoped().where({ id: id }).execute();
180
+ return newQuery(this, 'delete').unscope().where({ id: id }).execute();
181
181
  }
182
182
 
183
183
  // delete all
184
184
  static async deleteAll() {
185
- return newQuery(this, 'delete').unscoped().execute();
185
+ return newQuery(this, 'delete').unscope().execute();
186
186
  }
187
187
 
188
188
  // destroy all
@@ -193,7 +193,7 @@ module.exports = function(schema) {
193
193
  //
194
194
  static update(values, ) {
195
195
  values.updated_at = new Date();
196
- return newQuery(this).unscoped().update(values, );
196
+ return newQuery(this).unscope().update(values, );
197
197
  }
198
198
 
199
199
  // includes
@@ -214,9 +214,15 @@ module.exports = function(schema) {
214
214
  console.warn('Invalid join argument for Model.join(). Must be a string, array, or object.');
215
215
  }
216
216
 
217
- //unscoped
217
+ // unscoped (deprecated, use unscope())
218
218
  static unscoped() {
219
- return newQuery(this).unscoped();
219
+ console.warn('Model.unscoped() is deprecated, use unscope() instead.');
220
+ return this.unscope();
221
+ }
222
+
223
+ // unscope
224
+ static unscope(...clauses) {
225
+ return newQuery(this).unscope(...clauses);
220
226
  }
221
227
 
222
228
  //scope
@@ -402,6 +402,10 @@ module.exports = class PaginatedOptimizedQuery extends Query {
402
402
  fullQuery.query = _.cloneDeep(this.query);
403
403
  fullQuery.query.verb = 'select';
404
404
 
405
+ // Restaurer les joins originaux : _.cloneDeep crée de nouveaux objets pour src_schema,
406
+ // ce qui casse les lookups par identité (===) dans le Map de Query.execute()
407
+ fullQuery.query.joins = this.query.joins;
408
+
405
409
  // Conserver uniquement les "vrais" joins (pas les filterJoins)
406
410
  // Les filterJoins ne sont utilisés que pour COUNT et IDS
407
411
  fullQuery.query.filterJoins = [];
package/src/Query.js CHANGED
@@ -50,7 +50,8 @@ module.exports = class Query {
50
50
  group: null,
51
51
  includes: {},
52
52
  options: {},
53
- scopes: [ 'default' ]
53
+ scopes: [ 'default' ],
54
+ unscopes: []
54
55
  };
55
56
 
56
57
  // filter on subclass
@@ -153,9 +154,19 @@ module.exports = class Query {
153
154
  return this;
154
155
  }
155
156
 
156
- // UNSCOPED
157
+ // UNSCOPED (deprecated, use unscope())
157
158
  unscoped() {
158
- this.query.scopes.length = 0;
159
+ console.warn('Query.unscoped() is deprecated, use unscope() instead.');
160
+ return this.unscope();
161
+ }
162
+
163
+ // UNSCOPE
164
+ unscope(...clauses) {
165
+ if (clauses.length === 0) {
166
+ this.query.scopes.length = 0;
167
+ } else {
168
+ this.query.unscopes.push(...clauses);
169
+ }
159
170
  return this;
160
171
  }
161
172
 
@@ -265,6 +276,21 @@ module.exports = class Query {
265
276
  }
266
277
  schema.scopes[scope](this);
267
278
  });
279
+ // Apply unscopes after scopes
280
+ for (const clause of query.unscopes) {
281
+ switch (clause) {
282
+ case 'where': query.where = []; break;
283
+ case 'whereNot': query.whereNot = []; break;
284
+ case 'order': query.order = []; break;
285
+ case 'includes': query.includes = {}; break;
286
+ case 'joins': query.joins = []; break;
287
+ case 'select': query.select = null; break;
288
+ case 'distinct': query.distinct = null; break;
289
+ case 'group': query.group = null; break;
290
+ case 'limit': delete query.limit; break;
291
+ case 'offset': delete query.offset; break;
292
+ }
293
+ }
268
294
  }
269
295
 
270
296
  // INCLUDES
package/test/JoinTest.js CHANGED
@@ -235,4 +235,38 @@ describe('includes', () => {
235
235
  });
236
236
  });
237
237
 
238
+ describe('paginatedOptimized join', () => {
239
+ it('should attach joined data in selectFull phase (cloneDeep regression)', async () => {
240
+ const city = await City.create({ name: 'Paris' });
241
+ const library = await Library.create({ title: 'BNF', city_id: city.id });
242
+ const book = await Book.create({ library_id: library.id });
243
+
244
+ const books = await Book.paginatedOptimized()
245
+ .join('library')
246
+ .limit(10)
247
+ .list();
248
+
249
+ assert.strictEqual(books.length, 1);
250
+ assert.strictEqual(books[0].id, book.id);
251
+ assert.strictEqual(books[0].library.id, library.id);
252
+ assert.strictEqual(books[0].library.title, 'BNF');
253
+ });
254
+
255
+ it('should attach nested joined data in selectFull phase', async () => {
256
+ const city = await City.create({ name: 'Lyon' });
257
+ const library = await Library.create({ title: 'Municipale', city_id: city.id });
258
+ const book = await Book.create({ library_id: library.id });
259
+
260
+ const books = await Book.paginatedOptimized()
261
+ .join({ library: 'city' })
262
+ .limit(10)
263
+ .list();
264
+
265
+ assert.strictEqual(books.length, 1);
266
+ assert.strictEqual(books[0].library.id, library.id);
267
+ assert.strictEqual(books[0].library.city.id, city.id);
268
+ assert.strictEqual(books[0].library.city.name, 'Lyon');
269
+ });
270
+ });
271
+
238
272
  });
package/test/ModelTest.js CHANGED
@@ -182,7 +182,7 @@ describe('db.Model', () => {
182
182
  const first = await Book.create();
183
183
  const hibook = await Book.create({title: 'hi'});
184
184
  await Book.create();
185
- const book = await Book.unscoped().first();
185
+ const book = await Book.unscope().first();
186
186
  assert.strictEqual(first.id, book.id);
187
187
  assert.strictEqual('hi', hibook.title);
188
188
  });
@@ -203,7 +203,7 @@ describe('db.Model', () => {
203
203
  await Book.create();
204
204
  await Book.create();
205
205
  const last = await Book.create();
206
- const book = await Book.unscoped().last();
206
+ const book = await Book.unscope().last();
207
207
  assert.strictEqual(last.id, book.id);
208
208
  });
209
209
  });
@@ -374,11 +374,91 @@ describe('db.Model', () => {
374
374
  it('should use a scope', async () => {
375
375
  await BookWithScopes.create({code: 'a'});
376
376
  await BookWithScopes.create({code: 'abc'});
377
- const books = await BookWithScopes.unscoped().scope('a').list();
377
+ const books = await BookWithScopes.unscope().scope('a').list();
378
378
  assert.strictEqual(books.length, 1);
379
379
  });
380
380
  });
381
381
 
382
+ describe('unscope', () => {
383
+
384
+ class Library extends Model({
385
+ table: 'libraries',
386
+ primary: ['id'],
387
+ columns: ['id', 'title'],
388
+ }) {}
389
+
390
+ const scopedSchema = {
391
+ table: 'books',
392
+ primary: ['id'],
393
+ columns: [
394
+ 'id',
395
+ 'code',
396
+ 'title',
397
+ {name: 'details_json', type: 'json', attr: 'details'},
398
+ {name:'is_available', type: 'boolean'},
399
+ 'library_id',
400
+ 'created_at'
401
+ ],
402
+ associations: () => ([
403
+ ['belongs_to', 'library', Library, 'library_id', 'id'],
404
+ ]),
405
+ scopes: {
406
+ default: query => query.where({ code: 'abc' }).order('`created_at` DESC').includes('library'),
407
+ active: query => query.where({ is_available: true }),
408
+ }
409
+ };
410
+
411
+ class ScopedBook extends Model(scopedSchema) {}
412
+
413
+ it('unscope() without args should remove all scopes', async () => {
414
+ await ScopedBook.create({ code: 'a' });
415
+ await ScopedBook.create({ code: 'abc' });
416
+ const books = await ScopedBook.unscope().list();
417
+ assert.strictEqual(books.length, 2);
418
+ });
419
+
420
+ it('unscope("where") should remove where added by scope', async () => {
421
+ await ScopedBook.create({ code: 'a' });
422
+ await ScopedBook.create({ code: 'abc' });
423
+ const books = await ScopedBook.unscope('where').list();
424
+ assert.strictEqual(books.length, 2);
425
+ });
426
+
427
+ it('unscope("includes") should remove includes added by scope', async () => {
428
+ const library = await Library.create({ title: 'Main' });
429
+ await ScopedBook.create({ code: 'abc', library_id: library.id });
430
+ const books = await ScopedBook.unscope('includes').list();
431
+ assert.strictEqual(books.length, 1);
432
+ assert.strictEqual(books[0].library, undefined);
433
+ });
434
+
435
+ it('unscope("order") should remove order added by scope', async () => {
436
+ const b1 = await ScopedBook.create({ code: 'abc' });
437
+ const b2 = await ScopedBook.create({ code: 'abc' });
438
+ const books = await ScopedBook.unscope('order').list();
439
+ // without DESC order, should be ASC (default)
440
+ assert(books[0].id <= books[1].id);
441
+ });
442
+
443
+ it('unscope("where", "order") should remove both', async () => {
444
+ await ScopedBook.create({ code: 'a' });
445
+ const b2 = await ScopedBook.create({ code: 'abc' });
446
+ const books = await ScopedBook.unscope('where', 'order').list();
447
+ assert.strictEqual(books.length, 2);
448
+ assert(books[0].id <= books[1].id);
449
+ });
450
+
451
+ it('should work with chaining: unscope("includes").scope("active")', async () => {
452
+ const library = await Library.create({ title: 'Main' });
453
+ await ScopedBook.create({ code: 'abc', is_available: true, library_id: library.id });
454
+ await ScopedBook.create({ code: 'abc', is_available: false, library_id: library.id });
455
+ const books = await ScopedBook.unscope('includes').scope('active').list();
456
+ assert.strictEqual(books.length, 1);
457
+ assert.strictEqual(books[0].is_available, true);
458
+ assert.strictEqual(books[0].library, undefined);
459
+ });
460
+ });
461
+
382
462
  describe('count', () => {
383
463
  it('should count rows', async () => {
384
464
  for (let n = 0; n < 10; n++) {