@strapi/database 4.0.0-next.9 → 4.0.3
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/jest.config.js +10 -0
- package/lib/dialects/dialect.js +45 -0
- package/lib/dialects/index.js +7 -113
- 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 +73 -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 +44 -2
- package/lib/fields.d.ts +2 -3
- package/lib/fields.js +7 -16
- package/lib/index.d.ts +53 -8
- 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 +17 -1
- 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 +374 -0
- package/lib/schema/index.d.ts +49 -0
- package/lib/schema/index.js +47 -50
- package/lib/schema/schema.js +22 -27
- package/lib/schema/storage.js +79 -0
- package/lib/utils/content-types.js +0 -1
- package/package.json +27 -21
- package/examples/data.sqlite +0 -0
- 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,374 @@
|
|
|
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'].includes(column.type);
|
|
141
|
+
const oldType = oldColumn.type;
|
|
142
|
+
const type = db.dialect.getSqlType(column.type);
|
|
143
|
+
|
|
144
|
+
if (oldType !== type && !isIgnoredType) {
|
|
145
|
+
changes.push('type');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// NOTE: compare args at some point and split them into specific properties instead
|
|
149
|
+
|
|
150
|
+
if (oldColumn.notNullable !== column.notNullable) {
|
|
151
|
+
changes.push('notNullable');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const hasSameDefault = diffDefault(oldColumn, column);
|
|
155
|
+
if (!hasSameDefault) {
|
|
156
|
+
changes.push('defaultTo');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (oldColumn.unsigned !== column.unsigned && db.dialect.supportsUnsigned()) {
|
|
160
|
+
changes.push('unsigned');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
165
|
+
diff: {
|
|
166
|
+
name: column.name,
|
|
167
|
+
object: column,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const diffTableColumns = (srcTable, destTable) => {
|
|
173
|
+
const addedColumns = [];
|
|
174
|
+
const updatedColumns = [];
|
|
175
|
+
const unchangedColumns = [];
|
|
176
|
+
const removedColumns = [];
|
|
177
|
+
|
|
178
|
+
for (const destColumn of destTable.columns) {
|
|
179
|
+
if (!helpers.hasColumn(srcTable, destColumn.name)) {
|
|
180
|
+
addedColumns.push(destColumn);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const srcColumn = helpers.findColumn(srcTable, destColumn.name);
|
|
185
|
+
const { status, diff } = diffColumns(srcColumn, destColumn);
|
|
186
|
+
|
|
187
|
+
if (status === statuses.CHANGED) {
|
|
188
|
+
updatedColumns.push(diff);
|
|
189
|
+
} else {
|
|
190
|
+
unchangedColumns.push(srcColumn);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const srcColumn of srcTable.columns) {
|
|
195
|
+
if (!helpers.hasColumn(destTable, srcColumn.name)) {
|
|
196
|
+
removedColumns.push(srcColumn);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const hasChanged = [addedColumns, updatedColumns, removedColumns].some(arr => arr.length > 0);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
204
|
+
diff: {
|
|
205
|
+
added: addedColumns,
|
|
206
|
+
updated: updatedColumns,
|
|
207
|
+
unchanged: unchangedColumns,
|
|
208
|
+
removed: removedColumns,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const diffTableIndexes = (srcTable, destTable) => {
|
|
214
|
+
const addedIndexes = [];
|
|
215
|
+
const updatedIndexes = [];
|
|
216
|
+
const unchangedIndexes = [];
|
|
217
|
+
const removedIndexes = [];
|
|
218
|
+
|
|
219
|
+
for (const destIndex of destTable.indexes) {
|
|
220
|
+
if (helpers.hasIndex(srcTable, destIndex.name)) {
|
|
221
|
+
const srcIndex = helpers.findIndex(srcTable, destIndex.name);
|
|
222
|
+
const { status, diff } = diffIndexes(srcIndex, destIndex);
|
|
223
|
+
|
|
224
|
+
if (status === statuses.CHANGED) {
|
|
225
|
+
updatedIndexes.push(diff);
|
|
226
|
+
} else {
|
|
227
|
+
unchangedIndexes.push(srcIndex);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
addedIndexes.push(destIndex);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const srcIndex of srcTable.indexes) {
|
|
235
|
+
if (!helpers.hasIndex(destTable, srcIndex.name)) {
|
|
236
|
+
removedIndexes.push(srcIndex);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const hasChanged = [addedIndexes, updatedIndexes, removedIndexes].some(arr => arr.length > 0);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
244
|
+
diff: {
|
|
245
|
+
added: addedIndexes,
|
|
246
|
+
updated: updatedIndexes,
|
|
247
|
+
unchanged: unchangedIndexes,
|
|
248
|
+
removed: removedIndexes,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const diffTableForeignKeys = (srcTable, destTable) => {
|
|
254
|
+
const addedForeignKeys = [];
|
|
255
|
+
const updatedForeignKeys = [];
|
|
256
|
+
const unchangedForeignKeys = [];
|
|
257
|
+
const removedForeignKeys = [];
|
|
258
|
+
|
|
259
|
+
if (!db.dialect.usesForeignKeys()) {
|
|
260
|
+
return {
|
|
261
|
+
status: statuses.UNCHANGED,
|
|
262
|
+
diff: {
|
|
263
|
+
added: addedForeignKeys,
|
|
264
|
+
updated: updatedForeignKeys,
|
|
265
|
+
unchanged: unchangedForeignKeys,
|
|
266
|
+
removed: removedForeignKeys,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (const destForeignKey of destTable.foreignKeys) {
|
|
272
|
+
if (helpers.hasForeignKey(srcTable, destForeignKey.name)) {
|
|
273
|
+
const srcForeignKey = helpers.findForeignKey(srcTable, destForeignKey.name);
|
|
274
|
+
const { status, diff } = diffForeignKeys(srcForeignKey, destForeignKey);
|
|
275
|
+
|
|
276
|
+
if (status === statuses.CHANGED) {
|
|
277
|
+
updatedForeignKeys.push(diff);
|
|
278
|
+
} else {
|
|
279
|
+
unchangedForeignKeys.push(srcForeignKey);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
addedForeignKeys.push(destForeignKey);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const srcForeignKey of srcTable.foreignKeys) {
|
|
287
|
+
if (!helpers.hasForeignKey(destTable, srcForeignKey.name)) {
|
|
288
|
+
removedForeignKeys.push(srcForeignKey);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const hasChanged = [addedForeignKeys, updatedForeignKeys, removedForeignKeys].some(
|
|
293
|
+
arr => arr.length > 0
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
298
|
+
diff: {
|
|
299
|
+
added: addedForeignKeys,
|
|
300
|
+
updated: updatedForeignKeys,
|
|
301
|
+
unchanged: unchangedForeignKeys,
|
|
302
|
+
removed: removedForeignKeys,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const diffTables = (srcTable, destTable) => {
|
|
308
|
+
const columnsDiff = diffTableColumns(srcTable, destTable);
|
|
309
|
+
const indexesDiff = diffTableIndexes(srcTable, destTable);
|
|
310
|
+
const foreignKeysDiff = diffTableForeignKeys(srcTable, destTable);
|
|
311
|
+
|
|
312
|
+
const hasChanged = [columnsDiff, indexesDiff, foreignKeysDiff].some(hasChangedStatus);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
316
|
+
diff: {
|
|
317
|
+
name: srcTable.name,
|
|
318
|
+
indexes: indexesDiff.diff,
|
|
319
|
+
foreignKeys: foreignKeysDiff.diff,
|
|
320
|
+
columns: columnsDiff.diff,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const diffSchemas = (srcSchema, destSchema) => {
|
|
326
|
+
const addedTables = [];
|
|
327
|
+
const updatedTables = [];
|
|
328
|
+
const unchangedTables = [];
|
|
329
|
+
const removedTables = [];
|
|
330
|
+
|
|
331
|
+
for (const destTable of destSchema.tables) {
|
|
332
|
+
if (helpers.hasTable(srcSchema, destTable.name)) {
|
|
333
|
+
const srcTable = helpers.findTable(srcSchema, destTable.name);
|
|
334
|
+
|
|
335
|
+
const { status, diff } = diffTables(srcTable, destTable);
|
|
336
|
+
|
|
337
|
+
if (status === statuses.CHANGED) {
|
|
338
|
+
updatedTables.push(diff);
|
|
339
|
+
} else {
|
|
340
|
+
unchangedTables.push(srcTable);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
addedTables.push(destTable);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const srcTable of srcSchema.tables) {
|
|
348
|
+
if (
|
|
349
|
+
!helpers.hasTable(destSchema, srcTable.name) &&
|
|
350
|
+
!RESERVED_TABLE_NAMES.includes(srcTable.name)
|
|
351
|
+
) {
|
|
352
|
+
removedTables.push(srcTable);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const hasChanged = [addedTables, updatedTables, removedTables].some(arr => arr.length > 0);
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
360
|
+
diff: {
|
|
361
|
+
tables: {
|
|
362
|
+
added: addedTables,
|
|
363
|
+
updated: updatedTables,
|
|
364
|
+
unchanged: unchangedTables,
|
|
365
|
+
removed: removedTables,
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
diff: diffSchemas,
|
|
373
|
+
};
|
|
374
|
+
};
|
|
@@ -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,12 +103,18 @@ 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':
|
|
109
|
-
case 'string':
|
|
116
|
+
case 'string':
|
|
117
|
+
case 'enumeration': {
|
|
110
118
|
return { type: 'string' };
|
|
111
119
|
}
|
|
112
120
|
case 'uid': {
|
|
@@ -125,33 +133,21 @@ const getColumnType = attribute => {
|
|
|
125
133
|
case 'json': {
|
|
126
134
|
return { type: 'jsonb' };
|
|
127
135
|
}
|
|
128
|
-
case 'enumeration': {
|
|
129
|
-
return {
|
|
130
|
-
type: 'enum',
|
|
131
|
-
args: [
|
|
132
|
-
attribute.enum /*,{ useNative: true, existingType: true, enumName: 'foo_type', schemaName: 'public' }*/,
|
|
133
|
-
],
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
136
|
case 'integer': {
|
|
138
137
|
return { type: 'integer' };
|
|
139
138
|
}
|
|
140
139
|
case 'biginteger': {
|
|
141
140
|
return { type: 'bigInteger' };
|
|
142
141
|
}
|
|
143
|
-
// TODO: verify usage of double vs float
|
|
144
142
|
case 'float': {
|
|
145
|
-
return { type: 'double'
|
|
143
|
+
return { type: 'double' };
|
|
146
144
|
}
|
|
147
|
-
// TODO: define precision
|
|
148
145
|
case 'decimal': {
|
|
149
146
|
return { type: 'decimal', args: [10, 2] };
|
|
150
147
|
}
|
|
151
148
|
case 'date': {
|
|
152
149
|
return { type: 'date' };
|
|
153
150
|
}
|
|
154
|
-
// TODO: define precision
|
|
155
151
|
case 'time': {
|
|
156
152
|
return { type: 'time', args: [{ precision: 3 }] };
|
|
157
153
|
}
|
|
@@ -161,19 +157,18 @@ const getColumnType = attribute => {
|
|
|
161
157
|
args: [
|
|
162
158
|
{
|
|
163
159
|
useTz: false,
|
|
164
|
-
precision: 6,
|
|
160
|
+
precision: 6,
|
|
165
161
|
},
|
|
166
162
|
],
|
|
167
163
|
};
|
|
168
164
|
}
|
|
169
|
-
// TODO: handle defaults
|
|
170
165
|
case 'timestamp': {
|
|
171
166
|
return {
|
|
172
167
|
type: 'timestamp',
|
|
173
168
|
args: [
|
|
174
169
|
{
|
|
175
170
|
useTz: false,
|
|
176
|
-
precision: 6,
|
|
171
|
+
precision: 6,
|
|
177
172
|
},
|
|
178
173
|
],
|
|
179
174
|
};
|
|
@@ -182,7 +177,7 @@ const getColumnType = attribute => {
|
|
|
182
177
|
return { type: 'boolean' };
|
|
183
178
|
}
|
|
184
179
|
default: {
|
|
185
|
-
throw new Error(`
|
|
180
|
+
throw new Error(`Unknown type ${attribute.type}`);
|
|
186
181
|
}
|
|
187
182
|
}
|
|
188
183
|
};
|