@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.
- package/lib/collection.d.ts +9 -9
- package/lib/collection.js +100 -96
- package/lib/database.d.ts +4 -4
- package/lib/database.js +25 -53
- package/lib/eager-loading/eager-loading-tree.d.ts +23 -0
- package/lib/eager-loading/eager-loading-tree.js +338 -0
- package/lib/filter-parser.d.ts +1 -7
- package/lib/filter-parser.js +27 -7
- package/lib/listeners/append-child-collection-name-after-repository-find.d.ts +5 -0
- package/lib/listeners/append-child-collection-name-after-repository-find.js +40 -0
- package/lib/listeners/index.js +2 -0
- package/lib/mock-database.js +3 -1
- package/lib/operators/string.js +1 -1
- package/lib/options-parser.js +4 -0
- package/lib/query-interface/postgres-query-interface.js +2 -2
- package/lib/relation-repository/belongs-to-many-repository.d.ts +2 -1
- package/lib/relation-repository/belongs-to-many-repository.js +58 -37
- package/lib/relation-repository/hasmany-repository.d.ts +2 -1
- package/lib/relation-repository/hasmany-repository.js +31 -16
- package/lib/relation-repository/multiple-relation-repository.js +8 -26
- package/lib/relation-repository/relation-repository.d.ts +1 -7
- package/lib/relation-repository/single-relation-repository.d.ts +1 -1
- package/lib/relation-repository/single-relation-repository.js +10 -16
- package/lib/repository.d.ts +11 -8
- package/lib/repository.js +104 -89
- package/lib/sql-parser/postgres.js +41 -0
- package/lib/update-guard.d.ts +1 -1
- package/lib/update-guard.js +16 -13
- package/lib/utils.d.ts +0 -7
- package/lib/utils.js +0 -76
- package/package.json +4 -4
- package/src/__tests__/eager-loading/eager-loading-tree.test.ts +393 -0
- package/src/__tests__/migrator.test.ts +4 -0
- package/src/__tests__/relation-repository/hasone-repository.test.ts +1 -0
- package/src/__tests__/repository/aggregation.test.ts +297 -0
- package/src/__tests__/repository/count.test.ts +1 -1
- package/src/__tests__/repository/find.test.ts +10 -1
- package/src/__tests__/repository.test.ts +30 -0
- package/src/__tests__/update-guard.test.ts +13 -0
- package/src/collection.ts +74 -66
- package/src/database.ts +26 -42
- package/src/eager-loading/eager-loading-tree.ts +304 -0
- package/src/filter-parser.ts +16 -2
- package/src/listeners/adjacency-list.ts +1 -3
- package/src/listeners/append-child-collection-name-after-repository-find.ts +31 -0
- package/src/listeners/index.ts +2 -0
- package/src/mock-database.ts +3 -1
- package/src/operators/notIn.ts +1 -0
- package/src/operators/string.ts +1 -1
- package/src/options-parser.ts +5 -0
- package/src/query-interface/postgres-query-interface.ts +1 -1
- package/src/relation-repository/belongs-to-many-repository.ts +33 -1
- package/src/relation-repository/hasmany-repository.ts +17 -0
- package/src/relation-repository/multiple-relation-repository.ts +14 -19
- package/src/relation-repository/single-relation-repository.ts +13 -15
- package/src/repository.ts +79 -36
- package/src/sql-parser/postgres.js +25505 -0
- package/src/update-guard.ts +21 -16
- package/src/utils.ts +0 -61
package/src/database.ts
CHANGED
|
@@ -62,13 +62,13 @@ import {
|
|
|
62
62
|
} from './types';
|
|
63
63
|
import { patchSequelizeQueryInterface, snakeCase } from './utils';
|
|
64
64
|
|
|
65
|
+
import { Logger } from '@nocobase/logger';
|
|
66
|
+
import { CollectionGroupManager } from './collection-group-manager';
|
|
65
67
|
import DatabaseUtils from './database-utils';
|
|
66
68
|
import { registerBuiltInListeners } from './listeners';
|
|
67
|
-
import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
|
|
68
|
-
import buildQueryInterface from './query-interface/query-interface-builder';
|
|
69
69
|
import QueryInterface from './query-interface/query-interface';
|
|
70
|
-
import
|
|
71
|
-
import {
|
|
70
|
+
import buildQueryInterface from './query-interface/query-interface-builder';
|
|
71
|
+
import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
|
|
72
72
|
import { ViewCollection } from './view-collection';
|
|
73
73
|
|
|
74
74
|
export type MergeOptions = merge.Options;
|
|
@@ -188,6 +188,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
188
188
|
logger: Logger;
|
|
189
189
|
|
|
190
190
|
collectionGroupManager = new CollectionGroupManager(this);
|
|
191
|
+
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
|
191
192
|
|
|
192
193
|
constructor(options: DatabaseOptions) {
|
|
193
194
|
super();
|
|
@@ -275,6 +276,12 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
275
276
|
}),
|
|
276
277
|
});
|
|
277
278
|
|
|
279
|
+
this.sequelize.beforeDefine((model, opts) => {
|
|
280
|
+
if (this.options.tablePrefix) {
|
|
281
|
+
opts.tableName = `${this.options.tablePrefix}${opts.tableName || opts.modelName || opts.name.plural}`;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
278
285
|
this.collection({
|
|
279
286
|
name: 'migrations',
|
|
280
287
|
autoGenId: false,
|
|
@@ -284,12 +291,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
284
291
|
fields: [{ type: 'string', name: 'name' }],
|
|
285
292
|
});
|
|
286
293
|
|
|
287
|
-
this.sequelize.beforeDefine((model, opts) => {
|
|
288
|
-
if (this.options.tablePrefix) {
|
|
289
|
-
opts.tableName = `${this.options.tablePrefix}${opts.tableName || opts.modelName || opts.name.plural}`;
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
294
|
this.initListener();
|
|
294
295
|
patchSequelizeQueryInterface(this);
|
|
295
296
|
}
|
|
@@ -387,36 +388,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
387
388
|
}
|
|
388
389
|
});
|
|
389
390
|
|
|
390
|
-
this.on('afterRepositoryFind', ({ findOptions, dataCollection, data }) => {
|
|
391
|
-
if (dataCollection.isParent()) {
|
|
392
|
-
for (const row of data) {
|
|
393
|
-
const rowCollection = this.tableNameCollectionMap.get(
|
|
394
|
-
findOptions.raw
|
|
395
|
-
? `${row['__schemaName']}.${row['__tableName']}`
|
|
396
|
-
: `${row.get('__schemaName')}.${row.get('__tableName')}`,
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
if (!rowCollection) {
|
|
400
|
-
this.logger.warn(
|
|
401
|
-
`Can not find collection by table name ${JSON.stringify(row)}, current collections: ${Array.from(
|
|
402
|
-
this.tableNameCollectionMap.keys(),
|
|
403
|
-
).join(', ')}`,
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const rowCollectionName = rowCollection.name;
|
|
410
|
-
|
|
411
|
-
findOptions.raw
|
|
412
|
-
? (row['__collection'] = rowCollectionName)
|
|
413
|
-
: row.set('__collection', rowCollectionName, {
|
|
414
|
-
raw: true,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
391
|
registerBuiltInListeners(this);
|
|
421
392
|
}
|
|
422
393
|
|
|
@@ -565,7 +536,9 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
565
536
|
}
|
|
566
537
|
|
|
567
538
|
getRepository<R extends Repository>(name: string): R;
|
|
539
|
+
|
|
568
540
|
getRepository<R extends RelationRepository>(name: string, relationId: string | number): R;
|
|
541
|
+
|
|
569
542
|
getRepository<R extends ArrayFieldRepository>(name: string, relationId: string | number): R;
|
|
570
543
|
|
|
571
544
|
getRepository<R extends RelationRepository>(name: string, relationId?: string | number): Repository | R {
|
|
@@ -779,21 +752,34 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
779
752
|
}
|
|
780
753
|
|
|
781
754
|
on(event: EventType, listener: any): this;
|
|
755
|
+
|
|
782
756
|
on(event: ModelValidateEventTypes, listener: SyncListener): this;
|
|
757
|
+
|
|
783
758
|
on(event: ModelValidateEventTypes, listener: ValidateListener): this;
|
|
759
|
+
|
|
784
760
|
on(event: ModelCreateEventTypes, listener: CreateListener): this;
|
|
761
|
+
|
|
785
762
|
on(event: ModelUpdateEventTypes, listener: UpdateListener): this;
|
|
763
|
+
|
|
786
764
|
on(event: ModelSaveEventTypes, listener: SaveListener): this;
|
|
765
|
+
|
|
787
766
|
on(event: ModelDestroyEventTypes, listener: DestroyListener): this;
|
|
767
|
+
|
|
788
768
|
on(event: ModelCreateWithAssociationsEventTypes, listener: CreateWithAssociationsListener): this;
|
|
769
|
+
|
|
789
770
|
on(event: ModelUpdateWithAssociationsEventTypes, listener: UpdateWithAssociationsListener): this;
|
|
771
|
+
|
|
790
772
|
on(event: ModelSaveWithAssociationsEventTypes, listener: SaveWithAssociationsListener): this;
|
|
773
|
+
|
|
791
774
|
on(event: DatabaseBeforeDefineCollectionEventType, listener: BeforeDefineCollectionListener): this;
|
|
775
|
+
|
|
792
776
|
on(event: DatabaseAfterDefineCollectionEventType, listener: AfterDefineCollectionListener): this;
|
|
777
|
+
|
|
793
778
|
on(
|
|
794
779
|
event: DatabaseBeforeRemoveCollectionEventType | DatabaseAfterRemoveCollectionEventType,
|
|
795
780
|
listener: RemoveCollectionListener,
|
|
796
781
|
): this;
|
|
782
|
+
|
|
797
783
|
on(event: EventType, listener: any): this {
|
|
798
784
|
// NOTE: to match if event is a sequelize or model type
|
|
799
785
|
const type = this.modelHook.match(event);
|
|
@@ -844,8 +830,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
844
830
|
|
|
845
831
|
return result;
|
|
846
832
|
}
|
|
847
|
-
|
|
848
|
-
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
|
849
833
|
}
|
|
850
834
|
|
|
851
835
|
export function extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions) {
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Association, Includeable, Model, ModelStatic, Transaction } from 'sequelize';
|
|
2
|
+
import lodash from 'lodash';
|
|
3
|
+
|
|
4
|
+
interface EagerLoadingNode {
|
|
5
|
+
model: ModelStatic<any>;
|
|
6
|
+
association: Association;
|
|
7
|
+
attributes: Array<string>;
|
|
8
|
+
rawAttributes: Array<string>;
|
|
9
|
+
children: Array<EagerLoadingNode>;
|
|
10
|
+
parent?: EagerLoadingNode;
|
|
11
|
+
instances?: Array<Model>;
|
|
12
|
+
order?: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class EagerLoadingTree {
|
|
16
|
+
public root: EagerLoadingNode;
|
|
17
|
+
|
|
18
|
+
constructor(root: EagerLoadingNode) {
|
|
19
|
+
this.root = root;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static buildFromSequelizeOptions(options: {
|
|
23
|
+
model: ModelStatic<any>;
|
|
24
|
+
rootAttributes: Array<string>;
|
|
25
|
+
rootOrder?: any;
|
|
26
|
+
includeOption: Includeable | Includeable[];
|
|
27
|
+
}): EagerLoadingTree {
|
|
28
|
+
const { model, rootAttributes, includeOption } = options;
|
|
29
|
+
|
|
30
|
+
const root = {
|
|
31
|
+
model,
|
|
32
|
+
association: null,
|
|
33
|
+
rawAttributes: lodash.cloneDeep(rootAttributes),
|
|
34
|
+
attributes: lodash.cloneDeep(rootAttributes),
|
|
35
|
+
order: options.rootOrder,
|
|
36
|
+
children: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const pushAttribute = (node, attribute) => {
|
|
40
|
+
if (lodash.isArray(node.attributes) && !node.attributes.includes(attribute)) {
|
|
41
|
+
node.attributes.push(attribute);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const traverseIncludeOption = (includeOption, eagerLoadingTreeParent) => {
|
|
46
|
+
const includeOptions = lodash.castArray(includeOption);
|
|
47
|
+
|
|
48
|
+
if (includeOption.length > 0) {
|
|
49
|
+
const modelPrimaryKey = eagerLoadingTreeParent.model.primaryKeyAttribute;
|
|
50
|
+
pushAttribute(eagerLoadingTreeParent, modelPrimaryKey);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const include of includeOptions) {
|
|
54
|
+
// skip fromFilter include option
|
|
55
|
+
if (include.fromFilter) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const association = eagerLoadingTreeParent.model.associations[include.association];
|
|
60
|
+
const associationType = association.associationType;
|
|
61
|
+
|
|
62
|
+
const child = {
|
|
63
|
+
model: association.target,
|
|
64
|
+
association,
|
|
65
|
+
rawAttributes: lodash.cloneDeep(include.attributes),
|
|
66
|
+
attributes: lodash.cloneDeep(include.attributes),
|
|
67
|
+
parent: eagerLoadingTreeParent,
|
|
68
|
+
children: [],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (associationType == 'HasOne' || associationType == 'HasMany') {
|
|
72
|
+
const { sourceKey, foreignKey } = association;
|
|
73
|
+
|
|
74
|
+
pushAttribute(eagerLoadingTreeParent, sourceKey);
|
|
75
|
+
pushAttribute(child, foreignKey);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (associationType == 'BelongsTo') {
|
|
79
|
+
const { targetKey, foreignKey } = association;
|
|
80
|
+
|
|
81
|
+
pushAttribute(eagerLoadingTreeParent, foreignKey);
|
|
82
|
+
pushAttribute(child, targetKey);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (associationType == 'BelongsToMany') {
|
|
86
|
+
const { sourceKey } = association;
|
|
87
|
+
pushAttribute(eagerLoadingTreeParent, sourceKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
eagerLoadingTreeParent.children.push(child);
|
|
91
|
+
|
|
92
|
+
if (include.include) {
|
|
93
|
+
traverseIncludeOption(include.include, child);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
traverseIncludeOption(includeOption, root);
|
|
99
|
+
|
|
100
|
+
return new EagerLoadingTree(root);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async load(pks: Array<string | number>, transaction?: Transaction) {
|
|
104
|
+
const result = {};
|
|
105
|
+
|
|
106
|
+
const loadRecursive = async (node, ids) => {
|
|
107
|
+
const modelPrimaryKey = node.model.primaryKeyAttribute;
|
|
108
|
+
|
|
109
|
+
let instances = [];
|
|
110
|
+
|
|
111
|
+
// load instances from database
|
|
112
|
+
if (!node.parent) {
|
|
113
|
+
const findOptions = {
|
|
114
|
+
where: { [modelPrimaryKey]: ids },
|
|
115
|
+
attributes: node.attributes,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (node.order) {
|
|
119
|
+
findOptions['order'] = node.order;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
instances = await node.model.findAll({
|
|
123
|
+
...findOptions,
|
|
124
|
+
transaction,
|
|
125
|
+
});
|
|
126
|
+
} else if (ids.length > 0) {
|
|
127
|
+
const association = node.association;
|
|
128
|
+
const associationType = association.associationType;
|
|
129
|
+
|
|
130
|
+
if (associationType == 'HasOne' || associationType == 'HasMany') {
|
|
131
|
+
const foreignKey = association.foreignKey;
|
|
132
|
+
const foreignKeyValues = node.parent.instances.map((instance) => instance.get(association.sourceKey));
|
|
133
|
+
|
|
134
|
+
const findOptions = {
|
|
135
|
+
where: { [foreignKey]: foreignKeyValues },
|
|
136
|
+
attributes: node.attributes,
|
|
137
|
+
transaction,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
instances = await node.model.findAll(findOptions);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (associationType == 'BelongsTo') {
|
|
144
|
+
const foreignKey = association.foreignKey;
|
|
145
|
+
|
|
146
|
+
const parentInstancesForeignKeyValues = node.parent.instances.map((instance) => instance.get(foreignKey));
|
|
147
|
+
|
|
148
|
+
instances = await node.model.findAll({
|
|
149
|
+
transaction,
|
|
150
|
+
where: {
|
|
151
|
+
[association.targetKey]: parentInstancesForeignKeyValues,
|
|
152
|
+
},
|
|
153
|
+
attributes: node.attributes,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (associationType == 'BelongsToMany') {
|
|
158
|
+
const foreignKeyValues = node.parent.instances.map((instance) => instance.get(association.sourceKey));
|
|
159
|
+
|
|
160
|
+
instances = await node.model.findAll({
|
|
161
|
+
transaction,
|
|
162
|
+
attributes: node.attributes,
|
|
163
|
+
include: [
|
|
164
|
+
{
|
|
165
|
+
association: association.oneFromTarget,
|
|
166
|
+
where: {
|
|
167
|
+
[association.foreignKey]: foreignKeyValues,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
node.instances = instances;
|
|
176
|
+
|
|
177
|
+
for (const child of node.children) {
|
|
178
|
+
const nodeIds = instances.map((instance) => instance.get(modelPrimaryKey));
|
|
179
|
+
|
|
180
|
+
await loadRecursive(child, nodeIds);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// merge instances to parent
|
|
184
|
+
if (!node.parent) {
|
|
185
|
+
return;
|
|
186
|
+
} else {
|
|
187
|
+
const association = node.association;
|
|
188
|
+
const associationType = association.associationType;
|
|
189
|
+
|
|
190
|
+
const setParentAccessor = (parentInstance) => {
|
|
191
|
+
const key = association.as;
|
|
192
|
+
|
|
193
|
+
const children = parentInstance.getDataValue(association.as);
|
|
194
|
+
|
|
195
|
+
if (association.isSingleAssociation) {
|
|
196
|
+
const isEmpty = !children;
|
|
197
|
+
parentInstance[key] = parentInstance.dataValues[key] = isEmpty ? null : children;
|
|
198
|
+
} else {
|
|
199
|
+
const isEmpty = !children || children.length == 0;
|
|
200
|
+
parentInstance[key] = parentInstance.dataValues[key] = isEmpty ? [] : children;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (associationType == 'HasMany' || associationType == 'HasOne') {
|
|
205
|
+
const foreignKey = association.foreignKey;
|
|
206
|
+
const sourceKey = association.sourceKey;
|
|
207
|
+
|
|
208
|
+
for (const instance of node.instances) {
|
|
209
|
+
const parentInstance = node.parent.instances.find(
|
|
210
|
+
(parentInstance) => parentInstance.get(sourceKey) == instance.get(foreignKey),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (parentInstance) {
|
|
214
|
+
if (associationType == 'HasMany') {
|
|
215
|
+
const children = parentInstance.getDataValue(association.as);
|
|
216
|
+
if (!children) {
|
|
217
|
+
parentInstance.setDataValue(association.as, [instance]);
|
|
218
|
+
} else {
|
|
219
|
+
children.push(instance);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (associationType == 'HasOne') {
|
|
224
|
+
parentInstance.setDataValue(association.as, instance);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (associationType == 'BelongsTo') {
|
|
231
|
+
const foreignKey = association.foreignKey;
|
|
232
|
+
const targetKey = association.targetKey;
|
|
233
|
+
|
|
234
|
+
for (const instance of node.instances) {
|
|
235
|
+
const parentInstances = node.parent.instances.filter(
|
|
236
|
+
(parentInstance) => parentInstance.get(foreignKey) == instance.get(targetKey),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
for (const parentInstance of parentInstances) {
|
|
240
|
+
parentInstance.setDataValue(association.as, instance);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (associationType == 'BelongsToMany') {
|
|
246
|
+
const sourceKey = association.sourceKey;
|
|
247
|
+
const foreignKey = association.foreignKey;
|
|
248
|
+
|
|
249
|
+
const oneFromTarget = association.oneFromTarget;
|
|
250
|
+
|
|
251
|
+
for (const instance of node.instances) {
|
|
252
|
+
const parentInstance = node.parent.instances.find(
|
|
253
|
+
(parentInstance) => parentInstance.get(sourceKey) == instance[oneFromTarget.as].get(foreignKey),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (parentInstance) {
|
|
257
|
+
const children = parentInstance.getDataValue(association.as);
|
|
258
|
+
|
|
259
|
+
if (!children) {
|
|
260
|
+
parentInstance.setDataValue(association.as, [instance]);
|
|
261
|
+
} else {
|
|
262
|
+
children.push(instance);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for (const parent of node.parent.instances) {
|
|
269
|
+
setParentAccessor(parent);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
await loadRecursive(this.root, pks);
|
|
275
|
+
|
|
276
|
+
const setInstanceAttributes = (node) => {
|
|
277
|
+
const nodeRawAttributes = node.rawAttributes;
|
|
278
|
+
|
|
279
|
+
if (!lodash.isArray(nodeRawAttributes)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const nodeChildrenAs = node.children.map((child) => child.association.as);
|
|
284
|
+
const includeAttributes = [...nodeRawAttributes, ...nodeChildrenAs];
|
|
285
|
+
|
|
286
|
+
for (const instance of node.instances) {
|
|
287
|
+
const attributes = lodash.pick(instance.dataValues, includeAttributes);
|
|
288
|
+
instance.dataValues = attributes;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// traverse tree and set instance attributes
|
|
293
|
+
const traverse = (node) => {
|
|
294
|
+
setInstanceAttributes(node);
|
|
295
|
+
|
|
296
|
+
for (const child of node.children) {
|
|
297
|
+
traverse(child);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
traverse(this.root);
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
}
|
package/src/filter-parser.ts
CHANGED
|
@@ -56,7 +56,7 @@ export default class FilterParser {
|
|
|
56
56
|
return filter;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
toSequelizeParams() {
|
|
59
|
+
toSequelizeParams(): any {
|
|
60
60
|
debug('filter %o', this.filter);
|
|
61
61
|
|
|
62
62
|
if (!this.filter) {
|
|
@@ -230,7 +230,21 @@ export default class FilterParser {
|
|
|
230
230
|
});
|
|
231
231
|
};
|
|
232
232
|
debug('where %o, include %o', where, include);
|
|
233
|
-
|
|
233
|
+
const results = { where, include: toInclude(include) };
|
|
234
|
+
|
|
235
|
+
//traverse filter include, set fromFiler to true
|
|
236
|
+
const traverseInclude = (include) => {
|
|
237
|
+
for (const item of include) {
|
|
238
|
+
if (item.include) {
|
|
239
|
+
traverseInclude(item.include);
|
|
240
|
+
}
|
|
241
|
+
item.fromFilter = true;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
traverseInclude(results.include);
|
|
246
|
+
|
|
247
|
+
return results;
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
private getFieldNameFromQueryPath(queryPath: string) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Collection, CollectionOptions } from '../collection';
|
|
3
|
-
import { Model } from '../model';
|
|
1
|
+
import { CollectionOptions } from '../collection';
|
|
4
2
|
|
|
5
3
|
export const beforeDefineAdjacencyListCollection = (options: CollectionOptions) => {
|
|
6
4
|
if (!options.tree) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const appendChildCollectionNameAfterRepositoryFind = (db) => {
|
|
2
|
+
return ({ findOptions, dataCollection, data }) => {
|
|
3
|
+
if (dataCollection.isParent()) {
|
|
4
|
+
for (const row of data) {
|
|
5
|
+
const rowCollection = db.tableNameCollectionMap.get(
|
|
6
|
+
findOptions.raw
|
|
7
|
+
? `${row['__schemaName']}.${row['__tableName']}`
|
|
8
|
+
: `${row.get('__schemaName')}.${row.get('__tableName')}`,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
if (!rowCollection) {
|
|
12
|
+
db.logger.warn(
|
|
13
|
+
`Can not find collection by table name ${JSON.stringify(row)}, current collections: ${Array.from(
|
|
14
|
+
db.tableNameCollectionMap.keys(),
|
|
15
|
+
).join(', ')}`,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rowCollectionName = rowCollection.name;
|
|
22
|
+
|
|
23
|
+
findOptions.raw
|
|
24
|
+
? (row['__collection'] = rowCollectionName)
|
|
25
|
+
: row.set('__collection', rowCollectionName, {
|
|
26
|
+
raw: true,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
package/src/listeners/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Database } from '../database';
|
|
2
2
|
import { beforeDefineAdjacencyListCollection } from './adjacency-list';
|
|
3
|
+
import { appendChildCollectionNameAfterRepositoryFind } from './append-child-collection-name-after-repository-find';
|
|
3
4
|
|
|
4
5
|
export const registerBuiltInListeners = (db: Database) => {
|
|
5
6
|
db.on('beforeDefineCollection', beforeDefineAdjacencyListCollection);
|
|
7
|
+
db.on('afterRepositoryFind', appendChildCollectionNameAfterRepositoryFind(db));
|
|
6
8
|
};
|
package/src/mock-database.ts
CHANGED
|
@@ -45,7 +45,9 @@ export function getConfigByEnv() {
|
|
|
45
45
|
|
|
46
46
|
function customLogger(queryString, queryObject) {
|
|
47
47
|
console.log(queryString); // outputs a string
|
|
48
|
-
|
|
48
|
+
if (queryObject.bind) {
|
|
49
|
+
console.log(queryObject.bind); // outputs an array
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export function mockDatabase(options: IDatabaseOptions = {}): MockDatabase {
|
package/src/operators/notIn.ts
CHANGED
package/src/operators/string.ts
CHANGED
package/src/options-parser.ts
CHANGED
|
@@ -269,6 +269,11 @@ export class OptionsParser {
|
|
|
269
269
|
(include) => include['association'] == appendAssociation,
|
|
270
270
|
);
|
|
271
271
|
|
|
272
|
+
// if include from filter, remove fromFilter attribute
|
|
273
|
+
if (existIncludeIndex != -1) {
|
|
274
|
+
delete queryParams['include'][existIncludeIndex]['fromFilter'];
|
|
275
|
+
}
|
|
276
|
+
|
|
272
277
|
// if association not exist, create it
|
|
273
278
|
if (existIncludeIndex == -1) {
|
|
274
279
|
// association not exists
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import QueryInterface from './query-interface';
|
|
2
2
|
import { Collection } from '../collection';
|
|
3
|
-
import sqlParser from '../sql-parser';
|
|
3
|
+
import sqlParser from '../sql-parser/postgres';
|
|
4
4
|
|
|
5
5
|
export default class PostgresQueryInterface extends QueryInterface {
|
|
6
6
|
constructor(db) {
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import lodash from 'lodash';
|
|
2
2
|
import { BelongsToMany, Op, Transaction } from 'sequelize';
|
|
3
3
|
import { Model } from '../model';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AggregateOptions,
|
|
6
|
+
CreateOptions,
|
|
7
|
+
DestroyOptions,
|
|
8
|
+
FindOneOptions,
|
|
9
|
+
FindOptions,
|
|
10
|
+
TargetKey,
|
|
11
|
+
UpdateOptions,
|
|
12
|
+
} from '../repository';
|
|
5
13
|
import { updateThroughTableValue } from '../update-associations';
|
|
6
14
|
import { FindAndCountOptions, MultipleRelationRepository } from './multiple-relation-repository';
|
|
7
15
|
import { transaction } from './relation-repository';
|
|
@@ -38,6 +46,30 @@ interface IBelongsToManyRepository<M extends Model> {
|
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export class BelongsToManyRepository extends MultipleRelationRepository implements IBelongsToManyRepository<any> {
|
|
49
|
+
async aggregate(options: AggregateOptions) {
|
|
50
|
+
const targetRepository = this.targetCollection.repository;
|
|
51
|
+
|
|
52
|
+
const sourceModel = await this.getSourceModel();
|
|
53
|
+
|
|
54
|
+
const association = this.association as any;
|
|
55
|
+
|
|
56
|
+
return await targetRepository.aggregate({
|
|
57
|
+
...options,
|
|
58
|
+
optionsTransformer: (modelOptions) => {
|
|
59
|
+
modelOptions.include = modelOptions.include || [];
|
|
60
|
+
const throughWhere = {};
|
|
61
|
+
throughWhere[association.foreignKey] = sourceModel.get(association.sourceKey);
|
|
62
|
+
|
|
63
|
+
modelOptions.include.push({
|
|
64
|
+
association: association.oneFromTarget,
|
|
65
|
+
required: true,
|
|
66
|
+
attributes: [],
|
|
67
|
+
where: throughWhere,
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
@transaction()
|
|
42
74
|
async create(options?: CreateBelongsToManyOptions): Promise<any> {
|
|
43
75
|
if (Array.isArray(options.values)) {
|
|
@@ -2,6 +2,7 @@ import { omit } from 'lodash';
|
|
|
2
2
|
import { HasMany, Op } from 'sequelize';
|
|
3
3
|
import { Model } from '../model';
|
|
4
4
|
import {
|
|
5
|
+
AggregateOptions,
|
|
5
6
|
CreateOptions,
|
|
6
7
|
DestroyOptions,
|
|
7
8
|
FindOneOptions,
|
|
@@ -53,6 +54,22 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
|
|
|
53
54
|
return await targetRepository.find(findOptions);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
async aggregate(options: AggregateOptions) {
|
|
58
|
+
const targetRepository = this.targetCollection.repository;
|
|
59
|
+
const addFilter = {
|
|
60
|
+
[this.association.foreignKey]: this.sourceKeyValue,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const aggOptions = {
|
|
64
|
+
...options,
|
|
65
|
+
filter: {
|
|
66
|
+
$and: [options.filter || {}, addFilter],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return await targetRepository.aggregate(aggOptions);
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
@transaction((args, transaction) => {
|
|
57
74
|
return {
|
|
58
75
|
filterByTk: args[0],
|