db-migrate 1.6.0 → 1.7.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/api.js +21 -0
- package/lib/commands/generated.js +1 -0
- package/lib/commands/run.js +14 -1
- package/lib/commands/set-default-argv.js +1 -1
- package/lib/commands/status.js +36 -0
- package/lib/dml.js +38 -0
- package/lib/learn.js +4 -2
- package/lib/release.js +114 -7
- package/lib/status.js +179 -0
- package/lib/walker.js +6 -0
- package/lib/work.js +4 -1
- package/package.json +1 -1
package/api.js
CHANGED
|
@@ -421,6 +421,27 @@ dbmigrate.prototype = {
|
|
|
421
421
|
this.internals.onComplete = callback;
|
|
422
422
|
},
|
|
423
423
|
|
|
424
|
+
/**
|
|
425
|
+
* What db-migrate knows about the database: pending migrations, the lock,
|
|
426
|
+
* the jobs of background migrations, the release and what is deprecated
|
|
427
|
+
* or due, see lib/status.js.
|
|
428
|
+
*/
|
|
429
|
+
status: function (scope, callback) {
|
|
430
|
+
if (typeof scope === 'function') {
|
|
431
|
+
callback = scope;
|
|
432
|
+
scope = undefined;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (scope) {
|
|
436
|
+
this.internals.migrationMode = scope;
|
|
437
|
+
this.internals.matching = scope;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return Promise.resolve(load('status')(this.internals, this.config)).asCallback(
|
|
441
|
+
callback
|
|
442
|
+
);
|
|
443
|
+
},
|
|
444
|
+
|
|
424
445
|
/**
|
|
425
446
|
* Runs the jobs of background migrations, see lib/work.js for the
|
|
426
447
|
* options. Returns { done, stop }, done resolves with the jobs done and
|
|
@@ -23,6 +23,7 @@ const files = {
|
|
|
23
23
|
"run": require('./run.js'),
|
|
24
24
|
"seed": require('./seed.js'),
|
|
25
25
|
"set-default-argv": require('./set-default-argv.js'),
|
|
26
|
+
"status": require('./status.js'),
|
|
26
27
|
"sync": require('./sync.js'),
|
|
27
28
|
"undo-seed": require('./undo-seed.js'),
|
|
28
29
|
"up": require('./up.js'),
|
package/lib/commands/run.js
CHANGED
|
@@ -97,6 +97,19 @@ function run (internals, config) {
|
|
|
97
97
|
report(load('db')(internals, config));
|
|
98
98
|
}
|
|
99
99
|
break;
|
|
100
|
+
case 'status':
|
|
101
|
+
if (folder[1]) {
|
|
102
|
+
internals.matching = folder[1];
|
|
103
|
+
internals.migrationMode = folder[1];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
report(
|
|
107
|
+
load('status')(internals, config).then(result => {
|
|
108
|
+
console.log(require('../status').format(result));
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
break;
|
|
112
|
+
|
|
100
113
|
case 'work': {
|
|
101
114
|
// runs the jobs of background migrations, until none is left or, with
|
|
102
115
|
// --watch, until stopped
|
|
@@ -166,7 +179,7 @@ function run (internals, config) {
|
|
|
166
179
|
);
|
|
167
180
|
} else {
|
|
168
181
|
log.error(
|
|
169
|
-
'Invalid Action: Must be [up|down|check|create|reset|sync|db|seed|work].'
|
|
182
|
+
'Invalid Action: Must be [up|down|check|create|reset|sync|db|seed|work|status].'
|
|
170
183
|
);
|
|
171
184
|
yargs.showHelp();
|
|
172
185
|
process.exit(1);
|
|
@@ -27,7 +27,7 @@ module.exports = function (internals, isModule) {
|
|
|
27
27
|
internals.argv = yargs
|
|
28
28
|
.default(defaultConfig)
|
|
29
29
|
.usage(
|
|
30
|
-
'Usage: db-migrate [up|down|check|reset|sync|create|db|seed|work] ' +
|
|
30
|
+
'Usage: db-migrate [up|down|check|reset|sync|create|db|seed|work|status] ' +
|
|
31
31
|
'[[dbname/]migrationName|all] [options]'
|
|
32
32
|
)
|
|
33
33
|
.describe(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const migrationHook = require('./helper/migration-hook.js');
|
|
5
|
+
const status = require('../status');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The status of the database, see lib/status.js.
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function (internals, config) {
|
|
11
|
+
await migrationHook(internals);
|
|
12
|
+
const Migrator = require('../walker.js');
|
|
13
|
+
const index = require('../../connect');
|
|
14
|
+
|
|
15
|
+
const migrator = await index.connect(
|
|
16
|
+
{
|
|
17
|
+
config: config.getCurrent().settings,
|
|
18
|
+
internals: internals,
|
|
19
|
+
prefix: 'migration'
|
|
20
|
+
},
|
|
21
|
+
Migrator
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
migrator.directory = path.resolve(
|
|
25
|
+
internals.argv['migrations-dir'],
|
|
26
|
+
internals.locTitle || ''
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
await migrator.createMigrationsTable();
|
|
31
|
+
const result = await status(migrator);
|
|
32
|
+
return internals.onComplete(migrator, internals, null, result);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return internals.onComplete(migrator, internals, err);
|
|
35
|
+
}
|
|
36
|
+
};
|
package/lib/dml.js
CHANGED
|
@@ -798,6 +798,35 @@ class Dml {
|
|
|
798
798
|
);
|
|
799
799
|
}
|
|
800
800
|
|
|
801
|
+
/**
|
|
802
|
+
* dropBackups([migration]), drops the backups of update and delete of the
|
|
803
|
+
* migrations due, or of the one named, due or not. They can not be
|
|
804
|
+
* reverted afterwards, nor this migration.
|
|
805
|
+
*/
|
|
806
|
+
dropBackups (migration) {
|
|
807
|
+
return this.step(
|
|
808
|
+
'dropBackups',
|
|
809
|
+
migration ? [migration] : [],
|
|
810
|
+
() => ({ t: 3, a: 'dropBackups', c: [migration || null] }),
|
|
811
|
+
async () => {
|
|
812
|
+
const Release = require('./release');
|
|
813
|
+
const groups = await Release.backupsOf(
|
|
814
|
+
this.pdriver,
|
|
815
|
+
this.internals,
|
|
816
|
+
this.internals.releaseCurrent || 0,
|
|
817
|
+
this.internals.releaseIndex || {},
|
|
818
|
+
!!migration
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
for (const group of groups) {
|
|
822
|
+
if (!migration || group.m === migration) {
|
|
823
|
+
await Release.dropBackups(this.driver, this.pdriver, this.internals, group);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
801
830
|
/**
|
|
802
831
|
* Reading is no step, it does not change anything.
|
|
803
832
|
*/
|
|
@@ -835,6 +864,14 @@ class Dml {
|
|
|
835
864
|
entry.b = 1;
|
|
836
865
|
entry.o = 0;
|
|
837
866
|
await this.save();
|
|
867
|
+
// dropped once the migration is final, after enough releases
|
|
868
|
+
await require('./release').registerBackup(
|
|
869
|
+
this.pdriver,
|
|
870
|
+
this.internals,
|
|
871
|
+
entry.c[1],
|
|
872
|
+
this.file.name,
|
|
873
|
+
table
|
|
874
|
+
);
|
|
838
875
|
}
|
|
839
876
|
|
|
840
877
|
/**
|
|
@@ -991,6 +1028,7 @@ const revert = async (context, file, internals, pdriver, d) => {
|
|
|
991
1028
|
entry.b = 2;
|
|
992
1029
|
await d.save();
|
|
993
1030
|
await d.driver.runSql(`DROP TABLE IF EXISTS ${d.ddl(entry.c[1])}`);
|
|
1031
|
+
await require('./release').forgetBackups(pdriver, internals, [entry.c[1]]);
|
|
994
1032
|
}
|
|
995
1033
|
}
|
|
996
1034
|
|
package/lib/learn.js
CHANGED
|
@@ -145,10 +145,12 @@ class STD {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
renameTable (t, n) {
|
|
148
|
-
// deprecations move with the table
|
|
148
|
+
// deprecations move with the table, unless it is renamed for its own
|
|
149
|
+
// deprecation, which stays with its name
|
|
149
150
|
['tables', 'columns'].forEach(kind => {
|
|
150
151
|
const d = this.deprecated[kind];
|
|
151
|
-
|
|
152
|
+
const own = kind === 'tables' && d && d[t] && d[t].to === n;
|
|
153
|
+
if (d && d[t] !== undefined && !own) {
|
|
152
154
|
d[n] = d[t];
|
|
153
155
|
delete d[t];
|
|
154
156
|
}
|
package/lib/release.js
CHANGED
|
@@ -28,6 +28,7 @@ const log = require('db-migrate-shared').log;
|
|
|
28
28
|
|
|
29
29
|
const PREFIX = '__dbmigrate_release__:';
|
|
30
30
|
const PURGES = '__dbmigrate_purges__';
|
|
31
|
+
const BACKUPS = '__dbmigrate_backups__';
|
|
31
32
|
const DEFAULTS = { releases: 4, drop: 'manual' };
|
|
32
33
|
const DROPS = ['auto', 'manual'];
|
|
33
34
|
|
|
@@ -221,13 +222,18 @@ const purges = async (pdriver, internals) => {
|
|
|
221
222
|
return { row, value: row ? JSON.parse(row.value) : {} };
|
|
222
223
|
};
|
|
223
224
|
|
|
224
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Changes a row of the state by compare and swap, retried until nobody
|
|
227
|
+
* wrote in between. mutate returns false to leave it as it is.
|
|
228
|
+
*/
|
|
229
|
+
const changeState = async (pdriver, internals, key, mutate) => {
|
|
225
230
|
if (internals.dryRun) {
|
|
226
231
|
return;
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
230
|
-
const
|
|
235
|
+
const row = await pdriver._getKV(internals.migrationState, key);
|
|
236
|
+
const value = row ? JSON.parse(row.value) : {};
|
|
231
237
|
if (mutate(value) === false) {
|
|
232
238
|
return;
|
|
233
239
|
}
|
|
@@ -235,23 +241,26 @@ const changePurges = async (pdriver, internals, mutate) => {
|
|
|
235
241
|
const next = JSON.stringify(value);
|
|
236
242
|
if (!row) {
|
|
237
243
|
try {
|
|
238
|
-
await pdriver._insertKV(internals.migrationState,
|
|
244
|
+
await pdriver._insertKV(internals.migrationState, key, next);
|
|
239
245
|
} catch (err) {
|
|
240
|
-
log.verbose(
|
|
246
|
+
log.verbose(`[release] ${key} inserted concurrently`, err.message);
|
|
241
247
|
}
|
|
242
248
|
} else {
|
|
243
|
-
await pdriver._updateKVC(internals.migrationState,
|
|
249
|
+
await pdriver._updateKVC(internals.migrationState, key, next, 'value', row.value);
|
|
244
250
|
}
|
|
245
251
|
|
|
246
|
-
const after = await pdriver._getKV(internals.migrationState,
|
|
252
|
+
const after = await pdriver._getKV(internals.migrationState, key);
|
|
247
253
|
if (after && after.value === next) {
|
|
248
254
|
return;
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
257
|
|
|
252
|
-
throw new Error(
|
|
258
|
+
throw new Error(`[release] could not change ${key}, too much contention`);
|
|
253
259
|
};
|
|
254
260
|
|
|
261
|
+
const changePurges = (pdriver, internals, mutate) =>
|
|
262
|
+
changeState(pdriver, internals, PURGES, mutate);
|
|
263
|
+
|
|
255
264
|
/**
|
|
256
265
|
* Schedules purging the rows of a soft delete, { purge: true } takes the
|
|
257
266
|
* options of the project.
|
|
@@ -285,6 +294,77 @@ const forgetPurges = (pdriver, internals, t, prefix) =>
|
|
|
285
294
|
marks.forEach(mark => delete value[mark]);
|
|
286
295
|
});
|
|
287
296
|
|
|
297
|
+
/**
|
|
298
|
+
* The backup tables of update and delete in copy mode, kept to revert their
|
|
299
|
+
* migration: { [backup]: { m: migration, t: table, r: release } }. Once
|
|
300
|
+
* enough releases passed, the migration is final, its backups are dropped.
|
|
301
|
+
*/
|
|
302
|
+
const registerBackup = (pdriver, internals, backup, migration, t) =>
|
|
303
|
+
changeState(pdriver, internals, BACKUPS, value => {
|
|
304
|
+
value[backup] = { m: migration, t, r: internals.releaseLabel || null };
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const forgetBackups = (pdriver, internals, backups) =>
|
|
308
|
+
changeState(pdriver, internals, BACKUPS, value => {
|
|
309
|
+
if (!backups.some(b => value[b])) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
backups.forEach(b => delete value[b]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The migrations with backups, with their age and options, those due only
|
|
318
|
+
* unless all.
|
|
319
|
+
*/
|
|
320
|
+
const backupsOf = async (pdriver, internals, index, releaseIndex, all = false) => {
|
|
321
|
+
const row = await pdriver._getKV(internals.migrationState, BACKUPS);
|
|
322
|
+
const value = row ? JSON.parse(row.value) : {};
|
|
323
|
+
const migrations = {};
|
|
324
|
+
|
|
325
|
+
Object.keys(value).forEach(backup => {
|
|
326
|
+
const e = value[backup];
|
|
327
|
+
if (!migrations[e.m]) {
|
|
328
|
+
const age = index - (e.r === null ? 0 : releaseIndex[e.r] || 0);
|
|
329
|
+
migrations[e.m] = Object.assign({ m: e.m, age, backups: [] }, settings(internals, e));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
migrations[e.m].backups.push(backup);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
return Object.keys(migrations)
|
|
336
|
+
.map(m => migrations[m])
|
|
337
|
+
.filter(g => all || g.age >= g.releases);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Drops the backups of a migration, which can not be reverted afterwards:
|
|
342
|
+
* its steps are recorded as irreversible first.
|
|
343
|
+
*/
|
|
344
|
+
const dropBackups = async (driver, pdriver, internals, group) => {
|
|
345
|
+
log.info(`[release] dropping the backups of ${group.m}, it can not be reverted anymore`);
|
|
346
|
+
if (internals.dryRun) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const row = await pdriver._getKV(internals.migrationState, group.m);
|
|
351
|
+
if (row) {
|
|
352
|
+
const state = JSON.parse(row.value);
|
|
353
|
+
state.s = (state.s || []).map(e =>
|
|
354
|
+
e.t === 4 && group.backups.indexOf(e.c[1]) !== -1
|
|
355
|
+
? { t: 3, a: e.a, c: [e.c[0]], n: e.n }
|
|
356
|
+
: e
|
|
357
|
+
);
|
|
358
|
+
await pdriver._updateKV(internals.migrationState, group.m, JSON.stringify(state));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const backup of group.backups) {
|
|
362
|
+
await driver.runSql(`DROP TABLE IF EXISTS ${driver.escapeDDL(backup)}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await forgetBackups(pdriver, internals, group.backups);
|
|
366
|
+
};
|
|
367
|
+
|
|
288
368
|
const duePurges = async (pdriver, internals, index, releaseIndex) => {
|
|
289
369
|
const { value } = await purges(pdriver, internals);
|
|
290
370
|
|
|
@@ -326,6 +406,14 @@ const start = async (walker, label, index, releaseIndex, mode = 'up') => {
|
|
|
326
406
|
if (purged.length) {
|
|
327
407
|
await purge(walker, label, purged);
|
|
328
408
|
}
|
|
409
|
+
|
|
410
|
+
const final = (
|
|
411
|
+
await backupsOf(walker._pdriver, internals, index, releaseIndex)
|
|
412
|
+
).filter(g => g.drop === 'auto');
|
|
413
|
+
|
|
414
|
+
for (const group of final) {
|
|
415
|
+
await dropBackups(walker._driver, walker._pdriver, internals, group);
|
|
416
|
+
}
|
|
329
417
|
}
|
|
330
418
|
|
|
331
419
|
if (!renames.length && !drops.length) {
|
|
@@ -463,6 +551,19 @@ const assertRevertible = async (walker, label) => {
|
|
|
463
551
|
/**
|
|
464
552
|
* Warns about what is due for dropping with drop 'manual'.
|
|
465
553
|
*/
|
|
554
|
+
const warnDueBackups = async (pdriver, internals, index, releaseIndex) => {
|
|
555
|
+
(await backupsOf(pdriver, internals, index, releaseIndex))
|
|
556
|
+
.filter(g => g.drop === 'manual')
|
|
557
|
+
.forEach(g =>
|
|
558
|
+
log.warn(
|
|
559
|
+
`[release] the backups of ${g.m} are due for dropping, it ran ` +
|
|
560
|
+
`${g.age} releases ago. Drop them in a dml migration with ` +
|
|
561
|
+
`db.dropBackups(${JSON.stringify(g.m)}), it can not be reverted ` +
|
|
562
|
+
'afterwards.'
|
|
563
|
+
)
|
|
564
|
+
);
|
|
565
|
+
};
|
|
566
|
+
|
|
466
567
|
const warnDuePurges = async (pdriver, internals, index, releaseIndex) => {
|
|
467
568
|
(await duePurges(pdriver, internals, index, releaseIndex))
|
|
468
569
|
.filter(e => e.drop === 'manual')
|
|
@@ -495,6 +596,12 @@ const warnDue = (internals, index, releaseIndex) => {
|
|
|
495
596
|
module.exports = {
|
|
496
597
|
PREFIX,
|
|
497
598
|
PURGES,
|
|
599
|
+
BACKUPS,
|
|
600
|
+
registerBackup,
|
|
601
|
+
forgetBackups,
|
|
602
|
+
backupsOf,
|
|
603
|
+
dropBackups,
|
|
604
|
+
warnDueBackups,
|
|
498
605
|
schedulePurge,
|
|
499
606
|
forgetPurges,
|
|
500
607
|
warnDuePurges,
|
package/lib/status.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const dbmUtil = require('db-migrate-shared').util;
|
|
4
|
+
const File = require('./file');
|
|
5
|
+
const State = require('./state');
|
|
6
|
+
const Jobs = require('./jobs');
|
|
7
|
+
const Release = require('./release');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What db-migrate knows about the database, without changing anything:
|
|
11
|
+
*
|
|
12
|
+
* pending: migrations not run yet, background migrations running excluded
|
|
13
|
+
* lock: the process holding the migration lock, and the migration it was
|
|
14
|
+
* interrupted in, if any
|
|
15
|
+
* jobs: the jobs of background migrations, paused by whom if so
|
|
16
|
+
* release: the latest release migrated
|
|
17
|
+
* deprecated: tables and columns deprecated, renamed or not, and when due
|
|
18
|
+
* purges, backups: soft deletes and backups due
|
|
19
|
+
*/
|
|
20
|
+
module.exports = async function status (walker) {
|
|
21
|
+
const internals = walker.internals;
|
|
22
|
+
const pdriver = walker._pdriver;
|
|
23
|
+
const supports = State.supportsLock(pdriver);
|
|
24
|
+
|
|
25
|
+
const [allFiles, completed] = await Promise.all([
|
|
26
|
+
File.loadFromFileystem(walker.directory, walker.prefix, internals).catch(err => {
|
|
27
|
+
if (err.code === 'ENOENT') {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
throw err;
|
|
32
|
+
}),
|
|
33
|
+
File.loadFromDatabase(walker.directory, walker.prefix, walker._driver, internals)
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
walker._releases(allFiles, completed);
|
|
37
|
+
const release = walker._release;
|
|
38
|
+
const index = walker._applied;
|
|
39
|
+
|
|
40
|
+
const jobs = supports ? await Jobs.list(pdriver, internals) : {};
|
|
41
|
+
const pending = dbmUtil
|
|
42
|
+
.filterUp(allFiles, completed, undefined, Number.MAX_VALUE)
|
|
43
|
+
.filter(file => !jobs[file.name] || jobs[file.name].s === 'failed')
|
|
44
|
+
.map(file => file.name);
|
|
45
|
+
|
|
46
|
+
const result = {
|
|
47
|
+
pending,
|
|
48
|
+
lock: null,
|
|
49
|
+
jobs: [],
|
|
50
|
+
paused: null,
|
|
51
|
+
release: null,
|
|
52
|
+
deprecated: [],
|
|
53
|
+
purges: [],
|
|
54
|
+
backups: []
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const labels = Object.keys(release.index);
|
|
58
|
+
result.release = labels.find(label => release.index[label] === index) || null;
|
|
59
|
+
|
|
60
|
+
if (supports) {
|
|
61
|
+
const lock = await State.readLock(pdriver, internals);
|
|
62
|
+
if (lock) {
|
|
63
|
+
const s = JSON.parse(lock.value).s;
|
|
64
|
+
result.lock = {
|
|
65
|
+
held: !!s.ID,
|
|
66
|
+
since: s.date,
|
|
67
|
+
interrupted: s.fin === 0 && s.f ? { migration: s.f, op: s.o, step: s.step, done: s.done } : null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const paused = await Jobs.paused(pdriver, internals);
|
|
72
|
+
result.paused = paused ? paused.ID : null;
|
|
73
|
+
result.jobs = Object.keys(jobs)
|
|
74
|
+
.sort()
|
|
75
|
+
.map(name => {
|
|
76
|
+
const job = jobs[name];
|
|
77
|
+
return {
|
|
78
|
+
name,
|
|
79
|
+
state: job.s,
|
|
80
|
+
blocking: !!job.blocking,
|
|
81
|
+
step: job.step,
|
|
82
|
+
done: job.done,
|
|
83
|
+
error: job.err || null
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const due = (e, age) => {
|
|
88
|
+
const options = Release.settings(internals, e);
|
|
89
|
+
return Object.assign({ age, due: age >= options.releases }, options);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
result.deprecated = Release.list(internals, index, release.index).map(item =>
|
|
93
|
+
Object.assign(
|
|
94
|
+
{
|
|
95
|
+
table: item.t,
|
|
96
|
+
column: item.c || null,
|
|
97
|
+
name: item.name,
|
|
98
|
+
renamed: item.renamed,
|
|
99
|
+
release: item.entry.r
|
|
100
|
+
},
|
|
101
|
+
due(item.entry, item.age)
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const purges = await pdriver._getKV(internals.migrationState, Release.PURGES);
|
|
106
|
+
const scheduled = purges ? JSON.parse(purges.value) : {};
|
|
107
|
+
result.purges = Object.keys(scheduled).map(mark => {
|
|
108
|
+
const e = scheduled[mark];
|
|
109
|
+
const age = index - (e.r === null ? 0 : release.index[e.r] || 0);
|
|
110
|
+
return Object.assign({ table: e.t, by: mark.slice(5), release: e.r }, due(e, age));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
result.backups = (
|
|
114
|
+
await Release.backupsOf(pdriver, internals, index, release.index, true)
|
|
115
|
+
).map(g => ({
|
|
116
|
+
migration: g.m,
|
|
117
|
+
tables: g.backups,
|
|
118
|
+
age: g.age,
|
|
119
|
+
due: g.age >= g.releases,
|
|
120
|
+
releases: g.releases,
|
|
121
|
+
drop: g.drop
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The status as text, for the command line.
|
|
130
|
+
*/
|
|
131
|
+
module.exports.format = s => {
|
|
132
|
+
const lines = [];
|
|
133
|
+
const section = (title, items, line) => {
|
|
134
|
+
lines.push(`${title}:${items.length ? '' : ' none'}`);
|
|
135
|
+
items.forEach(item => lines.push(` ${line(item)}`));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
section('Pending migrations', s.pending, name => name);
|
|
139
|
+
lines.push(`Release: ${s.release === null ? 'none' : s.release}`);
|
|
140
|
+
|
|
141
|
+
if (s.lock) {
|
|
142
|
+
lines.push(
|
|
143
|
+
`Migration lock: ${s.lock.held ? `held since ${s.lock.since}` : 'free'}` +
|
|
144
|
+
(s.lock.interrupted
|
|
145
|
+
? `, ${s.lock.interrupted.op} of ${s.lock.interrupted.migration} ` +
|
|
146
|
+
`interrupted at step ${s.lock.interrupted.step}`
|
|
147
|
+
: '')
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
section(
|
|
152
|
+
`Background jobs${s.paused ? ' (paused for migrations)' : ''}`,
|
|
153
|
+
s.jobs,
|
|
154
|
+
job =>
|
|
155
|
+
`${job.name}: ${job.state}${job.blocking ? ', blocking' : ''}` +
|
|
156
|
+
(job.step ? `, step ${job.step}, ${job.done} done` : '') +
|
|
157
|
+
(job.error ? `, ${job.error}` : '')
|
|
158
|
+
);
|
|
159
|
+
section(
|
|
160
|
+
'Deprecated',
|
|
161
|
+
s.deprecated,
|
|
162
|
+
d =>
|
|
163
|
+
`${d.column ? `column "${d.column}" of "${d.table}"` : `table "${d.table}"`}` +
|
|
164
|
+
`${d.renamed ? ` renamed to ${d.name}` : ''}, ${d.age} of ${d.releases} ` +
|
|
165
|
+
`releases${d.due ? `, due to drop (${d.drop})` : ''}`
|
|
166
|
+
);
|
|
167
|
+
section(
|
|
168
|
+
'Purges',
|
|
169
|
+
s.purges,
|
|
170
|
+
p => `"${p.table}" by ${p.by}, ${p.age} of ${p.releases} releases${p.due ? `, due (${p.drop})` : ''}`
|
|
171
|
+
);
|
|
172
|
+
section(
|
|
173
|
+
'Backups',
|
|
174
|
+
s.backups,
|
|
175
|
+
b => `${b.migration}: ${b.tables.length} table(s), ${b.age} of ${b.releases} releases${b.due ? `, due (${b.drop})` : ''}`
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return lines.join('\n');
|
|
179
|
+
};
|
package/lib/walker.js
CHANGED
package/lib/work.js
CHANGED
|
@@ -102,7 +102,10 @@ const runJob = async (walker, name, id, options, stopping, result, seen) => {
|
|
|
102
102
|
);
|
|
103
103
|
const file = files.find(f => f.name === name);
|
|
104
104
|
// the release of the migration, a soft delete schedules its purge by it
|
|
105
|
-
|
|
105
|
+
const release = Release.releases(files);
|
|
106
|
+
internals.releaseLabel = (release.map[name] || {}).label || null;
|
|
107
|
+
internals.releaseIndex = release.index;
|
|
108
|
+
internals.releaseCurrent = (release.map[name] || {}).index || 0;
|
|
106
109
|
|
|
107
110
|
if (!file) {
|
|
108
111
|
await update(job => {
|