mythix 2.5.4 → 2.5.6
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/package.json +3 -2
- package/src/application.js +5 -5
- package/src/cli/cli-utils.d.ts +14 -0
- package/src/cli/cli-utils.js +167 -70
- package/src/cli/command-executor.js +31 -0
- package/src/cli/deploy-command.js +949 -0
- package/src/cli/generators/generate-command.js +149 -0
- package/src/cli/generators/migration-generator.js +209 -0
- package/src/cli/{migrations/migrate-command.js → migrate-command.js} +2 -2
- package/src/models/model-utils.js +1 -1
- package/src/modules/database-module.js +1 -1
- package/src/tasks/task-utils.js +2 -2
- package/src/utils/file-utils.js +10 -1
- package/src/utils/test-utils.js +1 -2
- package/src/cli/migrations/makemigrations-command.js +0 -1200
|
@@ -1,1200 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const Path = require('path');
|
|
4
|
-
const FileSystem = require('fs');
|
|
5
|
-
const prompts = require('prompts');
|
|
6
|
-
const Nife = require('nife');
|
|
7
|
-
const { defineCommand } = require('../cli-utils');
|
|
8
|
-
|
|
9
|
-
function generateMigration(migrationID, upCode, downCode, preCode, postCode) {
|
|
10
|
-
let template =
|
|
11
|
-
`
|
|
12
|
-
const MIGRATION_ID = '${migrationID}';
|
|
13
|
-
|
|
14
|
-
module.exports = {
|
|
15
|
-
MIGRATION_ID,
|
|
16
|
-
up: async function(connection) {
|
|
17
|
-
${preCode}
|
|
18
|
-
try {
|
|
19
|
-
${upCode}
|
|
20
|
-
} finally {
|
|
21
|
-
${postCode}
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
down: async function(connection) {
|
|
25
|
-
${preCode}
|
|
26
|
-
try {
|
|
27
|
-
${downCode}
|
|
28
|
-
} finally {
|
|
29
|
-
${postCode}
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
`;
|
|
34
|
-
|
|
35
|
-
return template;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = defineCommand('makemigrations', ({ Parent }) => {
|
|
39
|
-
return class MakeMigrationsCommand extends Parent {
|
|
40
|
-
static commandArguments() {
|
|
41
|
-
return {
|
|
42
|
-
help: {
|
|
43
|
-
// eslint-disable-next-line key-spacing
|
|
44
|
-
'@usage': 'mythix-cli makemigrations [options]',
|
|
45
|
-
'@title': 'Generate a migration file',
|
|
46
|
-
'-n={name} | -n {name} | --name={name} | --name {name}': 'Specify the name of the generated migration file',
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getRevisionNumber() {
|
|
52
|
-
let date = new Date();
|
|
53
|
-
return date.toISOString().replace(/\.[^.]+$/, '').replace(/\D/g, '');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
convertDBTypeToLocalType(dialect, type) {
|
|
57
|
-
if (typeof type !== 'string')
|
|
58
|
-
return type;
|
|
59
|
-
|
|
60
|
-
switch (dialect) {
|
|
61
|
-
case 'postgres':
|
|
62
|
-
case 'db2':
|
|
63
|
-
return type.replace(/CHARACTER\s+VARYING/, 'VARCHAR').replace(/TINYINT\s*\(\s*1\s*\)/, 'BOOLEAN');
|
|
64
|
-
case 'mssql':
|
|
65
|
-
return type.replace(/NVARCHAR/, 'VARCHAR').replace(/TINYINT\s*\(\s*1\s*\)/, 'BIT');
|
|
66
|
-
case 'ibmi':
|
|
67
|
-
return type.replace(/TINYINT\s*\(\s*1\s*\)/, 'SMALLINT');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
convertDBTypesToLocalTypes(dialect, attributes) {
|
|
72
|
-
if (attributes == null)
|
|
73
|
-
return attributes;
|
|
74
|
-
|
|
75
|
-
let keys = Object.keys(attributes);
|
|
76
|
-
let mappedAttributes = {};
|
|
77
|
-
|
|
78
|
-
for (let i = 0, il = keys.length; i < il; i++) {
|
|
79
|
-
let key = keys[i];
|
|
80
|
-
let value = attributes[key];
|
|
81
|
-
|
|
82
|
-
if (typeof value === 'string') {
|
|
83
|
-
mappedAttributes[key] = this.convertDBTypeToLocalType(dialect, value);
|
|
84
|
-
} else {
|
|
85
|
-
mappedAttributes[key] = Object.assign({}, value, {
|
|
86
|
-
type: this.convertDBTypeToLocalType(dialect, value.type),
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return mappedAttributes;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async getDBSchema(connection) {
|
|
95
|
-
let queryInterface = connection.getQueryInterface();
|
|
96
|
-
let promises = [];
|
|
97
|
-
let options = {};
|
|
98
|
-
let models = [];
|
|
99
|
-
let dbTableSchema = {};
|
|
100
|
-
|
|
101
|
-
// First, fetch all tables from DB, and all info for these tables
|
|
102
|
-
let allDBTables = await queryInterface.showAllTables(options);
|
|
103
|
-
for (let i = 0, il = allDBTables.length; i < il; i++) {
|
|
104
|
-
let dbTableName = allDBTables[i];
|
|
105
|
-
|
|
106
|
-
promises.push(Promise.allSettled([
|
|
107
|
-
queryInterface.describeTable(dbTableName, options),
|
|
108
|
-
queryInterface.getForeignKeyReferencesForTable(dbTableName, options),
|
|
109
|
-
queryInterface.showIndex(dbTableName, options),
|
|
110
|
-
]));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Now wait on the results from the DB
|
|
114
|
-
let results = await Promise.allSettled(promises);
|
|
115
|
-
results = results.map((result) => {
|
|
116
|
-
if (result.status === 'rejected')
|
|
117
|
-
return null;
|
|
118
|
-
|
|
119
|
-
return result.value.map((subResult) => {
|
|
120
|
-
if (subResult.status === 'rejected')
|
|
121
|
-
return null;
|
|
122
|
-
|
|
123
|
-
return subResult.value;
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Compile all table schemas into an object, keyed by table name
|
|
128
|
-
for (let i = 0, il = results.length; i < il; i++) {
|
|
129
|
-
let dbTableName = allDBTables[i];
|
|
130
|
-
let result = results[i];
|
|
131
|
-
|
|
132
|
-
dbTableSchema[dbTableName] = {
|
|
133
|
-
attributes: this.convertDBTypesToLocalTypes(connection.getDialect(), result[0]),
|
|
134
|
-
foreignKeys: result[1],
|
|
135
|
-
indexes: result[2],
|
|
136
|
-
types: this.convertDBTypesToLocalTypes(connection.getDialect(), queryInterface.queryGenerator.attributesToSQL(result[0], options)),
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Now build all model information
|
|
141
|
-
connection.modelManager.forEachModel((Model) => {
|
|
142
|
-
let modelName = Model.getModelName();
|
|
143
|
-
let tableName = Model.getTableName(options);
|
|
144
|
-
|
|
145
|
-
models.push({
|
|
146
|
-
modelTypes: queryInterface.queryGenerator.attributesToSQL(Model.tableAttributes, options),
|
|
147
|
-
tableAttributes: Model.tableAttributes,
|
|
148
|
-
rawAttributes: Model.fieldRawAttributesMap,
|
|
149
|
-
Model,
|
|
150
|
-
modelName,
|
|
151
|
-
tableName,
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return { allDBTables, dbTableSchema, models };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async calculateModelSchemaDifferences(connection, modelSchema, dbSchema, options) {
|
|
159
|
-
const isColumnAltered = (fieldName, columnName, column, dbColumn) => {
|
|
160
|
-
const nullish = (_value, toString) => {
|
|
161
|
-
let value = (_value == null) ? null : _value;
|
|
162
|
-
return (toString && value != null) ? ('' + value) : value;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
if (columnName !== (column.field || fieldName))
|
|
166
|
-
return true;
|
|
167
|
-
|
|
168
|
-
if (column.autoIncrement !== true && Nife.get(column, 'defaultValue.key') === undefined && !Nife.instanceOf(column.defaultValue, 'function', 'object', 'array') && nullish(column.defaultValue, true) !== nullish(dbColumn.defaultValue, true))
|
|
169
|
-
return true;
|
|
170
|
-
|
|
171
|
-
if (nullish(column.comment) !== nullish(dbColumn.comment))
|
|
172
|
-
return true;
|
|
173
|
-
|
|
174
|
-
if (nullish(column.allowNull) !== nullish(dbColumn.allowNull))
|
|
175
|
-
return true;
|
|
176
|
-
|
|
177
|
-
if (column.type.toString() !== dbColumn.type)
|
|
178
|
-
return true;
|
|
179
|
-
|
|
180
|
-
return false;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
let tableAttributes = modelSchema.tableAttributes;
|
|
184
|
-
let Model = modelSchema.Model;
|
|
185
|
-
let tableName = Model.getTableName();
|
|
186
|
-
let fieldRawAttributesMap = Model.fieldRawAttributesMap;
|
|
187
|
-
let rawAttributeMapKeys = Object.keys(fieldRawAttributesMap);
|
|
188
|
-
let dbTableInfo = dbSchema.dbTableSchema[tableName];
|
|
189
|
-
let dbAttributes = (dbTableInfo) ? dbTableInfo.attributes : null;
|
|
190
|
-
let fieldNames = Object.keys(tableAttributes);
|
|
191
|
-
let schemaChanged = false;
|
|
192
|
-
|
|
193
|
-
const fieldNameToDBColumnName = (fieldName) => {
|
|
194
|
-
for (let i = 0, il = rawAttributeMapKeys.length; i < il; i++) {
|
|
195
|
-
let columnName = rawAttributeMapKeys[i];
|
|
196
|
-
let field = fieldRawAttributesMap[columnName];
|
|
197
|
-
|
|
198
|
-
if (field.fieldName === fieldName)
|
|
199
|
-
return columnName;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return null;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const isNewModel = (dbSchema, tableName) => {
|
|
206
|
-
let allDBTables = dbSchema.allDBTables;
|
|
207
|
-
if (allDBTables.length === 0)
|
|
208
|
-
return true;
|
|
209
|
-
|
|
210
|
-
if (allDBTables.indexOf(tableName) >= 0)
|
|
211
|
-
return false;
|
|
212
|
-
|
|
213
|
-
let found = [];
|
|
214
|
-
let models = dbSchema.models;
|
|
215
|
-
|
|
216
|
-
for (let i = 0, il = models.length; i < il; i++) {
|
|
217
|
-
let { Model } = models[i];
|
|
218
|
-
let thisTableName = Model.getTableName();
|
|
219
|
-
|
|
220
|
-
if (allDBTables.indexOf(thisTableName) >= 0)
|
|
221
|
-
found.push(thisTableName);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (found.length === allDBTables.length)
|
|
225
|
-
return true;
|
|
226
|
-
|
|
227
|
-
return null;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const isNewColumn = (dbAttributes, columnName) => {
|
|
231
|
-
if (dbAttributes == null)
|
|
232
|
-
return true;
|
|
233
|
-
|
|
234
|
-
let dbColumnNames = Object.keys(dbAttributes);
|
|
235
|
-
if (dbColumnNames.indexOf(columnName) >= 0)
|
|
236
|
-
return false;
|
|
237
|
-
|
|
238
|
-
let fieldNames = Object.keys(fieldRawAttributesMap);
|
|
239
|
-
let found = [];
|
|
240
|
-
|
|
241
|
-
for (let i = 0, il = fieldNames.length; i < il; i++) {
|
|
242
|
-
let fieldName = fieldNames[i];
|
|
243
|
-
let modelAttribute = fieldRawAttributesMap[fieldName];
|
|
244
|
-
let columnName = modelAttribute.field || fieldName;
|
|
245
|
-
|
|
246
|
-
if (dbColumnNames.indexOf(columnName) >= 0)
|
|
247
|
-
found.push(columnName);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (found.length === dbColumnNames.length)
|
|
251
|
-
return true;
|
|
252
|
-
|
|
253
|
-
return null;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const getFieldIndexes = (columnName) => {
|
|
257
|
-
let allIndexes = Model.options.indexes;
|
|
258
|
-
let foundIndexes = [];
|
|
259
|
-
|
|
260
|
-
for (let i = 0, il = allIndexes.length; i < il; i++) {
|
|
261
|
-
let index = allIndexes[i];
|
|
262
|
-
if (!index || !index.fields)
|
|
263
|
-
continue;
|
|
264
|
-
|
|
265
|
-
if (index.fields.indexOf(columnName) >= 0) {
|
|
266
|
-
let mappedIndex = { ...index, primary: index.primary || false, unique: index.unique || false };
|
|
267
|
-
foundIndexes.push(mappedIndex);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return foundIndexes;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
const getDBFieldIndexes = (columnName) => {
|
|
275
|
-
let allIndexes = dbTableInfo.indexes;
|
|
276
|
-
let foundIndexes = [];
|
|
277
|
-
|
|
278
|
-
for (let i = 0, il = allIndexes.length; i < il; i++) {
|
|
279
|
-
let index = allIndexes[i];
|
|
280
|
-
if (!index || !index.fields)
|
|
281
|
-
continue;
|
|
282
|
-
|
|
283
|
-
if (index.fields.findIndex((fieldInfo) => fieldInfo.attribute === columnName) >= 0) {
|
|
284
|
-
let mappedIndex = { ...index, fields: index.fields.map((fieldInfo) => fieldInfo.attribute) };
|
|
285
|
-
foundIndexes.push(mappedIndex);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return foundIndexes;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const indexDiffers = (index1, index2) => {
|
|
293
|
-
if (index1.name !== index2.name)
|
|
294
|
-
return true;
|
|
295
|
-
|
|
296
|
-
if (index2.primary !== index2.primary)
|
|
297
|
-
return true;
|
|
298
|
-
|
|
299
|
-
if (index2.unique !== index2.unique)
|
|
300
|
-
return true;
|
|
301
|
-
|
|
302
|
-
if (Nife.propsDiffer(index1.fields.sort(), index2.fields.sort()))
|
|
303
|
-
return true;
|
|
304
|
-
|
|
305
|
-
return false;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const calculateIndexDiff = (fieldIndexes, dbFieldIndexes) => {
|
|
309
|
-
const findIndex = (indexList, indexName) => {
|
|
310
|
-
return indexList.find((index) => index.name === indexName);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// Calculate from the perspective of fields
|
|
314
|
-
for (let i = 0, il = fieldIndexes.length; i < il; i++) {
|
|
315
|
-
let index = fieldIndexes[i];
|
|
316
|
-
let dbIndex = findIndex(dbFieldIndexes, index.name);
|
|
317
|
-
|
|
318
|
-
if (!dbIndex) {
|
|
319
|
-
if (index.primary)
|
|
320
|
-
continue;
|
|
321
|
-
|
|
322
|
-
diff.indexes.add.push({ Model, index });
|
|
323
|
-
schemaChanged = true;
|
|
324
|
-
} else if (indexDiffers(index, dbIndex)) {
|
|
325
|
-
diff.indexes.remove.push({ Model, index: dbIndex });
|
|
326
|
-
diff.indexes.add.push({ Model, index });
|
|
327
|
-
schemaChanged = true;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Calculate from the perspective of the DB
|
|
332
|
-
for (let i = 0, il = dbFieldIndexes.length; i < il; i++) {
|
|
333
|
-
let dbIndex = dbFieldIndexes[i];
|
|
334
|
-
if (dbIndex.primary)
|
|
335
|
-
continue;
|
|
336
|
-
|
|
337
|
-
let index = findIndex(fieldIndexes, dbIndex.name);
|
|
338
|
-
if (!index) {
|
|
339
|
-
diff.indexes.remove.push({ Model, index: dbIndex });
|
|
340
|
-
schemaChanged = true;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
const findModelByTableName = (tableName) => {
|
|
346
|
-
return dbSchema.models.map((modelInfo) => modelInfo.Model).find((thisModel) => thisModel.getTableName() === tableName);
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const calculateForeignKeysDiff = () => {
|
|
350
|
-
const findDBForeignKey = (tableName, columnName) => {
|
|
351
|
-
let thisTableInfo = dbSchema.dbTableSchema[tableName];
|
|
352
|
-
if (!thisTableInfo)
|
|
353
|
-
return;
|
|
354
|
-
|
|
355
|
-
let foreignKeys = thisTableInfo.foreignKeys;
|
|
356
|
-
if (!foreignKeys)
|
|
357
|
-
return;
|
|
358
|
-
|
|
359
|
-
return foreignKeys.find((fk) => fk.columnName === columnName);
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
const findModelForeignKey = (tableName, columnName) => {
|
|
363
|
-
let thisModel = findModelByTableName(tableName);
|
|
364
|
-
if (!thisModel)
|
|
365
|
-
return;
|
|
366
|
-
|
|
367
|
-
let associations = thisModel.associations;
|
|
368
|
-
let keys = Object.keys(associations);
|
|
369
|
-
|
|
370
|
-
for (let i = 0, il = keys.length; i < il; i++) {
|
|
371
|
-
let key = keys[i];
|
|
372
|
-
let association = associations[key];
|
|
373
|
-
if (association.identifierField === columnName)
|
|
374
|
-
return association;
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
// See if any foreign keys need to be added
|
|
379
|
-
let associations = Model.associations;
|
|
380
|
-
let keys = Object.keys(associations);
|
|
381
|
-
|
|
382
|
-
// debugger;
|
|
383
|
-
|
|
384
|
-
for (let i = 0, il = keys.length; i < il; i++) {
|
|
385
|
-
let key = keys[i];
|
|
386
|
-
let association = associations[key];
|
|
387
|
-
let targetModel;
|
|
388
|
-
|
|
389
|
-
if (!association.targetIdentifier) {
|
|
390
|
-
// field exists on target
|
|
391
|
-
targetModel = association.target;
|
|
392
|
-
} else {
|
|
393
|
-
// field exists on source
|
|
394
|
-
targetModel = association.source;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
let identifierField = association.identifierField;
|
|
398
|
-
let dbAssociation = findDBForeignKey(targetModel.getTableName(), identifierField);
|
|
399
|
-
if (!dbAssociation) {
|
|
400
|
-
diff.foreignKeys.add.push({ Model: targetModel, foreignKey: association });
|
|
401
|
-
schemaChanged = true;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Now check if any foreign keys need to be removed
|
|
406
|
-
let thisTableInfo = dbSchema.dbTableSchema[tableName];
|
|
407
|
-
if (!thisTableInfo)
|
|
408
|
-
return;
|
|
409
|
-
|
|
410
|
-
let foreignKeys = thisTableInfo.foreignKeys;
|
|
411
|
-
if (!foreignKeys)
|
|
412
|
-
return;
|
|
413
|
-
|
|
414
|
-
for (let i = 0, il = foreignKeys.length; i < il; i++) {
|
|
415
|
-
let foreignKey = foreignKeys[i];
|
|
416
|
-
let modelForeignKey = findModelForeignKey(foreignKey.tableName, foreignKey.columnName);
|
|
417
|
-
|
|
418
|
-
if (!modelForeignKey) {
|
|
419
|
-
diff.foreignKeys.remove.push({ Model, foreignKey });
|
|
420
|
-
schemaChanged = true;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
let diff = {
|
|
426
|
-
tables: {
|
|
427
|
-
add: [],
|
|
428
|
-
alter: [],
|
|
429
|
-
},
|
|
430
|
-
columns: {
|
|
431
|
-
add: [],
|
|
432
|
-
remove: [],
|
|
433
|
-
alter: [],
|
|
434
|
-
},
|
|
435
|
-
indexes: {
|
|
436
|
-
add: [],
|
|
437
|
-
remove: [],
|
|
438
|
-
},
|
|
439
|
-
foreignKeys: {
|
|
440
|
-
add: [],
|
|
441
|
-
remove: [],
|
|
442
|
-
},
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
if (dbAttributes == null) {
|
|
446
|
-
if (isNewModel(dbSchema, tableName) == null) {
|
|
447
|
-
const response = await prompts([
|
|
448
|
-
{
|
|
449
|
-
type: 'select',
|
|
450
|
-
name: 'operation',
|
|
451
|
-
message: `Ambiguity detected with table "${tableName}". Are you renaming this table, or adding a new table?`,
|
|
452
|
-
choices: [
|
|
453
|
-
{ title: 'Adding a new table', description: 'You are adding a new table', value: 'adding' },
|
|
454
|
-
{ title: 'Renaming a table', description: 'You are renaming an existing table', value: 'renaming' },
|
|
455
|
-
],
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
type: (prev) => {
|
|
459
|
-
return (prev === 'renaming') ? 'text' : null;
|
|
460
|
-
},
|
|
461
|
-
name: 'oldTableName',
|
|
462
|
-
message: 'What is the current name (in the database) of the table you are renaming?',
|
|
463
|
-
validate: (value) => {
|
|
464
|
-
if (value.match(/^[^\S]*$/))
|
|
465
|
-
return 'Must specify a table name';
|
|
466
|
-
|
|
467
|
-
if (!Object.prototype.hasOwnProperty.call(dbSchema.dbTableSchema, value))
|
|
468
|
-
return `No such table "${value}" found in the database`;
|
|
469
|
-
|
|
470
|
-
return true;
|
|
471
|
-
},
|
|
472
|
-
format: (value) => {
|
|
473
|
-
return value.trim();
|
|
474
|
-
},
|
|
475
|
-
},
|
|
476
|
-
]);
|
|
477
|
-
|
|
478
|
-
if (response.operation === 'renaming') {
|
|
479
|
-
let oldTableName = response.oldTableName;
|
|
480
|
-
let newTableName = tableName;
|
|
481
|
-
|
|
482
|
-
dbTableInfo = dbSchema.dbTableSchema[oldTableName];
|
|
483
|
-
dbAttributes = (dbTableInfo) ? dbTableInfo.attributes : null;
|
|
484
|
-
|
|
485
|
-
// table rename
|
|
486
|
-
schemaChanged = true;
|
|
487
|
-
diff.tables.alter.push({ operation: 'rename', Model, oldTableName, newTableName });
|
|
488
|
-
} else {
|
|
489
|
-
// entire table doesn't exist
|
|
490
|
-
diff.tables.add.push(Model);
|
|
491
|
-
return diff;
|
|
492
|
-
}
|
|
493
|
-
} else {
|
|
494
|
-
// entire table doesn't exist
|
|
495
|
-
diff.tables.add.push(Model);
|
|
496
|
-
diff.indexes.add = Model.options.indexes.map((index) => {
|
|
497
|
-
return {
|
|
498
|
-
Model,
|
|
499
|
-
index,
|
|
500
|
-
};
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
return diff;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Check for differences in columns
|
|
508
|
-
for (let i = 0, il = fieldNames.length; i < il; i++) {
|
|
509
|
-
let fieldName = fieldNames[i];
|
|
510
|
-
let columnName = fieldNameToDBColumnName(fieldName);
|
|
511
|
-
let columnDefinition = tableAttributes[fieldName];
|
|
512
|
-
let dbColumnDefinition = (dbAttributes) ? dbAttributes[columnName] : null;
|
|
513
|
-
let columnIsNew = isNewColumn(dbAttributes, columnName);
|
|
514
|
-
let fieldIndexes = getFieldIndexes(columnName);
|
|
515
|
-
let dbFieldIndexes = getDBFieldIndexes(columnName);
|
|
516
|
-
|
|
517
|
-
if (dbColumnDefinition == null && columnIsNew == null) {
|
|
518
|
-
const response = await prompts([
|
|
519
|
-
{
|
|
520
|
-
type: 'select',
|
|
521
|
-
name: 'operation',
|
|
522
|
-
message: `Ambiguity detected with column "${columnName}". Are you renaming this column, or adding a new column?`,
|
|
523
|
-
choices: [
|
|
524
|
-
{ title: 'Adding a new column', description: 'You are adding a new column', value: 'adding' },
|
|
525
|
-
{ title: 'Renaming a column', description: 'You are renaming an existing column', value: 'renaming' },
|
|
526
|
-
],
|
|
527
|
-
},
|
|
528
|
-
{
|
|
529
|
-
type: (prev) => {
|
|
530
|
-
return (prev === 'renaming') ? 'text' : null;
|
|
531
|
-
},
|
|
532
|
-
name: 'oldColumnName',
|
|
533
|
-
message: 'What is the current name (in the database) of the column you are renaming?',
|
|
534
|
-
validate: (value) => {
|
|
535
|
-
if (value.match(/^[^\S]*$/))
|
|
536
|
-
return 'Must specify a column name';
|
|
537
|
-
|
|
538
|
-
if (!Object.prototype.hasOwnProperty.call(dbAttributes, value))
|
|
539
|
-
return `No such column "${value}" found in the "${tableName}" table`;
|
|
540
|
-
|
|
541
|
-
return true;
|
|
542
|
-
},
|
|
543
|
-
format: (value) => {
|
|
544
|
-
return value.trim();
|
|
545
|
-
},
|
|
546
|
-
},
|
|
547
|
-
]);
|
|
548
|
-
|
|
549
|
-
if (response.operation === 'renaming') {
|
|
550
|
-
let oldColumnName = response.oldColumnName;
|
|
551
|
-
let newColumnName = columnName;
|
|
552
|
-
|
|
553
|
-
// column rename
|
|
554
|
-
dbColumnDefinition = (dbAttributes) ? dbAttributes[oldColumnName] : null;
|
|
555
|
-
schemaChanged = true;
|
|
556
|
-
diff.columns.alter.push({ operation: 'rename', Model, oldColumnName, newColumnName, dbColumnDefinition, columnDefinition });
|
|
557
|
-
} else {
|
|
558
|
-
schemaChanged = true;
|
|
559
|
-
diff.columns.add.push({ operation: 'add', Model, columnDefinition, dbColumnDefinition, fieldName, columnName, tableName });
|
|
560
|
-
diff.indexes.add = diff.indexes.add.concat(fieldIndexes);
|
|
561
|
-
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (columnIsNew) {
|
|
567
|
-
// add column
|
|
568
|
-
schemaChanged = true;
|
|
569
|
-
diff.columns.add.push({ operation: 'add', Model, columnDefinition, dbColumnDefinition, fieldName, columnName, tableName });
|
|
570
|
-
} else if (isColumnAltered(fieldName, columnName, columnDefinition, dbColumnDefinition)) {
|
|
571
|
-
// alter column
|
|
572
|
-
|
|
573
|
-
schemaChanged = true;
|
|
574
|
-
diff.columns.alter.push({ operation: 'alter', Model, columnDefinition, dbColumnDefinition, fieldName, columnName, tableName });
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
calculateIndexDiff(fieldIndexes, dbFieldIndexes);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if (dbAttributes != null) {
|
|
581
|
-
// check for removed columns
|
|
582
|
-
let dbFieldNames = Object.keys(dbAttributes);
|
|
583
|
-
for (let i = 0, il = dbFieldNames.length; i < il; i++) {
|
|
584
|
-
let dbFieldName = dbFieldNames[i];
|
|
585
|
-
let modelAttribute = fieldRawAttributesMap[dbFieldName];
|
|
586
|
-
let fieldIndexes = getFieldIndexes(dbFieldName);
|
|
587
|
-
let dbFieldIndexes = getDBFieldIndexes(dbFieldName);
|
|
588
|
-
|
|
589
|
-
if (modelAttribute == null) {
|
|
590
|
-
schemaChanged = true;
|
|
591
|
-
|
|
592
|
-
// remove column
|
|
593
|
-
diff.columns.remove.push({ operation: 'remove', Model, columnDefinition: dbAttributes[dbFieldName], columnName: dbFieldName });
|
|
594
|
-
|
|
595
|
-
calculateIndexDiff(fieldIndexes, dbFieldIndexes);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
calculateForeignKeysDiff();
|
|
601
|
-
|
|
602
|
-
if (schemaChanged === false)
|
|
603
|
-
return null;
|
|
604
|
-
|
|
605
|
-
return diff;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
async calculateDBSchemaDifferences(connection, dbSchema, options) {
|
|
609
|
-
let schemaDiff = [];
|
|
610
|
-
let schemaChanged = false;
|
|
611
|
-
let models = dbSchema.models;
|
|
612
|
-
|
|
613
|
-
for (let i = 0, il = models.length; i < il; i++) {
|
|
614
|
-
let modelSchema = models[i];
|
|
615
|
-
let diff = await this.calculateModelSchemaDifferences(connection, modelSchema, dbSchema, options);
|
|
616
|
-
|
|
617
|
-
if (diff == null)
|
|
618
|
-
continue;
|
|
619
|
-
|
|
620
|
-
schemaChanged = true;
|
|
621
|
-
|
|
622
|
-
schemaDiff.push({
|
|
623
|
-
modelSchema,
|
|
624
|
-
diff,
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (schemaChanged === false)
|
|
629
|
-
return null;
|
|
630
|
-
|
|
631
|
-
return schemaDiff;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
forignKeyChecksQuery(connection, tableName, constraints, disable) {
|
|
635
|
-
let dialect = connection.getDialect();
|
|
636
|
-
let queryParts = [];
|
|
637
|
-
|
|
638
|
-
if (disable) {
|
|
639
|
-
switch (dialect) {
|
|
640
|
-
case 'postgres':
|
|
641
|
-
queryParts.push('SET CONSTRAINTS ALL DEFERRED;');
|
|
642
|
-
break;
|
|
643
|
-
case 'db2':
|
|
644
|
-
for (let i = 0, il = constraints.length; i < il; i++) {
|
|
645
|
-
let constraint = constraints[i];
|
|
646
|
-
queryParts.push(`ALTER TABLE "${constraint.tableName}" ALTER FOREIGN KEY "${constraint.constraintName}" NOT ENFORCED`);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
break;
|
|
650
|
-
case 'ibmi':
|
|
651
|
-
case 'mssql':
|
|
652
|
-
case 'mariadb':
|
|
653
|
-
queryParts.push('SET FOREIGN_KEY_CHECKS = 0;');
|
|
654
|
-
break;
|
|
655
|
-
case 'sqlite':
|
|
656
|
-
queryParts.push('PRAGMA foreign_keys = OFF;');
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
} else {
|
|
660
|
-
switch (dialect) {
|
|
661
|
-
case 'postgres':
|
|
662
|
-
queryParts.push('SET CONSTRAINTS ALL IMMEDIATE;');
|
|
663
|
-
break;
|
|
664
|
-
case 'db2':
|
|
665
|
-
for (let i = 0, il = constraints.length; i < il; i++) {
|
|
666
|
-
let constraint = constraints[i];
|
|
667
|
-
queryParts.push(`ALTER TABLE "${constraint.tableName}" ALTER FOREIGN KEY "${constraint.constraintName}" ENFORCED`);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
break;
|
|
671
|
-
case 'ibmi':
|
|
672
|
-
case 'mssql':
|
|
673
|
-
case 'mariadb':
|
|
674
|
-
queryParts.push('SET FOREIGN_KEY_CHECKS = 1;');
|
|
675
|
-
break;
|
|
676
|
-
case 'sqlite':
|
|
677
|
-
queryParts.push('PRAGMA foreign_keys = ON;');
|
|
678
|
-
break;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
return queryParts.join(';');
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
async _hijackConnection(dbConnection, callback) {
|
|
686
|
-
let DialectQueryClass = dbConnection.dialect.Query;
|
|
687
|
-
|
|
688
|
-
try {
|
|
689
|
-
let sqlQueries = [];
|
|
690
|
-
|
|
691
|
-
dbConnection.dialect.Query = class Query extends dbConnection.dialect.Query {
|
|
692
|
-
constructor(connection, ...args) {
|
|
693
|
-
let originalQuery = connection.query;
|
|
694
|
-
|
|
695
|
-
connection.query = function(..._queryArgs) {
|
|
696
|
-
let queryArgs = _queryArgs.slice();
|
|
697
|
-
let sql = queryArgs[0];
|
|
698
|
-
|
|
699
|
-
// eslint-disable-next-line no-magic-numbers
|
|
700
|
-
let callbackIndex = (queryArgs.length === 3) ? 2 : 1;
|
|
701
|
-
let cb = queryArgs[callbackIndex];
|
|
702
|
-
|
|
703
|
-
if (sql.match(/^\s*SELECT/)) {
|
|
704
|
-
return originalQuery.apply(this, queryArgs);
|
|
705
|
-
} else {
|
|
706
|
-
sqlQueries.push(sql);
|
|
707
|
-
cb(null, { rows: [] });
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
|
|
711
|
-
super(connection, ...args);
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
let result = await callback(dbConnection);
|
|
716
|
-
|
|
717
|
-
return {
|
|
718
|
-
queries: sqlQueries,
|
|
719
|
-
result,
|
|
720
|
-
};
|
|
721
|
-
} finally {
|
|
722
|
-
dbConnection.dialect.Query = DialectQueryClass;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
sanitizeString(str) {
|
|
727
|
-
return str.replace(/'/g, '\\\'');
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
async generateMigrationFromDiff(connection, dbSchema, schemaDiff, migrationID) {
|
|
731
|
-
const PREFIX_WHITESPACE = ' ';
|
|
732
|
-
|
|
733
|
-
const generateUpAndDownFromResults = (_results) => {
|
|
734
|
-
let results = _results;
|
|
735
|
-
|
|
736
|
-
for (let i = 0, il = results.length; i < il; i++) {
|
|
737
|
-
let result = results[i];
|
|
738
|
-
result.up.queries.forEach((sql) => {
|
|
739
|
-
upCodeParts.push(`${PREFIX_WHITESPACE}await connection.query('${this.sanitizeString(sql)}');\n`);
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
results = results.reverse();
|
|
744
|
-
|
|
745
|
-
for (let i = 0, il = results.length; i < il; i++) {
|
|
746
|
-
let result = results[i];
|
|
747
|
-
result.down.queries.forEach((sql) => {
|
|
748
|
-
downCodeParts.push(`${PREFIX_WHITESPACE}await connection.query('${this.sanitizeString(sql)}');\n`);
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
const createTable = async (Model) => {
|
|
754
|
-
return await this._hijackConnection(connection, async () => {
|
|
755
|
-
let attributes = Model.tableAttributes;
|
|
756
|
-
let options = Model.options;
|
|
757
|
-
let tableName = Model.getTableName();
|
|
758
|
-
|
|
759
|
-
return await queryInterface.createTable(tableName, attributes, options, Model);
|
|
760
|
-
});
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
const renameTable = async (oldTableName, newTableName) => {
|
|
764
|
-
return await this._hijackConnection(connection, async () => {
|
|
765
|
-
return await queryInterface.renameTable(oldTableName, newTableName);
|
|
766
|
-
});
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
const addColumn = async (Model, columnDefinition, columnName) => {
|
|
770
|
-
return await this._hijackConnection(connection, async () => {
|
|
771
|
-
let options = Model.options;
|
|
772
|
-
let tableName = Model.getTableName();
|
|
773
|
-
|
|
774
|
-
return await queryInterface.addColumn(tableName, columnName, columnDefinition, options);
|
|
775
|
-
});
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
const removeColumn = async (Model, columnDefinition, columnName) => {
|
|
779
|
-
return await this._hijackConnection(connection, async () => {
|
|
780
|
-
let options = Model.options;
|
|
781
|
-
let tableName = Model.getTableName();
|
|
782
|
-
|
|
783
|
-
return await queryInterface.removeColumn(tableName, columnName, columnDefinition, options);
|
|
784
|
-
});
|
|
785
|
-
};
|
|
786
|
-
|
|
787
|
-
const alterColumn = async (Model, columnDefinition, columnName) => {
|
|
788
|
-
return await this._hijackConnection(connection, async () => {
|
|
789
|
-
let options = Model.options;
|
|
790
|
-
let tableName = Model.getTableName();
|
|
791
|
-
|
|
792
|
-
return await queryInterface.changeColumn(tableName, columnName, columnDefinition, options);
|
|
793
|
-
});
|
|
794
|
-
};
|
|
795
|
-
|
|
796
|
-
const renameColumn = async (Model, columnDefinition, oldColumnName, newColumnName) => {
|
|
797
|
-
return await this._hijackConnection(connection, async () => {
|
|
798
|
-
let options = Model.options;
|
|
799
|
-
let tableName = Model.getTableName();
|
|
800
|
-
|
|
801
|
-
let oldAssertTableHasColumn = queryInterface.assertTableHasColumn;
|
|
802
|
-
try {
|
|
803
|
-
queryInterface.assertTableHasColumn = async () => ({ [oldColumnName]: columnDefinition });
|
|
804
|
-
|
|
805
|
-
return await queryInterface.renameColumn(tableName, oldColumnName, newColumnName, options);
|
|
806
|
-
} finally {
|
|
807
|
-
queryInterface.assertTableHasColumn = oldAssertTableHasColumn;
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
const addIndex = async (Model, index) => {
|
|
813
|
-
return await this._hijackConnection(connection, async () => {
|
|
814
|
-
let tableName = Model.getTableName();
|
|
815
|
-
|
|
816
|
-
return await queryInterface.addIndex(tableName, index.fields, { ...index, concurrently: true }, tableName);
|
|
817
|
-
});
|
|
818
|
-
};
|
|
819
|
-
|
|
820
|
-
const removeIndex = async (Model, index) => {
|
|
821
|
-
return await this._hijackConnection(connection, async () => {
|
|
822
|
-
let options = Model.options;
|
|
823
|
-
let tableName = Model.getTableName();
|
|
824
|
-
|
|
825
|
-
return await queryInterface.removeIndex(tableName, index.name, options);
|
|
826
|
-
});
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
const findModelByTableName = (tableName) => {
|
|
830
|
-
return dbSchema.models.map((modelInfo) => modelInfo.Model).find((thisModel) => thisModel.getTableName() === tableName);
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
const addForeignKey = async (foreignKey) => {
|
|
834
|
-
return await this._hijackConnection(connection, async () => {
|
|
835
|
-
let sourceTableName;
|
|
836
|
-
let targetTableName;
|
|
837
|
-
let fieldName;
|
|
838
|
-
let targetField;
|
|
839
|
-
let onDelete = 'NO ACTION';
|
|
840
|
-
let onUpdate = 'NO ACTION';
|
|
841
|
-
|
|
842
|
-
if (foreignKey.options) {
|
|
843
|
-
let options = foreignKey.options;
|
|
844
|
-
let targetModel = (foreignKey.targetIdentifier) ? foreignKey.target : foreignKey.source;
|
|
845
|
-
let sourceModel = (foreignKey.targetIdentifier) ? foreignKey.source : foreignKey.target;
|
|
846
|
-
|
|
847
|
-
fieldName = options.field;
|
|
848
|
-
sourceTableName = sourceModel.getTableName();
|
|
849
|
-
targetTableName = targetModel.getTableName();
|
|
850
|
-
targetField = (foreignKey.targetIdentifier) ? foreignKey.targetKeyField : foreignKey.sourceKeyField;
|
|
851
|
-
onDelete = options.onDelete;
|
|
852
|
-
onUpdate = options.onUpdate;
|
|
853
|
-
} else {
|
|
854
|
-
fieldName = foreignKey.columnName;
|
|
855
|
-
sourceTableName = foreignKey.tableName;
|
|
856
|
-
targetTableName = foreignKey.referencedTableName;
|
|
857
|
-
targetField = foreignKey.referencedColumnName;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
return await queryInterface.addConstraint(sourceTableName, {
|
|
861
|
-
type: 'FOREIGN KEY',
|
|
862
|
-
fields: [ fieldName ],
|
|
863
|
-
name: `${sourceTableName}_${fieldName}_fkey`,
|
|
864
|
-
references: {
|
|
865
|
-
table: targetTableName,
|
|
866
|
-
field: targetField,
|
|
867
|
-
},
|
|
868
|
-
onDelete,
|
|
869
|
-
onUpdate,
|
|
870
|
-
});
|
|
871
|
-
});
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
const removeForeignKey = async (foreignKey) => {
|
|
875
|
-
return await this._hijackConnection(connection, async () => {
|
|
876
|
-
if (foreignKey.options) {
|
|
877
|
-
let options = foreignKey.options;
|
|
878
|
-
let sourceModel = (foreignKey.targetIdentifier) ? foreignKey.source : foreignKey.target;
|
|
879
|
-
let constraintName = `${sourceModel.getTableName()}_${options.field}_fkey`;
|
|
880
|
-
|
|
881
|
-
return await queryInterface.removeConstraint(sourceModel.getTableName(), constraintName, sourceModel.options);
|
|
882
|
-
} else {
|
|
883
|
-
let Model = findModelByTableName(foreignKey.tableName);
|
|
884
|
-
let options = Model.options;
|
|
885
|
-
|
|
886
|
-
return await queryInterface.removeConstraint(foreignKey.tableName, foreignKey.constraintName, options);
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
};
|
|
890
|
-
|
|
891
|
-
const checkCreateTables = async (tablesToCreate) => {
|
|
892
|
-
if (!tablesToCreate || tablesToCreate.length === 0)
|
|
893
|
-
return;
|
|
894
|
-
|
|
895
|
-
// Right now we only ever have one table to create for a given model
|
|
896
|
-
let Model = tablesToCreate[0];
|
|
897
|
-
let tableName = Model.getTableName();
|
|
898
|
-
let result = await createTable(Model);
|
|
899
|
-
|
|
900
|
-
result.queries.forEach((sql) => {
|
|
901
|
-
upCodeParts.push(`${PREFIX_WHITESPACE}await connection.query('${this.sanitizeString(sql)}');\n`);
|
|
902
|
-
downCodeParts.push(`${PREFIX_WHITESPACE}await connection.query('DROP TABLE IF EXISTS ${this.sanitizeString(queryInterface.quoteIdentifier(tableName))};');\n`);
|
|
903
|
-
});
|
|
904
|
-
};
|
|
905
|
-
|
|
906
|
-
const checkAlterTables = async (tablesToAlter) => {
|
|
907
|
-
if (!tablesToAlter || tablesToAlter.length === 0)
|
|
908
|
-
return;
|
|
909
|
-
|
|
910
|
-
let results = [];
|
|
911
|
-
for (let i = 0, il = tablesToAlter.length; i < il; i++) {
|
|
912
|
-
let tableToAlter = tablesToAlter[i];
|
|
913
|
-
if (tableToAlter == null)
|
|
914
|
-
continue;
|
|
915
|
-
|
|
916
|
-
if (tableToAlter.operation === 'rename') {
|
|
917
|
-
let { oldTableName, newTableName } = tableToAlter;
|
|
918
|
-
let result = {
|
|
919
|
-
up: await renameTable(oldTableName, newTableName),
|
|
920
|
-
down: await renameTable(newTableName, oldTableName),
|
|
921
|
-
};
|
|
922
|
-
|
|
923
|
-
results.push(result);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
generateUpAndDownFromResults(results);
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
const checkAddColumns = async (columnsToAdd) => {
|
|
931
|
-
if (!columnsToAdd || columnsToAdd.length === 0)
|
|
932
|
-
return;
|
|
933
|
-
|
|
934
|
-
let results = [];
|
|
935
|
-
for (let i = 0, il = columnsToAdd.length; i < il; i++) {
|
|
936
|
-
let columnToAdd = columnsToAdd[i];
|
|
937
|
-
if (columnToAdd == null)
|
|
938
|
-
continue;
|
|
939
|
-
|
|
940
|
-
let { Model, columnDefinition, columnName } = columnToAdd;
|
|
941
|
-
let result = await addColumn(Model, columnDefinition, columnName);
|
|
942
|
-
|
|
943
|
-
results.push(result);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
for (let i = 0, il = results.length; i < il; i++) {
|
|
947
|
-
let columnToAdd = columnsToAdd[i];
|
|
948
|
-
if (columnToAdd == null)
|
|
949
|
-
continue;
|
|
950
|
-
|
|
951
|
-
let { columnName, tableName } = columnToAdd;
|
|
952
|
-
let result = results[i];
|
|
953
|
-
|
|
954
|
-
result.queries.forEach((sql) => {
|
|
955
|
-
upCodeParts.push(`${PREFIX_WHITESPACE}await connection.query('${this.sanitizeString(sql)}');\n`);
|
|
956
|
-
downCodeParts.push(`${PREFIX_WHITESPACE}await connection.query('ALTER TABLE ${this.sanitizeString(queryInterface.quoteIdentifier(tableName))} DROP COLUMN IF EXISTS ${this.sanitizeString(queryInterface.quoteIdentifier(columnName))} CASCADE;');\n`);
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
};
|
|
960
|
-
|
|
961
|
-
const checkAlterColumns = async (columnsToAlter) => {
|
|
962
|
-
if (!columnsToAlter || columnsToAlter.length === 0)
|
|
963
|
-
return;
|
|
964
|
-
|
|
965
|
-
disableForeignKeyChecks = true;
|
|
966
|
-
|
|
967
|
-
let results = [];
|
|
968
|
-
for (let i = 0, il = columnsToAlter.length; i < il; i++) {
|
|
969
|
-
let columnToAlter = columnsToAlter[i];
|
|
970
|
-
if (columnToAlter == null)
|
|
971
|
-
continue;
|
|
972
|
-
|
|
973
|
-
let {
|
|
974
|
-
operation,
|
|
975
|
-
Model,
|
|
976
|
-
columnDefinition,
|
|
977
|
-
columnName,
|
|
978
|
-
dbColumnDefinition,
|
|
979
|
-
oldColumnName,
|
|
980
|
-
newColumnName,
|
|
981
|
-
} = columnToAlter;
|
|
982
|
-
|
|
983
|
-
if (operation === 'rename') {
|
|
984
|
-
let result = {
|
|
985
|
-
up: await renameColumn(Model, dbColumnDefinition || columnDefinition, oldColumnName, newColumnName),
|
|
986
|
-
down: await renameColumn(Model, dbColumnDefinition || columnDefinition, newColumnName, oldColumnName),
|
|
987
|
-
};
|
|
988
|
-
|
|
989
|
-
results.push(result);
|
|
990
|
-
} else if (operation === 'alter') {
|
|
991
|
-
let result = {
|
|
992
|
-
up: await alterColumn(Model, columnDefinition, columnName),
|
|
993
|
-
down: await alterColumn(Model, dbColumnDefinition, columnName),
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
results.push(result);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
generateUpAndDownFromResults(results);
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
const checkRemoveColumns = async (columnsToRemove) => {
|
|
1004
|
-
if (!columnsToRemove || columnsToRemove.length === 0)
|
|
1005
|
-
return;
|
|
1006
|
-
|
|
1007
|
-
disableForeignKeyChecks = true;
|
|
1008
|
-
|
|
1009
|
-
let results = [];
|
|
1010
|
-
for (let i = 0, il = columnsToRemove.length; i < il; i++) {
|
|
1011
|
-
let columnToAdd = columnsToRemove[i];
|
|
1012
|
-
if (columnToAdd == null)
|
|
1013
|
-
continue;
|
|
1014
|
-
|
|
1015
|
-
let { Model, columnDefinition, columnName } = columnToAdd;
|
|
1016
|
-
let result = {
|
|
1017
|
-
up: await removeColumn(Model, columnDefinition, columnName),
|
|
1018
|
-
down: await addColumn(Model, columnDefinition, columnName),
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
results.push(result);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
generateUpAndDownFromResults(results);
|
|
1025
|
-
};
|
|
1026
|
-
|
|
1027
|
-
const checkAddIndexes = async (indexesToAdd) => {
|
|
1028
|
-
if (!indexesToAdd || indexesToAdd.length === 0)
|
|
1029
|
-
return;
|
|
1030
|
-
|
|
1031
|
-
let results = [];
|
|
1032
|
-
for (let i = 0, il = indexesToAdd.length; i < il; i++) {
|
|
1033
|
-
let indexToAdd = indexesToAdd[i];
|
|
1034
|
-
if (indexToAdd == null)
|
|
1035
|
-
continue;
|
|
1036
|
-
|
|
1037
|
-
let { Model, index } = indexToAdd;
|
|
1038
|
-
let result = {
|
|
1039
|
-
up: await addIndex(Model, index),
|
|
1040
|
-
down: await removeIndex(Model, index),
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
results.push(result);
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
generateUpAndDownFromResults(results);
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
const checkRemoveIndexes = async (indexesToRemove) => {
|
|
1050
|
-
if (!indexesToRemove || indexesToRemove.length === 0)
|
|
1051
|
-
return;
|
|
1052
|
-
|
|
1053
|
-
let results = [];
|
|
1054
|
-
for (let i = 0, il = indexesToRemove.length; i < il; i++) {
|
|
1055
|
-
let indexToRemove = indexesToRemove[i];
|
|
1056
|
-
if (indexToRemove == null)
|
|
1057
|
-
continue;
|
|
1058
|
-
|
|
1059
|
-
let { Model, index } = indexToRemove;
|
|
1060
|
-
let result = {
|
|
1061
|
-
up: await removeIndex(Model, index),
|
|
1062
|
-
down: await addIndex(Model, index),
|
|
1063
|
-
};
|
|
1064
|
-
|
|
1065
|
-
results.push(result);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
generateUpAndDownFromResults(results);
|
|
1069
|
-
};
|
|
1070
|
-
|
|
1071
|
-
const checkAddForeignKeys = async (foreignKeysToAdd) => {
|
|
1072
|
-
if (!foreignKeysToAdd || foreignKeysToAdd.length === 0)
|
|
1073
|
-
return;
|
|
1074
|
-
|
|
1075
|
-
let results = [];
|
|
1076
|
-
for (let i = 0, il = foreignKeysToAdd.length; i < il; i++) {
|
|
1077
|
-
let foreignKeyToAdd = foreignKeysToAdd[i];
|
|
1078
|
-
if (foreignKeyToAdd == null)
|
|
1079
|
-
continue;
|
|
1080
|
-
|
|
1081
|
-
let { foreignKey } = foreignKeyToAdd;
|
|
1082
|
-
let result = {
|
|
1083
|
-
up: await addForeignKey(foreignKey),
|
|
1084
|
-
down: await removeForeignKey(foreignKey),
|
|
1085
|
-
};
|
|
1086
|
-
|
|
1087
|
-
results.push(result);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
generateUpAndDownFromResults(results);
|
|
1091
|
-
};
|
|
1092
|
-
|
|
1093
|
-
const checkRemoveForeignKeys = async (foreignKeysToRemove) => {
|
|
1094
|
-
if (!foreignKeysToRemove || foreignKeysToRemove.length === 0)
|
|
1095
|
-
return;
|
|
1096
|
-
|
|
1097
|
-
let results = [];
|
|
1098
|
-
for (let i = 0, il = foreignKeysToRemove.length; i < il; i++) {
|
|
1099
|
-
let foreignKeyToRemove = foreignKeysToRemove[i];
|
|
1100
|
-
if (foreignKeyToRemove == null)
|
|
1101
|
-
continue;
|
|
1102
|
-
|
|
1103
|
-
let { foreignKey } = foreignKeyToRemove;
|
|
1104
|
-
let result = {
|
|
1105
|
-
up: await removeForeignKey(foreignKey),
|
|
1106
|
-
down: await addForeignKey(foreignKey),
|
|
1107
|
-
};
|
|
1108
|
-
|
|
1109
|
-
results.push(result);
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
generateUpAndDownFromResults(results);
|
|
1113
|
-
};
|
|
1114
|
-
|
|
1115
|
-
let disableForeignKeyChecks = false;
|
|
1116
|
-
let queryInterface = connection.getQueryInterface();
|
|
1117
|
-
let upCodeParts = [];
|
|
1118
|
-
let downCodeParts = [];
|
|
1119
|
-
let preCodeParts = [];
|
|
1120
|
-
let postCodeParts = [];
|
|
1121
|
-
let models = dbSchema.models;
|
|
1122
|
-
let dbTableSchema = dbSchema.dbTableSchema;
|
|
1123
|
-
|
|
1124
|
-
// Now create migration
|
|
1125
|
-
for (let i = 0, il = schemaDiff.length; i < il; i++) {
|
|
1126
|
-
let thisDiff = schemaDiff[i];
|
|
1127
|
-
let diff = thisDiff.diff;
|
|
1128
|
-
if (diff == null)
|
|
1129
|
-
continue;
|
|
1130
|
-
|
|
1131
|
-
await checkCreateTables(diff.tables.add);
|
|
1132
|
-
await checkAlterTables(diff.tables.alter);
|
|
1133
|
-
await checkRemoveForeignKeys(diff.foreignKeys.remove);
|
|
1134
|
-
await checkAddColumns(diff.columns.add);
|
|
1135
|
-
await checkAlterColumns(diff.columns.alter);
|
|
1136
|
-
await checkRemoveColumns(diff.columns.remove);
|
|
1137
|
-
await checkAddIndexes(diff.indexes.add);
|
|
1138
|
-
await checkRemoveIndexes(diff.indexes.remove);
|
|
1139
|
-
await checkAddForeignKeys(diff.foreignKeys.add);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
if (disableForeignKeyChecks) {
|
|
1143
|
-
// Setup pre and post migration code
|
|
1144
|
-
for (let i = 0, il = models.length; i < il; i++) {
|
|
1145
|
-
let modelSchema = models[i];
|
|
1146
|
-
let tableName = modelSchema.tableName;
|
|
1147
|
-
let dbSchemaInfo = dbTableSchema[tableName];
|
|
1148
|
-
|
|
1149
|
-
preCodeParts.push(this.forignKeyChecksQuery(connection, tableName, (dbSchemaInfo) ? dbSchemaInfo.foreignKeys : [], true));
|
|
1150
|
-
postCodeParts.push(this.forignKeyChecksQuery(connection, tableName, (dbSchemaInfo) ? dbSchemaInfo.foreignKeys : [], false));
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
preCodeParts = Nife.uniq(preCodeParts).map((value) => ` await connection.query('${this.sanitizeString(value)}');`);
|
|
1154
|
-
postCodeParts = Nife.uniq(postCodeParts).map((value) => `${PREFIX_WHITESPACE}await connection.query('${this.sanitizeString(value)}');`);
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// Now generate the migration file contents
|
|
1158
|
-
let template = generateMigration(
|
|
1159
|
-
migrationID,
|
|
1160
|
-
upCodeParts.join('').trimEnd(),
|
|
1161
|
-
downCodeParts.reverse().join('').trimEnd(),
|
|
1162
|
-
preCodeParts.join('').trimEnd(),
|
|
1163
|
-
postCodeParts.reverse().join('').trimEnd(),
|
|
1164
|
-
);
|
|
1165
|
-
|
|
1166
|
-
return template;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
async execute(args) {
|
|
1170
|
-
let options = {};
|
|
1171
|
-
let application = this.getApplication();
|
|
1172
|
-
let applicationOptions = application.getOptions();
|
|
1173
|
-
let connection = application.getDBConnection();
|
|
1174
|
-
let dbSchema = await this.getDBSchema(connection);
|
|
1175
|
-
let schemaDiff = await this.calculateDBSchemaDifferences(connection, dbSchema, options);
|
|
1176
|
-
|
|
1177
|
-
if (schemaDiff == null) {
|
|
1178
|
-
console.log('No changes to schema detected. Aborting.');
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
let migrationName = args.name;
|
|
1183
|
-
if (Nife.isEmpty(migrationName)) {
|
|
1184
|
-
console.error('Migration "name" required. Please supply the migration name via the "--name" argument and try again.');
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
migrationName = migrationName.replace(/\W+/g, '-').toLowerCase();
|
|
1189
|
-
|
|
1190
|
-
let migrationsPath = applicationOptions.migrationsPath;
|
|
1191
|
-
let migrationID = this.getRevisionNumber();
|
|
1192
|
-
let migrationWritePath = Path.join(migrationsPath, `${migrationID}-${migrationName}.js`);
|
|
1193
|
-
let migrationSource = await this.generateMigrationFromDiff(connection, dbSchema, schemaDiff, migrationID);
|
|
1194
|
-
|
|
1195
|
-
FileSystem.writeFileSync(migrationWritePath, migrationSource, 'utf8');
|
|
1196
|
-
|
|
1197
|
-
console.log(`New migration to revision ${migrationID} has been written to file '${migrationWritePath}'`);
|
|
1198
|
-
}
|
|
1199
|
-
};
|
|
1200
|
-
});
|