@nocobase/database 0.9.2-alpha.3 → 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
|
@@ -1,7 +1,264 @@
|
|
|
1
1
|
import { mockDatabase } from '../index';
|
|
2
2
|
import Database from '@nocobase/database';
|
|
3
3
|
import { Collection } from '../../collection';
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
describe('find with associations', () => {
|
|
6
|
+
let db: Database;
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
db = mockDatabase();
|
|
9
|
+
|
|
10
|
+
await db.clean({ drop: true });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await db.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should filter by association array field', async () => {
|
|
18
|
+
const User = db.collection({
|
|
19
|
+
name: 'users',
|
|
20
|
+
fields: [
|
|
21
|
+
{
|
|
22
|
+
type: 'string',
|
|
23
|
+
name: 'name',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'hasMany',
|
|
27
|
+
name: 'posts',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const Post = db.collection({
|
|
33
|
+
name: 'posts',
|
|
34
|
+
fields: [
|
|
35
|
+
{
|
|
36
|
+
type: 'array',
|
|
37
|
+
name: 'tags',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'string',
|
|
41
|
+
name: 'title',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await db.sync();
|
|
47
|
+
|
|
48
|
+
await User.repository.create({
|
|
49
|
+
values: [
|
|
50
|
+
{
|
|
51
|
+
name: 'u1',
|
|
52
|
+
posts: [
|
|
53
|
+
{
|
|
54
|
+
tags: ['t1'],
|
|
55
|
+
title: 'u1p1',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const posts = await Post.repository.find({
|
|
63
|
+
filter: {
|
|
64
|
+
tags: {
|
|
65
|
+
$match: ['t1'],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(posts.length).toEqual(1);
|
|
71
|
+
|
|
72
|
+
const filter = {
|
|
73
|
+
$and: [
|
|
74
|
+
{
|
|
75
|
+
posts: {
|
|
76
|
+
tags: {
|
|
77
|
+
$match: ['t1'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const results = await User.repository.find({
|
|
85
|
+
filter,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(results[0].get('name')).toEqual('u1');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should filter with append', async () => {
|
|
92
|
+
const Post = db.collection({
|
|
93
|
+
name: 'posts',
|
|
94
|
+
fields: [
|
|
95
|
+
{ name: 'title', type: 'string' },
|
|
96
|
+
{
|
|
97
|
+
name: 'user',
|
|
98
|
+
type: 'belongsTo',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'category',
|
|
102
|
+
type: 'belongsTo',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const Category = db.collection({
|
|
108
|
+
name: 'categories',
|
|
109
|
+
fields: [
|
|
110
|
+
{
|
|
111
|
+
name: 'name',
|
|
112
|
+
type: 'string',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const User = db.collection({
|
|
118
|
+
name: 'users',
|
|
119
|
+
fields: [
|
|
120
|
+
{ name: 'name', type: 'string' },
|
|
121
|
+
{ type: 'belongsTo', name: 'organization' },
|
|
122
|
+
{
|
|
123
|
+
type: 'belongsTo',
|
|
124
|
+
name: 'department',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const Org = db.collection({
|
|
130
|
+
name: 'organizations',
|
|
131
|
+
fields: [{ name: 'name', type: 'string' }],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const Dept = db.collection({
|
|
135
|
+
name: 'departments',
|
|
136
|
+
fields: [{ name: 'name', type: 'string' }],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await db.sync();
|
|
140
|
+
|
|
141
|
+
await Post.repository.create({
|
|
142
|
+
values: [
|
|
143
|
+
{
|
|
144
|
+
title: 'p1',
|
|
145
|
+
category: { name: 'c1' },
|
|
146
|
+
user: {
|
|
147
|
+
name: 'u1',
|
|
148
|
+
organization: {
|
|
149
|
+
name: 'o1',
|
|
150
|
+
},
|
|
151
|
+
department: {
|
|
152
|
+
name: 'd1',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
title: 'p2',
|
|
158
|
+
category: { name: 'c2' },
|
|
159
|
+
user: {
|
|
160
|
+
name: 'u2',
|
|
161
|
+
organization: {
|
|
162
|
+
name: 'o2',
|
|
163
|
+
},
|
|
164
|
+
department: {
|
|
165
|
+
name: 'd2',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const filterResult = await Post.repository.find({
|
|
173
|
+
appends: ['user.department'],
|
|
174
|
+
filter: {
|
|
175
|
+
'user.name': 'u1',
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(filterResult[0].user.department).toBeDefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should filter by association field', async () => {
|
|
183
|
+
const User = db.collection({
|
|
184
|
+
name: 'users',
|
|
185
|
+
tree: 'adjacency-list',
|
|
186
|
+
fields: [
|
|
187
|
+
{ type: 'string', name: 'name' },
|
|
188
|
+
{ type: 'hasMany', name: 'posts', target: 'posts', foreignKey: 'user_id' },
|
|
189
|
+
{
|
|
190
|
+
type: 'belongsTo',
|
|
191
|
+
name: 'parent',
|
|
192
|
+
foreignKey: 'parent_id',
|
|
193
|
+
treeParent: true,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: 'hasMany',
|
|
197
|
+
name: 'children',
|
|
198
|
+
foreignKey: 'parent_id',
|
|
199
|
+
treeChildren: true,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const Post = db.collection({
|
|
205
|
+
name: 'posts',
|
|
206
|
+
fields: [
|
|
207
|
+
{ type: 'string', name: 'title' },
|
|
208
|
+
{ type: 'belongsTo', name: 'user', target: 'users', foreignKey: 'user_id' },
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await db.sync();
|
|
213
|
+
|
|
214
|
+
expect(User.options.tree).toBeTruthy();
|
|
215
|
+
|
|
216
|
+
await User.repository.create({
|
|
217
|
+
values: [
|
|
218
|
+
{
|
|
219
|
+
name: 'u1',
|
|
220
|
+
posts: [
|
|
221
|
+
{
|
|
222
|
+
title: 'u1p1',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
children: [
|
|
226
|
+
{
|
|
227
|
+
name: 'u2',
|
|
228
|
+
posts: [
|
|
229
|
+
{
|
|
230
|
+
title: '标题2',
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const filter = {
|
|
240
|
+
$and: [
|
|
241
|
+
{
|
|
242
|
+
children: {
|
|
243
|
+
posts: {
|
|
244
|
+
title: {
|
|
245
|
+
$eq: '标题2',
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const [findResult, count] = await User.repository.findAndCount({
|
|
254
|
+
filter,
|
|
255
|
+
offset: 0,
|
|
256
|
+
limit: 20,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(findResult[0].get('name')).toEqual('u1');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
5
262
|
|
|
6
263
|
describe('repository find', () => {
|
|
7
264
|
let db: Database;
|
|
@@ -1,17 +1,184 @@
|
|
|
1
1
|
import { Database } from '../database';
|
|
2
2
|
import { mockDatabase } from './';
|
|
3
|
+
import { AdjacencyListRepository } from '../tree-repository/adjacency-list-repository';
|
|
3
4
|
|
|
4
|
-
describe('
|
|
5
|
+
describe('tree test', function () {
|
|
5
6
|
let db: Database;
|
|
6
7
|
|
|
7
8
|
beforeEach(async () => {
|
|
8
|
-
db = mockDatabase(
|
|
9
|
+
db = mockDatabase({
|
|
10
|
+
tablePrefix: '',
|
|
11
|
+
});
|
|
12
|
+
await db.clean({ drop: true });
|
|
9
13
|
});
|
|
10
14
|
|
|
11
15
|
afterEach(async () => {
|
|
12
16
|
await db.close();
|
|
13
17
|
});
|
|
14
18
|
|
|
19
|
+
it('should works with appends option', async () => {
|
|
20
|
+
const collection = db.collection({
|
|
21
|
+
name: 'categories',
|
|
22
|
+
tree: 'adjacency-list',
|
|
23
|
+
fields: [
|
|
24
|
+
{ type: 'string', name: 'name' },
|
|
25
|
+
{
|
|
26
|
+
type: 'belongsTo',
|
|
27
|
+
name: 'parent',
|
|
28
|
+
treeParent: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'hasMany',
|
|
32
|
+
name: 'children',
|
|
33
|
+
treeChildren: true,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await db.sync();
|
|
39
|
+
|
|
40
|
+
await collection.repository.create({
|
|
41
|
+
values: [
|
|
42
|
+
{
|
|
43
|
+
name: 'c1',
|
|
44
|
+
children: [
|
|
45
|
+
{
|
|
46
|
+
name: 'c11',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'c12',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'c2',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const tree = await collection.repository.find({
|
|
60
|
+
tree: true,
|
|
61
|
+
filter: {
|
|
62
|
+
parentId: null,
|
|
63
|
+
},
|
|
64
|
+
fields: ['name'],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(tree.length).toBe(2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should not return children property when child nodes are empty', async () => {
|
|
71
|
+
const collection = db.collection({
|
|
72
|
+
name: 'categories',
|
|
73
|
+
tree: 'adjacency-list',
|
|
74
|
+
fields: [
|
|
75
|
+
{ type: 'string', name: 'name' },
|
|
76
|
+
{
|
|
77
|
+
type: 'belongsTo',
|
|
78
|
+
name: 'parent',
|
|
79
|
+
treeParent: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: 'hasMany',
|
|
83
|
+
name: 'children',
|
|
84
|
+
treeChildren: true,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await db.sync();
|
|
90
|
+
|
|
91
|
+
await collection.repository.create({
|
|
92
|
+
values: [
|
|
93
|
+
{
|
|
94
|
+
name: 'c1',
|
|
95
|
+
children: [
|
|
96
|
+
{
|
|
97
|
+
name: 'c11',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'c12',
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'c2',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const tree = await collection.repository.find({
|
|
111
|
+
filter: {
|
|
112
|
+
parentId: null,
|
|
113
|
+
},
|
|
114
|
+
tree: true,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const c2 = tree.find((item) => item.name === 'c2');
|
|
118
|
+
expect(c2.toJSON()['children']).toBeUndefined();
|
|
119
|
+
|
|
120
|
+
const c11 = tree
|
|
121
|
+
.find((item) => item.name === 'c1')
|
|
122
|
+
.get('children')
|
|
123
|
+
.find((item) => item.name === 'c11');
|
|
124
|
+
|
|
125
|
+
expect(c11.toJSON()['children']).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should add sort field', async () => {
|
|
129
|
+
const Tasks = db.collection({
|
|
130
|
+
name: 'tasks',
|
|
131
|
+
tree: 'adjacency-list',
|
|
132
|
+
fields: [
|
|
133
|
+
{
|
|
134
|
+
type: 'string',
|
|
135
|
+
name: 'name',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'belongsTo',
|
|
139
|
+
name: 'parent',
|
|
140
|
+
treeParent: true,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'hasMany',
|
|
144
|
+
name: 'children',
|
|
145
|
+
treeChildren: true,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'string',
|
|
149
|
+
name: 'status',
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await db.sync();
|
|
155
|
+
|
|
156
|
+
await Tasks.repository.create({
|
|
157
|
+
values: {
|
|
158
|
+
name: 'task1',
|
|
159
|
+
status: 'doing',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await Tasks.repository.create({
|
|
164
|
+
values: {
|
|
165
|
+
name: 'task2',
|
|
166
|
+
status: 'pending',
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await Tasks.repository.create({
|
|
171
|
+
values: {
|
|
172
|
+
name: 'task3',
|
|
173
|
+
status: 'pending',
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
Tasks.setField('sort', { type: 'sort', scopeKey: 'status' });
|
|
178
|
+
|
|
179
|
+
await db.sync();
|
|
180
|
+
});
|
|
181
|
+
|
|
15
182
|
it('should be auto completed', () => {
|
|
16
183
|
const collection = db.collection({
|
|
17
184
|
name: 'categories',
|
|
@@ -162,7 +329,7 @@ describe('sort', function () {
|
|
|
162
329
|
expect(instance.toJSON()).toMatchObject(values[0]);
|
|
163
330
|
});
|
|
164
331
|
|
|
165
|
-
it('should
|
|
332
|
+
it('should find tree collection', async () => {
|
|
166
333
|
const collection = db.collection({
|
|
167
334
|
name: 'categories',
|
|
168
335
|
tree: 'adjacency-list',
|
|
@@ -214,4 +381,100 @@ describe('sort', function () {
|
|
|
214
381
|
|
|
215
382
|
expect(instance.toJSON()).toMatchObject(values[0]);
|
|
216
383
|
});
|
|
384
|
+
|
|
385
|
+
it('should get adjacency list repository', async () => {
|
|
386
|
+
const collection = db.collection({
|
|
387
|
+
name: 'categories',
|
|
388
|
+
tree: 'adjacency-list',
|
|
389
|
+
fields: [
|
|
390
|
+
{
|
|
391
|
+
type: 'string',
|
|
392
|
+
name: 'name',
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
type: 'belongsTo',
|
|
396
|
+
name: 'parent',
|
|
397
|
+
foreignKey: 'parentId',
|
|
398
|
+
treeParent: true,
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
type: 'hasMany',
|
|
402
|
+
name: 'children',
|
|
403
|
+
foreignKey: 'parentId',
|
|
404
|
+
treeChildren: true,
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const repository = db.getRepository('categories');
|
|
410
|
+
expect(repository).toBeInstanceOf(AdjacencyListRepository);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('performance', async () => {
|
|
414
|
+
const collection = db.collection({
|
|
415
|
+
name: 'categories',
|
|
416
|
+
tree: 'adjacency-list',
|
|
417
|
+
fields: [
|
|
418
|
+
{
|
|
419
|
+
type: 'string',
|
|
420
|
+
name: 'name',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
type: 'belongsTo',
|
|
424
|
+
name: 'parent',
|
|
425
|
+
foreignKey: 'parentId',
|
|
426
|
+
treeParent: true,
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
type: 'hasMany',
|
|
430
|
+
name: 'children',
|
|
431
|
+
foreignKey: 'parentId',
|
|
432
|
+
treeChildren: true,
|
|
433
|
+
},
|
|
434
|
+
],
|
|
435
|
+
});
|
|
436
|
+
await db.sync();
|
|
437
|
+
|
|
438
|
+
const values = [];
|
|
439
|
+
for (let i = 0; i < 10; i++) {
|
|
440
|
+
const children = [];
|
|
441
|
+
for (let j = 0; j < 10; j++) {
|
|
442
|
+
const grandchildren = [];
|
|
443
|
+
for (let k = 0; k < 10; k++) {
|
|
444
|
+
grandchildren.push({
|
|
445
|
+
name: `name-${i}-${j}-${k}`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
children.push({
|
|
449
|
+
name: `name-${i}-${j}`,
|
|
450
|
+
children: grandchildren,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
values.push({
|
|
455
|
+
name: `name-${i}`,
|
|
456
|
+
description: `description-${i}`,
|
|
457
|
+
children,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
await db.getRepository('categories').create({
|
|
462
|
+
values,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const before = Date.now();
|
|
466
|
+
|
|
467
|
+
const instances = await db.getRepository('categories').find({
|
|
468
|
+
filter: {
|
|
469
|
+
parentId: null,
|
|
470
|
+
},
|
|
471
|
+
tree: true,
|
|
472
|
+
fields: ['id', 'name'],
|
|
473
|
+
sort: 'id',
|
|
474
|
+
limit: 10,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const after = Date.now();
|
|
478
|
+
console.log(after - before);
|
|
479
|
+
});
|
|
217
480
|
});
|
package/src/collection.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { BelongsToField, Field, FieldOptions, HasManyField } from './fields';
|
|
|
14
14
|
import { Model } from './model';
|
|
15
15
|
import { Repository } from './repository';
|
|
16
16
|
import { checkIdentifier, md5, snakeCase } from './utils';
|
|
17
|
+
import { AdjacencyListRepository } from './tree-repository/adjacency-list-repository';
|
|
17
18
|
|
|
18
19
|
export type RepositoryType = typeof Repository;
|
|
19
20
|
|
|
@@ -223,6 +224,11 @@ export class Collection<
|
|
|
223
224
|
if (typeof repository === 'string') {
|
|
224
225
|
repo = this.context.database.repositories.get(repository) || Repository;
|
|
225
226
|
}
|
|
227
|
+
|
|
228
|
+
if (this.options.tree == 'adjacency-list' || this.options.tree == 'adjacencyList') {
|
|
229
|
+
repo = AdjacencyListRepository;
|
|
230
|
+
}
|
|
231
|
+
|
|
226
232
|
this.repository = new repo(this);
|
|
227
233
|
}
|
|
228
234
|
|
package/src/fields/field.ts
CHANGED
|
@@ -217,6 +217,7 @@ export abstract class Field {
|
|
|
217
217
|
bind() {
|
|
218
218
|
const { model } = this.context.collection;
|
|
219
219
|
model.rawAttributes[this.name] = this.toSequelize();
|
|
220
|
+
|
|
220
221
|
// @ts-ignore
|
|
221
222
|
model.refreshAttributes();
|
|
222
223
|
if (this.options.index) {
|
|
@@ -226,6 +227,9 @@ export abstract class Field {
|
|
|
226
227
|
|
|
227
228
|
unbind() {
|
|
228
229
|
const { model } = this.context.collection;
|
|
230
|
+
|
|
231
|
+
delete model.prototype[this.name];
|
|
232
|
+
|
|
229
233
|
model.removeAttribute(this.name);
|
|
230
234
|
if (this.options.index || this.options.unique) {
|
|
231
235
|
this.context.collection.removeIndex([this.name]);
|
|
@@ -17,44 +17,3 @@ export const beforeDefineAdjacencyListCollection = (options: CollectionOptions)
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
|
-
|
|
21
|
-
export const afterDefineAdjacencyListCollection = (collection: Collection) => {
|
|
22
|
-
if (!collection.options.tree) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
collection.model.afterFind(async (instances, options: any) => {
|
|
26
|
-
if (!options.tree) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const foreignKey = collection.treeParentField?.foreignKey ?? 'parentId';
|
|
30
|
-
const childrenKey = collection.treeChildrenField?.name ?? 'children';
|
|
31
|
-
const arr: Model[] = Array.isArray(instances) ? instances : [instances];
|
|
32
|
-
let index = 0;
|
|
33
|
-
for (const instance of arr) {
|
|
34
|
-
const opts = {
|
|
35
|
-
...lodash.pick(options, ['tree', 'fields', 'appends', 'except', 'sort']),
|
|
36
|
-
};
|
|
37
|
-
let __index = `${index++}`;
|
|
38
|
-
if (options.parentIndex) {
|
|
39
|
-
__index = `${options.parentIndex}.${__index}`;
|
|
40
|
-
}
|
|
41
|
-
instance.setDataValue('__index', __index);
|
|
42
|
-
const children = await collection.repository.find({
|
|
43
|
-
filter: {
|
|
44
|
-
[foreignKey]: instance.id,
|
|
45
|
-
},
|
|
46
|
-
transaction: options.transaction,
|
|
47
|
-
...opts,
|
|
48
|
-
// @ts-ignore
|
|
49
|
-
parentIndex: `${__index}.${childrenKey}`,
|
|
50
|
-
context: options.context,
|
|
51
|
-
});
|
|
52
|
-
if (children?.length > 0) {
|
|
53
|
-
instance.setDataValue(
|
|
54
|
-
childrenKey,
|
|
55
|
-
children.map((r) => r.toJSON()),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
};
|
package/src/listeners/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Database } from '../database';
|
|
2
|
-
import {
|
|
2
|
+
import { beforeDefineAdjacencyListCollection } from './adjacency-list';
|
|
3
3
|
|
|
4
4
|
export const registerBuiltInListeners = (db: Database) => {
|
|
5
5
|
db.on('beforeDefineCollection', beforeDefineAdjacencyListCollection);
|
|
6
|
-
db.on('afterDefineCollection', afterDefineAdjacencyListCollection);
|
|
7
6
|
};
|
package/src/operators/array.ts
CHANGED
|
@@ -11,6 +11,11 @@ const escape = (value, ctx) => {
|
|
|
11
11
|
return sequelize.escape(value);
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
const getQueryInterface = (ctx) => {
|
|
15
|
+
const sequelize = ctx.db.sequelize;
|
|
16
|
+
return sequelize.getQueryInterface();
|
|
17
|
+
};
|
|
18
|
+
|
|
14
19
|
const sqliteExistQuery = (value, ctx) => {
|
|
15
20
|
const fieldName = getFieldName(ctx);
|
|
16
21
|
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
|
|
@@ -45,10 +50,9 @@ export default {
|
|
|
45
50
|
const fieldName = getFieldName(ctx);
|
|
46
51
|
|
|
47
52
|
if (isPg(ctx)) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
53
|
+
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
|
|
54
|
+
const queryValue = escape(JSON.stringify(value), ctx);
|
|
55
|
+
return Sequelize.literal(`${name} @> ${queryValue}::JSONB AND ${name} <@ ${queryValue}::JSONB`);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
value = escape(JSON.stringify(value.sort()), ctx);
|
package/src/options-parser.ts
CHANGED
|
@@ -249,6 +249,11 @@ export class OptionsParser {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
if (appendFields.length == 2) {
|
|
252
|
+
const association = associations[appendFields[0]];
|
|
253
|
+
if (!association) {
|
|
254
|
+
throw new Error(`association ${appendFields[0]} in ${model.name} not found`);
|
|
255
|
+
}
|
|
256
|
+
|
|
252
257
|
const associationModel = associations[appendFields[0]].target;
|
|
253
258
|
if (associationModel.rawAttributes[appendFields[1]]) {
|
|
254
259
|
lastLevel = true;
|
|
@@ -307,6 +312,13 @@ export class OptionsParser {
|
|
|
307
312
|
attributes,
|
|
308
313
|
};
|
|
309
314
|
} else {
|
|
315
|
+
const existInclude = queryParams['include'][existIncludeIndex];
|
|
316
|
+
if (existInclude.attributes && Array.isArray(existInclude.attributes) && existInclude.attributes.length == 0) {
|
|
317
|
+
existInclude.attributes = {
|
|
318
|
+
include: [],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
310
322
|
setInclude(
|
|
311
323
|
model.associations[queryParams['include'][existIncludeIndex].association].target,
|
|
312
324
|
queryParams['include'][existIncludeIndex],
|