@nocobase/database 0.9.3-alpha.1 → 0.9.4-alpha.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.
Files changed (59) hide show
  1. package/lib/collection.d.ts +9 -9
  2. package/lib/collection.js +100 -96
  3. package/lib/database.d.ts +4 -4
  4. package/lib/database.js +25 -53
  5. package/lib/eager-loading/eager-loading-tree.d.ts +23 -0
  6. package/lib/eager-loading/eager-loading-tree.js +338 -0
  7. package/lib/filter-parser.d.ts +1 -7
  8. package/lib/filter-parser.js +27 -7
  9. package/lib/listeners/append-child-collection-name-after-repository-find.d.ts +5 -0
  10. package/lib/listeners/append-child-collection-name-after-repository-find.js +40 -0
  11. package/lib/listeners/index.js +2 -0
  12. package/lib/mock-database.js +3 -1
  13. package/lib/operators/string.js +1 -1
  14. package/lib/options-parser.js +4 -0
  15. package/lib/query-interface/postgres-query-interface.js +2 -2
  16. package/lib/relation-repository/belongs-to-many-repository.d.ts +2 -1
  17. package/lib/relation-repository/belongs-to-many-repository.js +58 -37
  18. package/lib/relation-repository/hasmany-repository.d.ts +2 -1
  19. package/lib/relation-repository/hasmany-repository.js +31 -16
  20. package/lib/relation-repository/multiple-relation-repository.js +8 -26
  21. package/lib/relation-repository/relation-repository.d.ts +1 -7
  22. package/lib/relation-repository/single-relation-repository.d.ts +1 -1
  23. package/lib/relation-repository/single-relation-repository.js +10 -16
  24. package/lib/repository.d.ts +11 -8
  25. package/lib/repository.js +104 -89
  26. package/lib/sql-parser/postgres.js +41 -0
  27. package/lib/update-guard.d.ts +1 -1
  28. package/lib/update-guard.js +16 -13
  29. package/lib/utils.d.ts +0 -7
  30. package/lib/utils.js +0 -76
  31. package/package.json +4 -4
  32. package/src/__tests__/eager-loading/eager-loading-tree.test.ts +393 -0
  33. package/src/__tests__/migrator.test.ts +4 -0
  34. package/src/__tests__/relation-repository/hasone-repository.test.ts +1 -0
  35. package/src/__tests__/repository/aggregation.test.ts +297 -0
  36. package/src/__tests__/repository/count.test.ts +1 -1
  37. package/src/__tests__/repository/find.test.ts +10 -1
  38. package/src/__tests__/repository.test.ts +30 -0
  39. package/src/__tests__/update-guard.test.ts +13 -0
  40. package/src/collection.ts +74 -66
  41. package/src/database.ts +26 -42
  42. package/src/eager-loading/eager-loading-tree.ts +304 -0
  43. package/src/filter-parser.ts +16 -2
  44. package/src/listeners/adjacency-list.ts +1 -3
  45. package/src/listeners/append-child-collection-name-after-repository-find.ts +31 -0
  46. package/src/listeners/index.ts +2 -0
  47. package/src/mock-database.ts +3 -1
  48. package/src/operators/notIn.ts +1 -0
  49. package/src/operators/string.ts +1 -1
  50. package/src/options-parser.ts +5 -0
  51. package/src/query-interface/postgres-query-interface.ts +1 -1
  52. package/src/relation-repository/belongs-to-many-repository.ts +33 -1
  53. package/src/relation-repository/hasmany-repository.ts +17 -0
  54. package/src/relation-repository/multiple-relation-repository.ts +14 -19
  55. package/src/relation-repository/single-relation-repository.ts +13 -15
  56. package/src/repository.ts +79 -36
  57. package/src/sql-parser/postgres.js +25505 -0
  58. package/src/update-guard.ts +21 -16
  59. package/src/utils.ts +0 -61
@@ -1,5 +1,4 @@
1
- import { omit } from 'lodash';
2
- import { MultiAssociationAccessors, Op, Sequelize, Transaction, Transactionable } from 'sequelize';
1
+ import { MultiAssociationAccessors, Sequelize, Transaction, Transactionable } from 'sequelize';
3
2
  import {
4
3
  CommonFindOptions,
5
4
  CountOptions,
@@ -14,7 +13,7 @@ import {
14
13
  import { updateModelByValues } from '../update-associations';
15
14
  import { UpdateGuard } from '../update-guard';
16
15
  import { RelationRepository, transaction } from './relation-repository';
17
- import { handleAppendsQuery } from '../utils';
16
+ import { EagerLoadingTree } from '../eager-loading/eager-loading-tree';
18
17
 
19
18
  export type FindAndCountOptions = CommonFindOptions;
20
19
 
@@ -61,23 +60,19 @@ export abstract class MultipleRelationRepository extends RelationRepository {
61
60
  return [];
62
61
  }
63
62
 
64
- return await handleAppendsQuery({
65
- templateModel: ids[0].row,
66
- queryPromises: findOptions.include.map((include) => {
67
- return sourceModel[getAccessor]({
68
- ...omit(findOptions, ['limit', 'offset']),
69
- include: [include],
70
- where: {
71
- [this.targetKey()]: {
72
- [Op.in]: ids.map((id) => id.pk),
73
- },
74
- },
75
- transaction,
76
- }).then((rows) => {
77
- return { rows, include };
78
- });
79
- }),
63
+ const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
64
+ model: this.targetModel,
65
+ rootAttributes: findOptions.attributes,
66
+ includeOption: findOptions.include,
67
+ rootOrder: findOptions.order,
80
68
  });
69
+
70
+ await eagerLoadingTree.load(
71
+ ids.map((i) => i.pk),
72
+ transaction,
73
+ );
74
+
75
+ return eagerLoadingTree.root.instances;
81
76
  }
82
77
 
83
78
  const data = await sourceModel[getAccessor]({
@@ -3,8 +3,8 @@ import { SingleAssociationAccessors, Transactionable } from 'sequelize';
3
3
  import { Model } from '../model';
4
4
  import { Appends, Except, Fields, Filter, TargetKey, UpdateOptions } from '../repository';
5
5
  import { updateModelByValues } from '../update-associations';
6
- import { handleAppendsQuery } from '../utils';
7
6
  import { RelationRepository, transaction } from './relation-repository';
7
+ import { EagerLoadingTree } from '../eager-loading/eager-loading-tree';
8
8
 
9
9
  export interface SingleRelationFindOption extends Transactionable {
10
10
  fields?: Fields;
@@ -44,7 +44,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
44
44
  });
45
45
  }
46
46
 
47
- async find(options?: SingleRelationFindOption): Promise<Model<any> | null> {
47
+ async find(options?: SingleRelationFindOption): Promise<any> {
48
48
  const transaction = await this.getTransaction(options);
49
49
 
50
50
  const findOptions = this.buildQueryOptions({
@@ -61,23 +61,21 @@ export abstract class SingleRelationRepository extends RelationRepository {
61
61
  ...findOptions,
62
62
  includeIgnoreAttributes: false,
63
63
  transaction,
64
- attributes: [this.targetKey()],
65
- group: `${this.targetModel.name}.${this.targetKey()}`,
64
+ attributes: [this.targetModel.primaryKeyAttribute],
65
+ group: `${this.targetModel.name}.${this.targetModel.primaryKeyAttribute}`,
66
66
  });
67
67
 
68
- const results = await handleAppendsQuery({
69
- templateModel,
70
- queryPromises: findOptions.include.map((include) => {
71
- return sourceModel[getAccessor]({
72
- ...findOptions,
73
- include: [include],
74
- }).then((row) => {
75
- return { rows: [row], include };
76
- });
77
- }),
68
+ if (!templateModel) return null;
69
+
70
+ const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
71
+ model: this.targetModel,
72
+ rootAttributes: findOptions.attributes,
73
+ includeOption: findOptions.include,
78
74
  });
79
75
 
80
- return results[0];
76
+ await eagerLoadingTree.load([templateModel.get(this.targetModel.primaryKeyAttribute)], transaction);
77
+
78
+ return eagerLoadingTree.root.instances[0];
81
79
  }
82
80
 
83
81
  return await sourceModel[getAccessor]({
package/src/repository.ts CHANGED
@@ -1,4 +1,4 @@
1
- import lodash, { omit } from 'lodash';
1
+ import lodash from 'lodash';
2
2
  import {
3
3
  Association,
4
4
  BulkCreateOptions,
@@ -9,6 +9,7 @@ import {
9
9
  FindOptions as SequelizeFindOptions,
10
10
  ModelStatic,
11
11
  Op,
12
+ Sequelize,
12
13
  Transactionable,
13
14
  UpdateOptions as SequelizeUpdateOptions,
14
15
  WhereOperators,
@@ -30,7 +31,7 @@ import { HasOneRepository } from './relation-repository/hasone-repository';
30
31
  import { RelationRepository } from './relation-repository/relation-repository';
31
32
  import { updateAssociations, updateModelByValues } from './update-associations';
32
33
  import { UpdateGuard } from './update-guard';
33
- import { handleAppendsQuery } from './utils';
34
+ import { EagerLoadingTree } from './eager-loading/eager-loading-tree';
34
35
 
35
36
  const debug = require('debug')('noco-database');
36
37
 
@@ -189,10 +190,6 @@ class RelationRepositoryBuilder<R extends RelationRepository> {
189
190
  }
190
191
  }
191
192
 
192
- protected builder() {
193
- return this.builderMap;
194
- }
195
-
196
193
  of(id: string | number): R {
197
194
  if (!this.association) {
198
195
  return;
@@ -200,6 +197,17 @@ class RelationRepositoryBuilder<R extends RelationRepository> {
200
197
  const klass = this.builder()[this.association.associationType];
201
198
  return new klass(this.collection, this.associationName, id);
202
199
  }
200
+
201
+ protected builder() {
202
+ return this.builderMap;
203
+ }
204
+ }
205
+
206
+ export interface AggregateOptions {
207
+ method: 'avg' | 'count' | 'min' | 'max' | 'sum';
208
+ field?: string;
209
+ filter?: Filter;
210
+ distinct?: boolean;
203
211
  }
204
212
 
205
213
  export class Repository<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes>
@@ -259,6 +267,59 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
259
267
  return count;
260
268
  }
261
269
 
270
+ async aggregate(options: AggregateOptions & { optionsTransformer?: (options: any) => any }): Promise<any> {
271
+ const { method, field } = options;
272
+
273
+ const queryOptions = this.buildQueryOptions({
274
+ ...options,
275
+ fields: [],
276
+ });
277
+
278
+ options.optionsTransformer?.(queryOptions);
279
+ const hasAssociationFilter = () => {
280
+ if (queryOptions.include && queryOptions.include.length > 0) {
281
+ const filterInclude = queryOptions.include.filter((include) => {
282
+ return (
283
+ Object.keys(include.where || {}).length > 0 ||
284
+ JSON.stringify(queryOptions?.filter)?.includes(include.association)
285
+ );
286
+ });
287
+ return filterInclude.length > 0;
288
+ }
289
+ return false;
290
+ };
291
+
292
+ if (hasAssociationFilter()) {
293
+ const primaryKeyField = this.model.primaryKeyAttribute;
294
+ const queryInterface = this.database.sequelize.getQueryInterface();
295
+
296
+ const findOptions = {
297
+ ...queryOptions,
298
+ raw: true,
299
+ includeIgnoreAttributes: false,
300
+ attributes: [
301
+ [
302
+ Sequelize.literal(
303
+ `DISTINCT ${queryInterface.quoteIdentifiers(`${this.collection.name}.${primaryKeyField}`)}`,
304
+ ),
305
+ primaryKeyField,
306
+ ],
307
+ ],
308
+ };
309
+
310
+ const ids = await this.model.findAll(findOptions);
311
+
312
+ return await this.model.aggregate(field, method, {
313
+ ...lodash.omit(queryOptions, ['where', 'include']),
314
+ where: {
315
+ [primaryKeyField]: ids.map((node) => node[primaryKeyField]),
316
+ },
317
+ });
318
+ }
319
+
320
+ return await this.model.aggregate(field, method, queryOptions);
321
+ }
322
+
262
323
  /**
263
324
  * find
264
325
  * @param options
@@ -300,38 +361,20 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
300
361
  return [];
301
362
  }
302
363
 
303
- // find template model
304
- const templateModel = await model.findOne({
305
- ...opts,
306
- includeIgnoreAttributes: false,
307
- attributes: [primaryKeyField],
308
- group: `${model.name}.${primaryKeyField}`,
309
- transaction,
310
- limit: 1,
311
- offset: 0,
312
- } as any);
364
+ // find all rows
365
+ const eagerLoadingTree = EagerLoadingTree.buildFromSequelizeOptions({
366
+ model,
367
+ rootAttributes: opts.attributes,
368
+ includeOption: opts.include,
369
+ rootOrder: opts.order,
370
+ });
313
371
 
314
- const where = {
315
- [primaryKeyField]: {
316
- [Op.in]: ids.map((id) => id['pk']),
317
- },
318
- };
372
+ await eagerLoadingTree.load(
373
+ ids.map((i) => i.pk),
374
+ transaction,
375
+ );
319
376
 
320
- rows = await handleAppendsQuery({
321
- queryPromises: opts.include.map((include) => {
322
- const options = {
323
- ...omit(opts, ['limit', 'offset', 'filter']),
324
- include: include,
325
- where,
326
- transaction,
327
- };
328
-
329
- return model.findAll(options).then((rows) => {
330
- return { rows, include };
331
- });
332
- }),
333
- templateModel: templateModel,
334
- });
377
+ rows = eagerLoadingTree.root.instances;
335
378
  } else {
336
379
  rows = await model.findAll({
337
380
  ...opts,