@nocobase/database 0.9.2-alpha.4 → 0.9.3-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.js +4 -0
- package/lib/fields/field.js +1 -0
- package/lib/listeners/adjacency-list.d.ts +1 -2
- package/lib/listeners/adjacency-list.js +2 -71
- package/lib/listeners/index.js +0 -1
- package/lib/operators/array.js +7 -4
- package/lib/options-parser.js +10 -0
- package/lib/repository.js +5 -4
- package/lib/tree-repository/adjacency-list-repository.d.ts +9 -0
- package/lib/tree-repository/adjacency-list-repository.js +165 -0
- package/package.json +4 -4
- package/src/__tests__/collection.test.ts +19 -0
- package/src/__tests__/repository/find.test.ts +258 -1
- package/src/__tests__/tree.test.ts +266 -3
- package/src/collection.ts +6 -0
- package/src/fields/field.ts +4 -0
- package/src/listeners/adjacency-list.ts +0 -41
- package/src/listeners/index.ts +1 -2
- package/src/operators/array.ts +8 -4
- package/src/options-parser.ts +12 -0
- package/src/repository.ts +6 -4
- package/src/tree-repository/adjacency-list-repository.ts +159 -0
package/src/repository.ts
CHANGED
|
@@ -263,7 +263,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
263
263
|
* find
|
|
264
264
|
* @param options
|
|
265
265
|
*/
|
|
266
|
-
async find(options
|
|
266
|
+
async find(options: FindOptions = {}) {
|
|
267
267
|
const model = this.collection.model;
|
|
268
268
|
const transaction = await this.getTransaction(options);
|
|
269
269
|
|
|
@@ -278,6 +278,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
278
278
|
// @ts-ignore
|
|
279
279
|
const primaryKeyField = model.primaryKeyField || model.primaryKeyAttribute;
|
|
280
280
|
|
|
281
|
+
// find all ids
|
|
281
282
|
const ids = (
|
|
282
283
|
await model.findAll({
|
|
283
284
|
...opts,
|
|
@@ -299,6 +300,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
299
300
|
return [];
|
|
300
301
|
}
|
|
301
302
|
|
|
303
|
+
// find template model
|
|
302
304
|
const templateModel = await model.findOne({
|
|
303
305
|
...opts,
|
|
304
306
|
includeIgnoreAttributes: false,
|
|
@@ -318,7 +320,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
318
320
|
rows = await handleAppendsQuery({
|
|
319
321
|
queryPromises: opts.include.map((include) => {
|
|
320
322
|
const options = {
|
|
321
|
-
...omit(opts, ['limit', 'offset']),
|
|
323
|
+
...omit(opts, ['limit', 'offset', 'filter']),
|
|
322
324
|
include: include,
|
|
323
325
|
where,
|
|
324
326
|
transaction,
|
|
@@ -351,10 +353,9 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
351
353
|
* @param options
|
|
352
354
|
*/
|
|
353
355
|
async findAndCount(options?: FindAndCountOptions): Promise<[Model[], number]> {
|
|
354
|
-
const transaction = await this.getTransaction(options);
|
|
355
356
|
options = {
|
|
356
357
|
...options,
|
|
357
|
-
transaction,
|
|
358
|
+
transaction: await this.getTransaction(options),
|
|
358
359
|
};
|
|
359
360
|
|
|
360
361
|
const count = await this.count(options);
|
|
@@ -472,6 +473,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
472
473
|
records: options.values,
|
|
473
474
|
});
|
|
474
475
|
}
|
|
476
|
+
|
|
475
477
|
const transaction = await this.getTransaction(options);
|
|
476
478
|
|
|
477
479
|
const guard = UpdateGuard.fromOptions(this.model, { ...options, underscored: this.collection.options.underscored });
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { FindOptions, Repository } from '../repository';
|
|
2
|
+
import lodash from 'lodash';
|
|
3
|
+
|
|
4
|
+
export class AdjacencyListRepository extends Repository {
|
|
5
|
+
async update(options): Promise<any> {
|
|
6
|
+
return super.update({
|
|
7
|
+
...(options || {}),
|
|
8
|
+
addIndex: false,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async find(options: FindOptions & { addIndex?: boolean } = {}): Promise<any> {
|
|
13
|
+
if (options.raw || !options.tree) {
|
|
14
|
+
return await super.find(options);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const collection = this.collection;
|
|
18
|
+
const primaryKey = collection.model.primaryKeyAttribute;
|
|
19
|
+
|
|
20
|
+
if (options.fields && !options.fields.includes(primaryKey)) {
|
|
21
|
+
options.fields.push(primaryKey);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parentNodes = await super.find(options);
|
|
25
|
+
if (parentNodes.length === 0) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { treeParentField } = collection;
|
|
30
|
+
const foreignKey = treeParentField.options.foreignKey;
|
|
31
|
+
|
|
32
|
+
const childrenKey = collection.treeChildrenField?.name ?? 'children';
|
|
33
|
+
|
|
34
|
+
const parentIds = parentNodes.map((node) => node[primaryKey]);
|
|
35
|
+
|
|
36
|
+
if (parentIds.length == 0) {
|
|
37
|
+
this.database.logger.warn('parentIds is empty');
|
|
38
|
+
return parentNodes;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const sql = this.querySQL(parentIds, collection);
|
|
42
|
+
|
|
43
|
+
const childNodes = await this.database.sequelize.query(sql, {
|
|
44
|
+
type: 'SELECT',
|
|
45
|
+
transaction: options.transaction,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const childIds = childNodes.map((node) => node[primaryKey]);
|
|
49
|
+
|
|
50
|
+
const findChildrenOptions = {
|
|
51
|
+
...lodash.omit(options, ['limit', 'offset', 'filterByTk']),
|
|
52
|
+
filter: {
|
|
53
|
+
[primaryKey]: childIds,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (findChildrenOptions.fields) {
|
|
58
|
+
[primaryKey, foreignKey].forEach((field) => {
|
|
59
|
+
if (!findChildrenOptions.fields.includes(field)) {
|
|
60
|
+
findChildrenOptions.fields.push(field);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const childInstances = await super.find(findChildrenOptions);
|
|
66
|
+
|
|
67
|
+
const nodeMap = {};
|
|
68
|
+
|
|
69
|
+
childInstances.forEach((node) => {
|
|
70
|
+
if (!nodeMap[`${node[foreignKey]}`]) {
|
|
71
|
+
nodeMap[`${node[foreignKey]}`] = [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
nodeMap[`${node[foreignKey]}`].push(node);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function buildTree(parentId) {
|
|
78
|
+
const children = nodeMap[parentId];
|
|
79
|
+
|
|
80
|
+
if (!children) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return children.map((child) => {
|
|
85
|
+
const childrenValues = buildTree(child.id);
|
|
86
|
+
if (childrenValues.length > 0) {
|
|
87
|
+
child.setDataValue(childrenKey, childrenValues);
|
|
88
|
+
}
|
|
89
|
+
return child;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const parent of parentNodes) {
|
|
94
|
+
const parentId = parent[primaryKey];
|
|
95
|
+
const children = buildTree(parentId);
|
|
96
|
+
if (children.length > 0) {
|
|
97
|
+
parent.setDataValue(childrenKey, children);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.addIndex(parentNodes, childrenKey, options);
|
|
102
|
+
|
|
103
|
+
return parentNodes;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private addIndex(treeArray, childrenKey, options) {
|
|
107
|
+
function traverse(node, index) {
|
|
108
|
+
// patch for sequelize toJSON
|
|
109
|
+
if (node._options.includeNames && !node._options.includeNames.includes(childrenKey)) {
|
|
110
|
+
node._options.includeNames.push(childrenKey);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (options.addIndex !== false) {
|
|
114
|
+
node.setDataValue('__index', `${index}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const children = node.getDataValue(childrenKey);
|
|
118
|
+
|
|
119
|
+
if (children && children.length === 0) {
|
|
120
|
+
node.setDataValue(childrenKey, undefined);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (children && children.length > 0) {
|
|
124
|
+
children.forEach((child, i) => {
|
|
125
|
+
traverse(child, `${index}.${childrenKey}.${i}`);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
treeArray.forEach((tree, i) => {
|
|
131
|
+
traverse(tree, i);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private querySQL(rootIds, collection) {
|
|
136
|
+
const { treeParentField } = collection;
|
|
137
|
+
const foreignKey = treeParentField.options.foreignKey;
|
|
138
|
+
const foreignKeyField = collection.model.rawAttributes[foreignKey].field;
|
|
139
|
+
|
|
140
|
+
const primaryKey = collection.model.primaryKeyAttribute;
|
|
141
|
+
|
|
142
|
+
const queryInterface = this.database.sequelize.getQueryInterface();
|
|
143
|
+
const q = queryInterface.quoteIdentifier.bind(queryInterface);
|
|
144
|
+
|
|
145
|
+
return `
|
|
146
|
+
WITH RECURSIVE cte AS (
|
|
147
|
+
SELECT ${q(primaryKey)}, ${q(foreignKeyField)}, 1 AS level
|
|
148
|
+
FROM ${collection.quotedTableName()}
|
|
149
|
+
WHERE ${q(foreignKeyField)} IN (${rootIds.join(',')})
|
|
150
|
+
UNION ALL
|
|
151
|
+
SELECT t.${q(primaryKey)}, t.${q(foreignKeyField)}, cte.level + 1 AS level
|
|
152
|
+
FROM ${collection.quotedTableName()} t
|
|
153
|
+
JOIN cte ON t.${q(foreignKeyField)} = cte.${q(primaryKey)}
|
|
154
|
+
)
|
|
155
|
+
SELECT ${q(primaryKey)}, ${q(foreignKeyField)} as ${q(foreignKey)}, level
|
|
156
|
+
FROM cte
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
}
|