@strapi/database 4.0.0-next.6 → 4.0.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/dialects/dialect.js +45 -0
- package/lib/dialects/index.js +6 -112
- package/lib/dialects/mysql/index.js +51 -0
- package/lib/dialects/mysql/schema-inspector.js +199 -0
- package/lib/dialects/postgresql/index.js +49 -0
- package/lib/dialects/postgresql/schema-inspector.js +232 -0
- package/lib/dialects/sqlite/index.js +74 -0
- package/lib/dialects/sqlite/schema-inspector.js +151 -0
- package/lib/entity-manager.js +18 -14
- package/lib/entity-repository.js +2 -3
- package/lib/errors.js +45 -3
- package/lib/fields.d.ts +2 -3
- package/lib/fields.js +7 -16
- package/lib/index.d.ts +67 -22
- package/lib/index.js +44 -27
- package/lib/lifecycles/index.d.ts +50 -0
- package/lib/{lifecycles.js → lifecycles/index.js} +25 -14
- package/lib/lifecycles/subscribers/index.d.ts +9 -0
- package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
- package/lib/lifecycles/subscribers/timestamps.js +65 -0
- package/lib/metadata/index.js +84 -95
- package/lib/metadata/relations.js +16 -0
- package/lib/migrations/index.d.ts +9 -0
- package/lib/migrations/index.js +69 -0
- package/lib/migrations/storage.js +51 -0
- package/lib/query/helpers/join.js +3 -5
- package/lib/query/helpers/order-by.js +21 -11
- package/lib/query/helpers/populate.js +35 -10
- package/lib/query/helpers/search.js +26 -12
- package/lib/query/helpers/transform.js +42 -14
- package/lib/query/helpers/where.js +92 -57
- package/lib/query/query-builder.js +116 -34
- package/lib/schema/__tests__/schema-diff.test.js +14 -1
- package/lib/schema/builder.js +315 -284
- package/lib/schema/diff.js +376 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +47 -50
- package/lib/schema/schema.js +21 -18
- package/lib/schema/storage.js +79 -0
- package/lib/utils/content-types.js +1 -2
- package/package.json +26 -21
- package/lib/configuration.js +0 -49
- package/lib/schema/schema-diff.js +0 -337
- package/lib/schema/schema-storage.js +0 -44
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const RESERVED_TABLE_NAMES = ['strapi_migrations', 'strapi_database_schema'];
|
|
6
|
+
|
|
7
|
+
const statuses = {
|
|
8
|
+
CHANGED: 'CHANGED',
|
|
9
|
+
UNCHANGED: 'UNCHANGED',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// NOTE:We could move the schema to use maps of tables & columns instead of arrays to make it easier to diff
|
|
13
|
+
// => this will make the creation a bit more complicated (ordering, Object.values(tables | columns)) -> not a big pbl
|
|
14
|
+
|
|
15
|
+
const helpers = {
|
|
16
|
+
hasTable(schema, tableName) {
|
|
17
|
+
return schema.tables.findIndex(table => table.name === tableName) !== -1;
|
|
18
|
+
},
|
|
19
|
+
findTable(schema, tableName) {
|
|
20
|
+
return schema.tables.find(table => table.name === tableName);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
hasColumn(table, columnName) {
|
|
24
|
+
return table.columns.findIndex(column => column.name === columnName) !== -1;
|
|
25
|
+
},
|
|
26
|
+
findColumn(table, columnName) {
|
|
27
|
+
return table.columns.find(column => column.name === columnName);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
hasIndex(table, columnName) {
|
|
31
|
+
return table.indexes.findIndex(column => column.name === columnName) !== -1;
|
|
32
|
+
},
|
|
33
|
+
findIndex(table, columnName) {
|
|
34
|
+
return table.indexes.find(column => column.name === columnName);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
hasForeignKey(table, columnName) {
|
|
38
|
+
return table.foreignKeys.findIndex(column => column.name === columnName) !== -1;
|
|
39
|
+
},
|
|
40
|
+
findForeignKey(table, columnName) {
|
|
41
|
+
return table.foreignKeys.find(column => column.name === columnName);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = db => {
|
|
46
|
+
const hasChangedStatus = diff => diff.status === statuses.CHANGED;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compares two indexes info
|
|
50
|
+
* @param {Object} oldIndex - index info read from DB
|
|
51
|
+
* @param {Object} index - newly generate index info
|
|
52
|
+
*/
|
|
53
|
+
const diffIndexes = (oldIndex, index) => {
|
|
54
|
+
const changes = [];
|
|
55
|
+
|
|
56
|
+
if (_.difference(oldIndex.columns, index.columns).length > 0) {
|
|
57
|
+
changes.push('columns');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (_.toLower(oldIndex.type) !== _.toLower(index.type)) {
|
|
61
|
+
changes.push('type');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
66
|
+
diff: {
|
|
67
|
+
name: index.name,
|
|
68
|
+
object: index,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compares two foreign keys info
|
|
75
|
+
* @param {Object} oldForeignKey - foreignKey info read from DB
|
|
76
|
+
* @param {Object} foreignKey - newly generate foreignKey info
|
|
77
|
+
*/
|
|
78
|
+
const diffForeignKeys = (oldForeignKey, foreignKey) => {
|
|
79
|
+
const changes = [];
|
|
80
|
+
|
|
81
|
+
if (_.difference(oldForeignKey.columns, foreignKey.columns).length > 0) {
|
|
82
|
+
changes.push('columns');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (_.difference(oldForeignKey.referencedColumns, foreignKey.referencedColumns).length > 0) {
|
|
86
|
+
changes.push('referencedColumns');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (oldForeignKey.referencedTable !== foreignKey.referencedTable) {
|
|
90
|
+
changes.push('referencedTable');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (_.isNil(oldForeignKey.onDelete) || _.toUpper(oldForeignKey.onDelete) === 'NO ACTION') {
|
|
94
|
+
if (!_.isNil(foreignKey.onDelete) && _.toUpper(oldForeignKey.onDelete) !== 'NO ACTION') {
|
|
95
|
+
changes.push('onDelete');
|
|
96
|
+
}
|
|
97
|
+
} else if (_.toUpper(oldForeignKey.onDelete) !== _.toUpper(foreignKey.onDelete)) {
|
|
98
|
+
changes.push('onDelete');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (_.isNil(oldForeignKey.onUpdate) || _.toUpper(oldForeignKey.onUpdate) === 'NO ACTION') {
|
|
102
|
+
if (!_.isNil(foreignKey.onUpdate) && _.toUpper(oldForeignKey.onUpdate) !== 'NO ACTION') {
|
|
103
|
+
changes.push('onUpdate');
|
|
104
|
+
}
|
|
105
|
+
} else if (_.toUpper(oldForeignKey.onUpdate) !== _.toUpper(foreignKey.onUpdate)) {
|
|
106
|
+
changes.push('onUpdate');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
111
|
+
diff: {
|
|
112
|
+
name: foreignKey.name,
|
|
113
|
+
object: foreignKey,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const diffDefault = (oldColumn, column) => {
|
|
119
|
+
const oldDefaultTo = oldColumn.defaultTo;
|
|
120
|
+
const defaultTo = column.defaultTo;
|
|
121
|
+
|
|
122
|
+
if (oldDefaultTo === null || _.toLower(oldDefaultTo) === 'null') {
|
|
123
|
+
return _.isNil(defaultTo) || _.toLower(defaultTo) === 'null';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
_.toLower(oldDefaultTo) === _.toLower(column.defaultTo) ||
|
|
128
|
+
_.toLower(oldDefaultTo) === _.toLower(`'${column.defaultTo}'`)
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compares two columns info
|
|
134
|
+
* @param {Object} oldColumn - column info read from DB
|
|
135
|
+
* @param {Object} column - newly generate column info
|
|
136
|
+
*/
|
|
137
|
+
const diffColumns = (oldColumn, column) => {
|
|
138
|
+
const changes = [];
|
|
139
|
+
|
|
140
|
+
const isIgnoredType = ['increments', 'enum'].includes(column.type);
|
|
141
|
+
|
|
142
|
+
// NOTE: enum aren't updated, they need to be dropped & recreated. Knex doesn't handle it
|
|
143
|
+
const oldType = oldColumn.type;
|
|
144
|
+
const type = db.dialect.getSqlType(column.type);
|
|
145
|
+
|
|
146
|
+
if (oldType !== type && !isIgnoredType) {
|
|
147
|
+
changes.push('type');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// NOTE: compare args at some point and split them into specific properties instead
|
|
151
|
+
|
|
152
|
+
if (oldColumn.notNullable !== column.notNullable) {
|
|
153
|
+
changes.push('notNullable');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const hasSameDefault = diffDefault(oldColumn, column);
|
|
157
|
+
if (!hasSameDefault) {
|
|
158
|
+
changes.push('defaultTo');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (oldColumn.unsigned !== column.unsigned && db.dialect.supportsUnsigned()) {
|
|
162
|
+
changes.push('unsigned');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
167
|
+
diff: {
|
|
168
|
+
name: column.name,
|
|
169
|
+
object: column,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const diffTableColumns = (srcTable, destTable) => {
|
|
175
|
+
const addedColumns = [];
|
|
176
|
+
const updatedColumns = [];
|
|
177
|
+
const unchangedColumns = [];
|
|
178
|
+
const removedColumns = [];
|
|
179
|
+
|
|
180
|
+
for (const destColumn of destTable.columns) {
|
|
181
|
+
if (!helpers.hasColumn(srcTable, destColumn.name)) {
|
|
182
|
+
addedColumns.push(destColumn);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const srcColumn = helpers.findColumn(srcTable, destColumn.name);
|
|
187
|
+
const { status, diff } = diffColumns(srcColumn, destColumn);
|
|
188
|
+
|
|
189
|
+
if (status === statuses.CHANGED) {
|
|
190
|
+
updatedColumns.push(diff);
|
|
191
|
+
} else {
|
|
192
|
+
unchangedColumns.push(srcColumn);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const srcColumn of srcTable.columns) {
|
|
197
|
+
if (!helpers.hasColumn(destTable, srcColumn.name)) {
|
|
198
|
+
removedColumns.push(srcColumn);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const hasChanged = [addedColumns, updatedColumns, removedColumns].some(arr => arr.length > 0);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
206
|
+
diff: {
|
|
207
|
+
added: addedColumns,
|
|
208
|
+
updated: updatedColumns,
|
|
209
|
+
unchanged: unchangedColumns,
|
|
210
|
+
removed: removedColumns,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const diffTableIndexes = (srcTable, destTable) => {
|
|
216
|
+
const addedIndexes = [];
|
|
217
|
+
const updatedIndexes = [];
|
|
218
|
+
const unchangedIndexes = [];
|
|
219
|
+
const removedIndexes = [];
|
|
220
|
+
|
|
221
|
+
for (const destIndex of destTable.indexes) {
|
|
222
|
+
if (helpers.hasIndex(srcTable, destIndex.name)) {
|
|
223
|
+
const srcIndex = helpers.findIndex(srcTable, destIndex.name);
|
|
224
|
+
const { status, diff } = diffIndexes(srcIndex, destIndex);
|
|
225
|
+
|
|
226
|
+
if (status === statuses.CHANGED) {
|
|
227
|
+
updatedIndexes.push(diff);
|
|
228
|
+
} else {
|
|
229
|
+
unchangedIndexes.push(srcIndex);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
addedIndexes.push(destIndex);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const srcIndex of srcTable.indexes) {
|
|
237
|
+
if (!helpers.hasIndex(destTable, srcIndex.name)) {
|
|
238
|
+
removedIndexes.push(srcIndex);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const hasChanged = [addedIndexes, updatedIndexes, removedIndexes].some(arr => arr.length > 0);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
246
|
+
diff: {
|
|
247
|
+
added: addedIndexes,
|
|
248
|
+
updated: updatedIndexes,
|
|
249
|
+
unchanged: unchangedIndexes,
|
|
250
|
+
removed: removedIndexes,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const diffTableForeignKeys = (srcTable, destTable) => {
|
|
256
|
+
const addedForeignKeys = [];
|
|
257
|
+
const updatedForeignKeys = [];
|
|
258
|
+
const unchangedForeignKeys = [];
|
|
259
|
+
const removedForeignKeys = [];
|
|
260
|
+
|
|
261
|
+
if (!db.dialect.usesForeignKeys()) {
|
|
262
|
+
return {
|
|
263
|
+
status: statuses.UNCHANGED,
|
|
264
|
+
diff: {
|
|
265
|
+
added: addedForeignKeys,
|
|
266
|
+
updated: updatedForeignKeys,
|
|
267
|
+
unchanged: unchangedForeignKeys,
|
|
268
|
+
removed: removedForeignKeys,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const destForeignKey of destTable.foreignKeys) {
|
|
274
|
+
if (helpers.hasForeignKey(srcTable, destForeignKey.name)) {
|
|
275
|
+
const srcForeignKey = helpers.findForeignKey(srcTable, destForeignKey.name);
|
|
276
|
+
const { status, diff } = diffForeignKeys(srcForeignKey, destForeignKey);
|
|
277
|
+
|
|
278
|
+
if (status === statuses.CHANGED) {
|
|
279
|
+
updatedForeignKeys.push(diff);
|
|
280
|
+
} else {
|
|
281
|
+
unchangedForeignKeys.push(srcForeignKey);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
addedForeignKeys.push(destForeignKey);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const srcForeignKey of srcTable.foreignKeys) {
|
|
289
|
+
if (!helpers.hasForeignKey(destTable, srcForeignKey.name)) {
|
|
290
|
+
removedForeignKeys.push(srcForeignKey);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const hasChanged = [addedForeignKeys, updatedForeignKeys, removedForeignKeys].some(
|
|
295
|
+
arr => arr.length > 0
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
300
|
+
diff: {
|
|
301
|
+
added: addedForeignKeys,
|
|
302
|
+
updated: updatedForeignKeys,
|
|
303
|
+
unchanged: unchangedForeignKeys,
|
|
304
|
+
removed: removedForeignKeys,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const diffTables = (srcTable, destTable) => {
|
|
310
|
+
const columnsDiff = diffTableColumns(srcTable, destTable);
|
|
311
|
+
const indexesDiff = diffTableIndexes(srcTable, destTable);
|
|
312
|
+
const foreignKeysDiff = diffTableForeignKeys(srcTable, destTable);
|
|
313
|
+
|
|
314
|
+
const hasChanged = [columnsDiff, indexesDiff, foreignKeysDiff].some(hasChangedStatus);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
318
|
+
diff: {
|
|
319
|
+
name: srcTable.name,
|
|
320
|
+
indexes: indexesDiff.diff,
|
|
321
|
+
foreignKeys: foreignKeysDiff.diff,
|
|
322
|
+
columns: columnsDiff.diff,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const diffSchemas = (srcSchema, destSchema) => {
|
|
328
|
+
const addedTables = [];
|
|
329
|
+
const updatedTables = [];
|
|
330
|
+
const unchangedTables = [];
|
|
331
|
+
const removedTables = [];
|
|
332
|
+
|
|
333
|
+
for (const destTable of destSchema.tables) {
|
|
334
|
+
if (helpers.hasTable(srcSchema, destTable.name)) {
|
|
335
|
+
const srcTable = helpers.findTable(srcSchema, destTable.name);
|
|
336
|
+
|
|
337
|
+
const { status, diff } = diffTables(srcTable, destTable);
|
|
338
|
+
|
|
339
|
+
if (status === statuses.CHANGED) {
|
|
340
|
+
updatedTables.push(diff);
|
|
341
|
+
} else {
|
|
342
|
+
unchangedTables.push(srcTable);
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
addedTables.push(destTable);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
for (const srcTable of srcSchema.tables) {
|
|
350
|
+
if (
|
|
351
|
+
!helpers.hasTable(destSchema, srcTable.name) &&
|
|
352
|
+
!RESERVED_TABLE_NAMES.includes(srcTable.name)
|
|
353
|
+
) {
|
|
354
|
+
removedTables.push(srcTable);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const hasChanged = [addedTables, updatedTables, removedTables].some(arr => arr.length > 0);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
362
|
+
diff: {
|
|
363
|
+
tables: {
|
|
364
|
+
added: addedTables,
|
|
365
|
+
updated: updatedTables,
|
|
366
|
+
unchanged: unchangedTables,
|
|
367
|
+
removed: removedTables,
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
diff: diffSchemas,
|
|
375
|
+
};
|
|
376
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Database } from '../';
|
|
2
|
+
import { Action } from '../lifecycles';
|
|
3
|
+
|
|
4
|
+
type Type =
|
|
5
|
+
| 'string'
|
|
6
|
+
| 'text'
|
|
7
|
+
| 'richtext'
|
|
8
|
+
| 'json'
|
|
9
|
+
| 'enumeration'
|
|
10
|
+
| 'password'
|
|
11
|
+
| 'email'
|
|
12
|
+
| 'integer'
|
|
13
|
+
| 'biginteger'
|
|
14
|
+
| 'float'
|
|
15
|
+
| 'decimal'
|
|
16
|
+
| 'date'
|
|
17
|
+
| 'time'
|
|
18
|
+
| 'datetime'
|
|
19
|
+
| 'timestamp'
|
|
20
|
+
| 'boolean'
|
|
21
|
+
| 'relation';
|
|
22
|
+
|
|
23
|
+
export interface Attribute {
|
|
24
|
+
type: Type;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Model {
|
|
28
|
+
uid: string;
|
|
29
|
+
tableName: string;
|
|
30
|
+
attributes: {
|
|
31
|
+
id: {
|
|
32
|
+
type: 'increments';
|
|
33
|
+
};
|
|
34
|
+
[k: string]: Attribute;
|
|
35
|
+
};
|
|
36
|
+
lifecycles?: {
|
|
37
|
+
[k in Action]: () => void;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SchemaProvideer {
|
|
42
|
+
sync(): Promise<void>;
|
|
43
|
+
syncSchema(): Promise<void>;
|
|
44
|
+
reset(): Promise<void>;
|
|
45
|
+
create(): Promise<void>;
|
|
46
|
+
drop(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default function(db: Database): SchemaProvideer;
|
package/lib/schema/index.js
CHANGED
|
@@ -1,49 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const debug = require('debug')('strapi::database');
|
|
4
|
+
|
|
3
5
|
const createSchemaBuilder = require('./builder');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const { metadataToSchema
|
|
7
|
-
|
|
8
|
-
const addInternalTables = schema => {
|
|
9
|
-
schema.addTable(
|
|
10
|
-
createTable({
|
|
11
|
-
tableName: 'strapi_database_schema',
|
|
12
|
-
attributes: {
|
|
13
|
-
id: {
|
|
14
|
-
type: 'increments',
|
|
15
|
-
},
|
|
16
|
-
schema: {
|
|
17
|
-
type: 'json',
|
|
18
|
-
},
|
|
19
|
-
createdAt: {
|
|
20
|
-
type: 'datetime',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
);
|
|
25
|
-
};
|
|
6
|
+
const createSchemaDiff = require('./diff');
|
|
7
|
+
const createSchemaStorage = require('./storage');
|
|
8
|
+
const { metadataToSchema } = require('./schema');
|
|
26
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @type {import('.').default}
|
|
12
|
+
*/
|
|
27
13
|
const createSchemaProvider = db => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
// Add Internal tables to schema
|
|
31
|
-
addInternalTables(currentSchema);
|
|
14
|
+
const schema = metadataToSchema(db.metadata);
|
|
32
15
|
|
|
33
16
|
return {
|
|
34
17
|
builder: createSchemaBuilder(db),
|
|
18
|
+
schemaDiff: createSchemaDiff(db),
|
|
35
19
|
schemaStorage: createSchemaStorage(db),
|
|
36
20
|
|
|
37
21
|
/**
|
|
38
22
|
* Drops the database schema
|
|
39
23
|
*/
|
|
40
24
|
async drop() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!DBSchema) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
25
|
+
debug('Dropping database schema');
|
|
46
26
|
|
|
27
|
+
const DBSchema = await db.dialect.schemaInspector.getSchema();
|
|
47
28
|
await this.builder.dropSchema(DBSchema);
|
|
48
29
|
},
|
|
49
30
|
|
|
@@ -51,46 +32,62 @@ const createSchemaProvider = db => {
|
|
|
51
32
|
* Creates the database schema
|
|
52
33
|
*/
|
|
53
34
|
async create() {
|
|
54
|
-
|
|
55
|
-
await this.
|
|
35
|
+
debug('Created database schema');
|
|
36
|
+
await this.builder.createSchema(schema);
|
|
56
37
|
},
|
|
57
38
|
|
|
58
39
|
/**
|
|
59
40
|
* Resets the database schema
|
|
60
41
|
*/
|
|
61
42
|
async reset() {
|
|
43
|
+
debug('Resetting database schema');
|
|
62
44
|
await this.drop();
|
|
63
45
|
await this.create();
|
|
64
46
|
},
|
|
65
47
|
|
|
48
|
+
async syncSchema() {
|
|
49
|
+
debug('Synchronizing database schema');
|
|
50
|
+
|
|
51
|
+
const DBSchema = await db.dialect.schemaInspector.getSchema();
|
|
52
|
+
|
|
53
|
+
const { status, diff } = this.schemaDiff.diff(DBSchema, schema);
|
|
54
|
+
|
|
55
|
+
if (status === 'CHANGED') {
|
|
56
|
+
await this.builder.updateSchema(diff);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await this.schemaStorage.add(schema);
|
|
60
|
+
},
|
|
61
|
+
|
|
66
62
|
// TODO: support options to migrate softly or forcefully
|
|
67
63
|
// TODO: support option to disable auto migration & run a CLI command instead to avoid doing it at startup
|
|
64
|
+
// TODO: Allow keeping extra indexes / extra tables / extra columns (globally or on a per table basis)
|
|
68
65
|
async sync() {
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
if (await db.migrations.shouldRun()) {
|
|
67
|
+
debug('Found migrations to run');
|
|
68
|
+
await db.migrations.up();
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
return this.create();
|
|
70
|
+
return this.syncSchema();
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
|
|
73
|
+
const oldSchema = await this.schemaStorage.read();
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
if (!oldSchema) {
|
|
76
|
+
debug('Schema not persisted yet');
|
|
77
|
+
return this.syncSchema();
|
|
78
|
+
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
const
|
|
80
|
+
const { hash: oldHash } = oldSchema;
|
|
81
|
+
const hash = await this.schemaStorage.hashSchema(schema);
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// NOTE: should we still update the schema in DB ?
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
83
|
+
if (oldHash !== hash) {
|
|
84
|
+
debug('Schema changed');
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
return this.syncSchema();
|
|
87
|
+
}
|
|
91
88
|
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
debug('Schema unchanged');
|
|
90
|
+
return;
|
|
94
91
|
},
|
|
95
92
|
};
|
|
96
93
|
};
|
package/lib/schema/schema.js
CHANGED
|
@@ -9,19 +9,16 @@ const createColumn = (name, attribute) => {
|
|
|
9
9
|
name,
|
|
10
10
|
type,
|
|
11
11
|
args,
|
|
12
|
+
defaultTo: null,
|
|
13
|
+
notNullable: false,
|
|
14
|
+
unsigned: false,
|
|
12
15
|
...opts,
|
|
13
16
|
...(attribute.column || {}),
|
|
14
|
-
// TODO: allow passing custom params to the DB from the model definition
|
|
15
17
|
};
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
const shouldCreateColumn = attribute => {
|
|
19
|
-
return types.isScalar(attribute.type);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
20
|
const createTable = meta => {
|
|
23
21
|
const table = {
|
|
24
|
-
// TODO: allow passing custom params to the DB from the model definition
|
|
25
22
|
name: meta.tableName,
|
|
26
23
|
indexes: meta.indexes || [],
|
|
27
24
|
foreignKeys: meta.foreignKeys || [],
|
|
@@ -67,9 +64,14 @@ const createTable = meta => {
|
|
|
67
64
|
// NOTE: could allow configuration
|
|
68
65
|
onDelete: 'SET NULL',
|
|
69
66
|
});
|
|
67
|
+
|
|
68
|
+
table.indexes.push({
|
|
69
|
+
name: `${table.name}_${columnName}_fk`,
|
|
70
|
+
columns: [columnName],
|
|
71
|
+
});
|
|
70
72
|
}
|
|
71
|
-
} else if (
|
|
72
|
-
const column = createColumn(key,
|
|
73
|
+
} else if (types.isScalar(attribute.type)) {
|
|
74
|
+
const column = createColumn(attribute.columnName || key, attribute);
|
|
73
75
|
|
|
74
76
|
if (column.unique) {
|
|
75
77
|
table.indexes.push({
|
|
@@ -101,8 +103,13 @@ const getColumnType = attribute => {
|
|
|
101
103
|
|
|
102
104
|
switch (attribute.type) {
|
|
103
105
|
case 'increments': {
|
|
104
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
type: 'increments',
|
|
108
|
+
args: [{ primary: true }],
|
|
109
|
+
notNullable: true,
|
|
110
|
+
};
|
|
105
111
|
}
|
|
112
|
+
|
|
106
113
|
// We might want to convert email/password to string types before going into the orm with specific validators & transformers
|
|
107
114
|
case 'password':
|
|
108
115
|
case 'email':
|
|
@@ -129,29 +136,26 @@ const getColumnType = attribute => {
|
|
|
129
136
|
return {
|
|
130
137
|
type: 'enum',
|
|
131
138
|
args: [
|
|
132
|
-
attribute.enum
|
|
139
|
+
attribute.enum,
|
|
140
|
+
/*,{ useNative: true, existingType: true, enumName: 'foo_type', schemaName: 'public' }*/
|
|
133
141
|
],
|
|
134
142
|
};
|
|
135
143
|
}
|
|
136
|
-
|
|
137
144
|
case 'integer': {
|
|
138
145
|
return { type: 'integer' };
|
|
139
146
|
}
|
|
140
147
|
case 'biginteger': {
|
|
141
148
|
return { type: 'bigInteger' };
|
|
142
149
|
}
|
|
143
|
-
// TODO: verify usage of double vs float
|
|
144
150
|
case 'float': {
|
|
145
|
-
return { type: 'double'
|
|
151
|
+
return { type: 'double' };
|
|
146
152
|
}
|
|
147
|
-
// TODO: define precision
|
|
148
153
|
case 'decimal': {
|
|
149
154
|
return { type: 'decimal', args: [10, 2] };
|
|
150
155
|
}
|
|
151
156
|
case 'date': {
|
|
152
157
|
return { type: 'date' };
|
|
153
158
|
}
|
|
154
|
-
// TODO: define precision
|
|
155
159
|
case 'time': {
|
|
156
160
|
return { type: 'time', args: [{ precision: 3 }] };
|
|
157
161
|
}
|
|
@@ -161,19 +165,18 @@ const getColumnType = attribute => {
|
|
|
161
165
|
args: [
|
|
162
166
|
{
|
|
163
167
|
useTz: false,
|
|
164
|
-
precision: 6,
|
|
168
|
+
precision: 6,
|
|
165
169
|
},
|
|
166
170
|
],
|
|
167
171
|
};
|
|
168
172
|
}
|
|
169
|
-
// TODO: handle defaults
|
|
170
173
|
case 'timestamp': {
|
|
171
174
|
return {
|
|
172
175
|
type: 'timestamp',
|
|
173
176
|
args: [
|
|
174
177
|
{
|
|
175
178
|
useTz: false,
|
|
176
|
-
precision: 6,
|
|
179
|
+
precision: 6,
|
|
177
180
|
},
|
|
178
181
|
],
|
|
179
182
|
};
|