@strapi/database 4.6.0-beta.1 → 4.6.0
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/__tests__/index.test.js +93 -0
- package/lib/dialects/postgresql/index.js +10 -2
- package/lib/entity-manager/__tests__/relations-orderer.test.js +147 -68
- package/lib/entity-manager/__tests__/sort-connect-array.test.js +79 -0
- package/lib/entity-manager/index.js +15 -6
- package/lib/entity-manager/regular-relations.js +133 -120
- package/lib/entity-manager/relations-orderer.js +120 -21
- package/lib/errors/index.js +2 -0
- package/lib/errors/invalid-relation.js +14 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +47 -5
- package/lib/metadata/relations.js +4 -1
- package/lib/migrations/index.js +1 -1
- package/lib/query/query-builder.js +10 -1
- package/lib/tests/transactions.test.api.js +308 -0
- package/lib/transaction-context.js +17 -0
- package/lib/validations/index.js +20 -0
- package/lib/validations/relations/bidirectional.js +89 -0
- package/lib/validations/relations/index.js +14 -0
- package/package.json +4 -4
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Database } = require('../index');
|
|
4
|
+
|
|
5
|
+
jest.mock('../connection', () =>
|
|
6
|
+
jest.fn(() => {
|
|
7
|
+
const trx = {
|
|
8
|
+
commit: jest.fn(),
|
|
9
|
+
rollback: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
...trx,
|
|
13
|
+
transaction: jest.fn(async () => trx),
|
|
14
|
+
};
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
jest.mock('../dialects', () => ({
|
|
19
|
+
getDialect: jest.fn(() => ({
|
|
20
|
+
configure: jest.fn(),
|
|
21
|
+
initialize: jest.fn(),
|
|
22
|
+
})),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.mock('../migrations', () => ({
|
|
26
|
+
createMigrationsProvider: jest.fn(),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const config = {
|
|
30
|
+
models: [
|
|
31
|
+
{
|
|
32
|
+
tableName: 'strapi_core_store_settings',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
connection: {
|
|
36
|
+
client: 'postgres',
|
|
37
|
+
connection: {
|
|
38
|
+
database: 'strapi',
|
|
39
|
+
user: 'strapi',
|
|
40
|
+
password: 'strapi',
|
|
41
|
+
port: 5432,
|
|
42
|
+
host: 'localhost',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe('Database', () => {
|
|
48
|
+
describe('constructor', () => {
|
|
49
|
+
it('should throw an error if no config is provided', async () => {
|
|
50
|
+
expect(async () => Database.init()).rejects.toThrowError();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('it should intialize if config is provided', async () => {
|
|
54
|
+
expect(() => Database.init(config)).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('Transaction', () => {
|
|
59
|
+
it('transaction should be defined', async () => {
|
|
60
|
+
const db = await Database.init(config);
|
|
61
|
+
expect(db.transaction).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return value if transaction is complete', async () => {
|
|
65
|
+
const db = await Database.init(config);
|
|
66
|
+
const result = await db.transaction(async () => 'test');
|
|
67
|
+
expect(result).toBe('test');
|
|
68
|
+
expect(db.connection.commit).toHaveBeenCalledTimes(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('rollback is called incase of error', async () => {
|
|
72
|
+
const db = await Database.init(config);
|
|
73
|
+
try {
|
|
74
|
+
await db.transaction(async () => {
|
|
75
|
+
throw new Error('test');
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
/* ignore */
|
|
79
|
+
}
|
|
80
|
+
expect(db.connection.rollback).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should throw error', async () => {
|
|
84
|
+
const db = await Database.init(config);
|
|
85
|
+
|
|
86
|
+
expect(async () => {
|
|
87
|
+
await db.transaction(async () => {
|
|
88
|
+
throw new Error('test');
|
|
89
|
+
});
|
|
90
|
+
}).rejects.toThrowError('test');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -16,8 +16,16 @@ class PostgresDialect extends Dialect {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async initialize() {
|
|
19
|
-
this.db.connection.client.driver.types.setTypeParser(
|
|
20
|
-
|
|
19
|
+
this.db.connection.client.driver.types.setTypeParser(
|
|
20
|
+
this.db.connection.client.driver.types.builtins.DATE,
|
|
21
|
+
'text',
|
|
22
|
+
(v) => v
|
|
23
|
+
); // Don't cast DATE string to Date()
|
|
24
|
+
this.db.connection.client.driver.types.setTypeParser(
|
|
25
|
+
this.db.connection.client.driver.types.builtins.NUMERIC,
|
|
26
|
+
'text',
|
|
27
|
+
parseFloat
|
|
28
|
+
);
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
usesForeignKeys() {
|
|
@@ -1,85 +1,164 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const relationsOrderer = require('../relations-orderer');
|
|
3
|
+
const { relationsOrderer } = require('../relations-orderer');
|
|
4
4
|
|
|
5
|
-
describe('relations
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
describe('Given I have some relations in the database', () => {
|
|
6
|
+
describe('When I connect a relation at the end', () => {
|
|
7
|
+
test('Then it is placed at the end with the correct order', () => {
|
|
8
|
+
const orderer = relationsOrderer(
|
|
9
|
+
[
|
|
10
|
+
{ id: 2, order: 4 },
|
|
11
|
+
{ id: 3, order: 10 },
|
|
12
|
+
],
|
|
13
|
+
'id',
|
|
14
|
+
'order'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
orderer.connect([{ id: 4, position: { end: true } }, { id: 5 }]);
|
|
18
|
+
|
|
19
|
+
expect(orderer.get()).toMatchObject([
|
|
9
20
|
{ id: 2, order: 4 },
|
|
10
21
|
{ id: 3, order: 10 },
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
orderer.connect([{ id: 4, position: { end: true } }, { id: 5 }]);
|
|
17
|
-
|
|
18
|
-
expect(orderer.get()).toMatchObject([
|
|
19
|
-
{ id: 2, order: 4 },
|
|
20
|
-
{ id: 3, order: 10 },
|
|
21
|
-
{ id: 4, order: 10.5 },
|
|
22
|
-
{ id: 5, order: 10.5 },
|
|
23
|
-
]);
|
|
22
|
+
{ id: 4, order: 10.5 },
|
|
23
|
+
{ id: 5, order: 10.5 },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
24
26
|
});
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
describe('When I connect a relation at the start', () => {
|
|
29
|
+
test('Then it is placed at the start with the correct order', () => {
|
|
30
|
+
const orderer = relationsOrderer(
|
|
31
|
+
[
|
|
32
|
+
{ id: 2, order: 4 },
|
|
33
|
+
{ id: 3, order: 10 },
|
|
34
|
+
],
|
|
35
|
+
'id',
|
|
36
|
+
'order'
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
orderer.connect([{ id: 4, position: { start: true } }]);
|
|
40
|
+
|
|
41
|
+
expect(orderer.get()).toMatchObject([
|
|
42
|
+
{ id: 4, order: 0.5 },
|
|
29
43
|
{ id: 2, order: 4 },
|
|
30
44
|
{ id: 3, order: 10 },
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
'order'
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
orderer.connect([{ id: 4, position: { start: true } }]);
|
|
37
|
-
|
|
38
|
-
expect(orderer.get()).toMatchObject([
|
|
39
|
-
{ id: 4, order: 0.5 },
|
|
40
|
-
{ id: 2, order: 4 },
|
|
41
|
-
{ id: 3, order: 10 },
|
|
42
|
-
]);
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
43
47
|
});
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
describe('When I connect multiple relations using before', () => {
|
|
50
|
+
test('Then they are correctly ordered', () => {
|
|
51
|
+
const orderer = relationsOrderer(
|
|
52
|
+
[
|
|
53
|
+
{ id: 2, order: 4 },
|
|
54
|
+
{ id: 3, order: 10 },
|
|
55
|
+
],
|
|
56
|
+
'id',
|
|
57
|
+
'order'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
orderer.connect([
|
|
61
|
+
{ id: 4, position: { before: 3 } },
|
|
62
|
+
{ id: 5, position: { before: 4 } },
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
expect(orderer.get()).toMatchObject([
|
|
48
66
|
{ id: 2, order: 4 },
|
|
67
|
+
{ id: 5, order: 9.5 },
|
|
68
|
+
{ id: 4, order: 9.5 },
|
|
49
69
|
{ id: 3, order: 10 },
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
'order'
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
orderer.connect([
|
|
56
|
-
{ id: 4, position: { before: 2 } },
|
|
57
|
-
{ id: 4, position: { before: 3 } },
|
|
58
|
-
{ id: 5, position: { before: 4 } },
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
expect(orderer.get()).toMatchObject([
|
|
62
|
-
{ id: 2, order: 4 },
|
|
63
|
-
{ id: 5, order: 9.5 },
|
|
64
|
-
{ id: 4, order: 9.5 },
|
|
65
|
-
{ id: 3, order: 10 },
|
|
66
|
-
]);
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
67
72
|
});
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
describe('When you connect multiple disordered relations', () => {
|
|
75
|
+
test('Then they are correctly ordered', () => {
|
|
76
|
+
const orderer = relationsOrderer(
|
|
77
|
+
[
|
|
78
|
+
{ id: 1, order: 1 },
|
|
79
|
+
{ id: 2, order: 2 },
|
|
80
|
+
{ id: 3, order: 3 },
|
|
81
|
+
],
|
|
82
|
+
'id',
|
|
83
|
+
'order'
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
orderer.connect([
|
|
87
|
+
{ id: 5, position: { before: 1 } },
|
|
88
|
+
{ id: 1, position: { before: 2 } },
|
|
89
|
+
{ id: 2, position: { end: true } },
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
expect(orderer.get()).toMatchObject([
|
|
93
|
+
{ id: 5, order: 0.5 },
|
|
94
|
+
{ id: 1, order: 1.5 },
|
|
95
|
+
{ id: 3, order: 3 },
|
|
96
|
+
{ id: 2, order: 3.5 },
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('When you connect a relation before a non-existing relation in non-strict mode', () => {
|
|
102
|
+
test('Then it is placed at the end', () => {
|
|
103
|
+
const orderer = relationsOrderer(
|
|
104
|
+
[
|
|
105
|
+
{ id: 1, order: 1 },
|
|
106
|
+
{ id: 2, order: 2 },
|
|
107
|
+
{ id: 3, order: 3 },
|
|
108
|
+
],
|
|
109
|
+
'id',
|
|
110
|
+
'order',
|
|
111
|
+
false
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
orderer.connect([{ id: 4, position: { before: 5 } }]);
|
|
115
|
+
|
|
116
|
+
expect(orderer.get()).toMatchObject([
|
|
117
|
+
{ id: 1, order: 1 },
|
|
118
|
+
{ id: 2, order: 2 },
|
|
119
|
+
{ id: 3, order: 3 },
|
|
120
|
+
{ id: 4, order: 3.5 },
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Given there are no relations in the database', () => {
|
|
127
|
+
describe('When you connect multiple new relations', () => {
|
|
128
|
+
test('Then they are correctly ordered', () => {
|
|
129
|
+
const orderer = relationsOrderer([], 'id', 'order');
|
|
130
|
+
|
|
131
|
+
orderer.connect([
|
|
132
|
+
{ id: 1, position: { start: true } },
|
|
133
|
+
{ id: 2, position: { start: true } },
|
|
134
|
+
{ id: 3, position: { after: 1 } },
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
expect(orderer.get()).toMatchObject([
|
|
138
|
+
{ id: 2, order: 0.5 },
|
|
139
|
+
{ id: 1, order: 0.5 },
|
|
140
|
+
{ id: 3, order: 0.5 },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('When you connect multiple disordered relations', () => {
|
|
146
|
+
test('Then they are correctly ordered', () => {
|
|
147
|
+
const orderer = relationsOrderer([], 'id', 'order');
|
|
148
|
+
|
|
149
|
+
orderer.connect([
|
|
150
|
+
{ id: 5, position: { before: 1 } },
|
|
151
|
+
{ id: 1, position: { before: 2 } },
|
|
152
|
+
{ id: 2, position: { end: true } },
|
|
153
|
+
{ id: 3, position: { after: 1 } },
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
expect(orderer.get()).toMatchObject([
|
|
157
|
+
{ id: 5, order: 0.5 },
|
|
158
|
+
{ id: 1, order: 0.5 },
|
|
159
|
+
{ id: 3, order: 0.5 },
|
|
160
|
+
{ id: 2, order: 0.5 },
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
84
163
|
});
|
|
85
164
|
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { sortConnectArray } = require('../relations-orderer');
|
|
4
|
+
|
|
5
|
+
describe('sortConnectArray', () => {
|
|
6
|
+
test('sorts connect array', () => {
|
|
7
|
+
const sortConnect = sortConnectArray([
|
|
8
|
+
{ id: 5, position: { before: 1 } },
|
|
9
|
+
{ id: 1, position: { before: 2 } },
|
|
10
|
+
{ id: 2, position: { end: true } },
|
|
11
|
+
{ id: 3, position: { after: 1 } },
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
expect(sortConnect).toMatchObject([
|
|
15
|
+
{ id: 2, position: { end: true } },
|
|
16
|
+
{ id: 1, position: { before: 2 } },
|
|
17
|
+
{ id: 5, position: { before: 1 } },
|
|
18
|
+
{ id: 3, position: { after: 1 } },
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('sorts connect array with initial relations', () => {
|
|
23
|
+
const sortConnect = sortConnectArray(
|
|
24
|
+
[
|
|
25
|
+
{ id: 5, position: { before: 1 } },
|
|
26
|
+
{ id: 1, position: { before: 2 } },
|
|
27
|
+
{ id: 2, position: { end: true } },
|
|
28
|
+
{ id: 3, position: { after: 1 } },
|
|
29
|
+
],
|
|
30
|
+
[{ id: 1 }]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(sortConnect).toMatchObject([
|
|
34
|
+
{ id: 5, position: { before: 1 } },
|
|
35
|
+
{ id: 2, position: { end: true } },
|
|
36
|
+
{ id: 1, position: { before: 2 } },
|
|
37
|
+
{ id: 3, position: { after: 1 } },
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("error if position doesn't exist", () => {
|
|
42
|
+
const sortConnect = () => sortConnectArray([{ id: 1, position: { after: 2 } }]);
|
|
43
|
+
|
|
44
|
+
expect(sortConnect).toThrowError(
|
|
45
|
+
'There was a problem connecting relation with id 1 at position {"after":2}. The relation with id 2 needs to be connected first.'
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('error with circular references', () => {
|
|
50
|
+
const sortConnect = () =>
|
|
51
|
+
sortConnectArray(
|
|
52
|
+
[
|
|
53
|
+
{ id: 2, position: { after: 1 } },
|
|
54
|
+
{ id: 3, position: { after: 1 } },
|
|
55
|
+
{ id: 1, position: { after: 3 } },
|
|
56
|
+
],
|
|
57
|
+
[]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(sortConnect).toThrowError(
|
|
61
|
+
'A circular reference was found in the connect array. One relation is trying to connect before/after another one that is trying to connect before/after it'
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('error when connecting same relation twice', () => {
|
|
66
|
+
const sortConnect = () =>
|
|
67
|
+
sortConnectArray(
|
|
68
|
+
[
|
|
69
|
+
{ id: 1, position: { after: 2 } },
|
|
70
|
+
{ id: 1, position: { after: 3 } },
|
|
71
|
+
],
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(sortConnect).toThrowError(
|
|
76
|
+
'The relation with id 1 is already connected. You cannot connect the same relation twice.'
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -39,7 +39,7 @@ const {
|
|
|
39
39
|
deleteRelations,
|
|
40
40
|
cleanOrderColumns,
|
|
41
41
|
} = require('./regular-relations');
|
|
42
|
-
const relationsOrderer = require('./relations-orderer');
|
|
42
|
+
const { relationsOrderer } = require('./relations-orderer');
|
|
43
43
|
|
|
44
44
|
const toId = (value) => value.id || value;
|
|
45
45
|
const toIds = (value) => castArray(value || []).map(toId);
|
|
@@ -78,6 +78,9 @@ const toAssocs = (data) => {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
return {
|
|
81
|
+
options: {
|
|
82
|
+
strict: data?.options?.strict,
|
|
83
|
+
},
|
|
81
84
|
connect: toIdArray(data?.connect).map((elm) => ({
|
|
82
85
|
id: elm.id,
|
|
83
86
|
position: elm.position ? elm.position : { end: true },
|
|
@@ -224,7 +227,7 @@ const createEntityManager = (db) => {
|
|
|
224
227
|
|
|
225
228
|
const trx = await strapi.db.transaction();
|
|
226
229
|
try {
|
|
227
|
-
await this.attachRelations(uid, id, data, { transaction: trx });
|
|
230
|
+
await this.attachRelations(uid, id, data, { transaction: trx.get() });
|
|
228
231
|
|
|
229
232
|
await trx.commit();
|
|
230
233
|
} catch (e) {
|
|
@@ -308,7 +311,7 @@ const createEntityManager = (db) => {
|
|
|
308
311
|
|
|
309
312
|
const trx = await strapi.db.transaction();
|
|
310
313
|
try {
|
|
311
|
-
await this.updateRelations(uid, id, data, { transaction: trx });
|
|
314
|
+
await this.updateRelations(uid, id, data, { transaction: trx.get() });
|
|
312
315
|
await trx.commit();
|
|
313
316
|
} catch (e) {
|
|
314
317
|
await trx.rollback();
|
|
@@ -379,7 +382,7 @@ const createEntityManager = (db) => {
|
|
|
379
382
|
|
|
380
383
|
const trx = await strapi.db.transaction();
|
|
381
384
|
try {
|
|
382
|
-
await this.deleteRelations(uid, id, { transaction: trx });
|
|
385
|
+
await this.deleteRelations(uid, id, { transaction: trx.get() });
|
|
383
386
|
|
|
384
387
|
await trx.commit();
|
|
385
388
|
} catch (e) {
|
|
@@ -583,7 +586,12 @@ const createEntityManager = (db) => {
|
|
|
583
586
|
});
|
|
584
587
|
} else if (cleanRelationData.connect && hasOrderColumn(attribute)) {
|
|
585
588
|
// use position attributes to calculate order
|
|
586
|
-
const orderMap = relationsOrderer(
|
|
589
|
+
const orderMap = relationsOrderer(
|
|
590
|
+
[],
|
|
591
|
+
inverseJoinColumn.name,
|
|
592
|
+
joinTable.orderColumnName,
|
|
593
|
+
true // Always make an strict connect when inserting
|
|
594
|
+
)
|
|
587
595
|
.connect(relsToAdd)
|
|
588
596
|
.get()
|
|
589
597
|
// set the order based on the order of the ids
|
|
@@ -873,7 +881,8 @@ const createEntityManager = (db) => {
|
|
|
873
881
|
const orderMap = relationsOrderer(
|
|
874
882
|
adjacentRelations,
|
|
875
883
|
inverseJoinColumn.name,
|
|
876
|
-
joinTable.orderColumnName
|
|
884
|
+
joinTable.orderColumnName,
|
|
885
|
+
cleanRelationData.options.strict
|
|
877
886
|
)
|
|
878
887
|
.connect(cleanRelationData.connect)
|
|
879
888
|
.getOrderMap();
|