db-migrate 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chain.js +84 -0
- package/lib/dml.js +34 -5
- package/lib/driver/shadow.js +15 -0
- package/lib/learn.js +52 -1
- package/lib/methods/v2/statedone.js +2 -1
- package/lib/methods/v2/statetravel.js +2 -1
- package/lib/release.js +502 -0
- package/lib/state.js +1 -1
- package/lib/walker.js +120 -16
- package/lib/work.js +3 -0
- package/package.json +1 -1
package/lib/chain.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const log = require('db-migrate-shared').log;
|
|
2
|
+
const Release = require('./release');
|
|
3
|
+
const { unknownTable, unknownColumn } = require('./learn');
|
|
2
4
|
|
|
3
5
|
const DEFAULT = [
|
|
4
6
|
'renameCollection',
|
|
@@ -151,6 +153,8 @@ class Chain {
|
|
|
151
153
|
*/
|
|
152
154
|
this.adopt = {};
|
|
153
155
|
|
|
156
|
+
this.deprecations();
|
|
157
|
+
|
|
154
158
|
DEFAULT.concat(Object.keys(this.context.learnable || [])).forEach(
|
|
155
159
|
method => {
|
|
156
160
|
this[method] = function (...args) {
|
|
@@ -173,6 +177,86 @@ class Chain {
|
|
|
173
177
|
);
|
|
174
178
|
}
|
|
175
179
|
|
|
180
|
+
/**
|
|
181
|
+
* deprecateTable, deprecateColumn and dropDeprecated, see Release. The
|
|
182
|
+
* marks are learned only, relaxing notNull of a column is executed.
|
|
183
|
+
*/
|
|
184
|
+
deprecations () {
|
|
185
|
+
const internals = this.internals;
|
|
186
|
+
|
|
187
|
+
this.deprecateTable = async (t, options) => {
|
|
188
|
+
this.transferInt();
|
|
189
|
+
if (!internals.schema.c[t]) {
|
|
190
|
+
throw unknownTable(t);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const entry = Release.entry(internals, t, options);
|
|
194
|
+
return this.run('setDeprecated', ['tables', t, null, entry], 'learn');
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
this.deprecateColumn = async (t, c, options) => {
|
|
198
|
+
this.transferInt();
|
|
199
|
+
const columns = Release.columnsOf(internals.schema, t);
|
|
200
|
+
if (!columns) {
|
|
201
|
+
throw unknownTable(t);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!columns[c]) {
|
|
205
|
+
throw unknownColumn(t, c);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const entry = Release.entry(internals, c, options);
|
|
209
|
+
if (columns[c].notNull) {
|
|
210
|
+
// the application stops writing it. The whole spec, MySQL changes
|
|
211
|
+
// a column by its full definition
|
|
212
|
+
await this.changeColumn(
|
|
213
|
+
t,
|
|
214
|
+
c,
|
|
215
|
+
Object.assign({}, columns[c], { notNull: false })
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return this.run('setDeprecated', ['columns', t, c, entry], 'learn');
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* dropDeprecated() drops what is due, dropDeprecated(table) or
|
|
224
|
+
* dropDeprecated(table, column) the one named, due or not.
|
|
225
|
+
*/
|
|
226
|
+
this.dropDeprecated = async (t, c) => {
|
|
227
|
+
this.transferInt();
|
|
228
|
+
const items = Release.list(
|
|
229
|
+
internals,
|
|
230
|
+
internals.releaseCurrent || 0,
|
|
231
|
+
internals.releaseIndex || {}
|
|
232
|
+
);
|
|
233
|
+
let targets;
|
|
234
|
+
|
|
235
|
+
if (t === undefined) {
|
|
236
|
+
targets = items.filter(
|
|
237
|
+
item => item.age >= Release.settings(internals, item.entry).releases
|
|
238
|
+
);
|
|
239
|
+
} else {
|
|
240
|
+
targets = items.filter(item =>
|
|
241
|
+
c === undefined
|
|
242
|
+
? item.kind === 'tables' && item.t === t
|
|
243
|
+
: item.kind === 'columns' && item.t === t && item.c === c
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (!targets.length) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`${c === undefined ? `The table "${t}"` : `The column "${c}" of "${t}"`} ` +
|
|
249
|
+
'is not deprecated, deprecate it first'
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const item of targets) {
|
|
255
|
+
await Release.drop(this, item);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
176
260
|
transferInt () {
|
|
177
261
|
rollbackCounter = counter;
|
|
178
262
|
counter = 0;
|
package/lib/dml.js
CHANGED
|
@@ -663,12 +663,20 @@ class Dml {
|
|
|
663
663
|
const condition = this.where(where);
|
|
664
664
|
const key = this.key(table, options, 'delete');
|
|
665
665
|
const time = options.value === undefined;
|
|
666
|
+
const purge = options.purge;
|
|
667
|
+
|
|
668
|
+
if (purge !== undefined && purge !== true && !isObject(purge)) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`delete("${table}") takes purge: true or { releases, drop }, to purge ` +
|
|
671
|
+
'the rows with a later release'
|
|
672
|
+
);
|
|
673
|
+
}
|
|
666
674
|
|
|
667
675
|
return this.step(
|
|
668
676
|
'delete',
|
|
669
677
|
[table],
|
|
670
678
|
op => ({ t: 4, a: 'soft', c: [table, column, key, deleted(this.file, op)] }),
|
|
671
|
-
entry => {
|
|
679
|
+
async entry => {
|
|
672
680
|
const mark = entry.c[3];
|
|
673
681
|
const set =
|
|
674
682
|
`${this.ddl(column)} = ${time ? 'CURRENT_TIMESTAMP' : '?'}, ` +
|
|
@@ -687,6 +695,17 @@ class Dml {
|
|
|
687
695
|
);
|
|
688
696
|
}
|
|
689
697
|
|
|
698
|
+
if (purge) {
|
|
699
|
+
await require('./release').schedulePurge(
|
|
700
|
+
this.pdriver,
|
|
701
|
+
this.internals,
|
|
702
|
+
mark,
|
|
703
|
+
table,
|
|
704
|
+
key,
|
|
705
|
+
purge
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
690
709
|
return this.untilDone(table, key, active, options, match =>
|
|
691
710
|
this.driver.runSql(
|
|
692
711
|
`UPDATE ${this.ddl(table)} SET ${set} WHERE ${match.sql}`,
|
|
@@ -715,7 +734,15 @@ class Dml {
|
|
|
715
734
|
'purge',
|
|
716
735
|
[table].concat(migration ? [migration] : []),
|
|
717
736
|
() => ({ t: 3, a: 'purge', c: [table] }),
|
|
718
|
-
() => {
|
|
737
|
+
async () => {
|
|
738
|
+
// nothing left to purge with a later release
|
|
739
|
+
await require('./release').forgetPurges(
|
|
740
|
+
this.pdriver,
|
|
741
|
+
this.internals,
|
|
742
|
+
table,
|
|
743
|
+
mark
|
|
744
|
+
);
|
|
745
|
+
|
|
719
746
|
const condition = {
|
|
720
747
|
sql: `${this.ddl(FLAG)} LIKE ? ESCAPE '!'`,
|
|
721
748
|
params: [`%${like(mark)}%`]
|
|
@@ -907,8 +934,9 @@ const reverts = {
|
|
|
907
934
|
);
|
|
908
935
|
},
|
|
909
936
|
|
|
910
|
-
soft: (d, [table, column, key, mark]) =>
|
|
911
|
-
d.
|
|
937
|
+
soft: async (d, [table, column, key, mark]) => {
|
|
938
|
+
await require('./release').forgetPurges(d.pdriver, d.internals, table, mark);
|
|
939
|
+
return d.untilDone(
|
|
912
940
|
table,
|
|
913
941
|
key,
|
|
914
942
|
{
|
|
@@ -923,7 +951,8 @@ const reverts = {
|
|
|
923
951
|
`WHERE ${match.sql}`,
|
|
924
952
|
[mark].concat(match.params)
|
|
925
953
|
)
|
|
926
|
-
)
|
|
954
|
+
);
|
|
955
|
+
}
|
|
927
956
|
};
|
|
928
957
|
|
|
929
958
|
const irreversibleSteps = internals =>
|
package/lib/driver/shadow.js
CHANGED
|
@@ -70,5 +70,20 @@ exports.overshadow = function (db, ShadowProto, ShadowError) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// instructions of db-migrate itself, which no driver has, e.g. setDeprecated
|
|
74
|
+
let proto = Object.getPrototypeOf(ShadowProto);
|
|
75
|
+
while (proto && proto !== Object.prototype) {
|
|
76
|
+
Object.getOwnPropertyNames(proto).forEach(prop => {
|
|
77
|
+
if (
|
|
78
|
+
prop !== 'constructor' &&
|
|
79
|
+
newDb[prop] === undefined &&
|
|
80
|
+
typeof ShadowProto[prop] === 'function'
|
|
81
|
+
) {
|
|
82
|
+
newDb[prop] = ShadowProto[prop];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
proto = Object.getPrototypeOf(proto);
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
return newDb;
|
|
74
89
|
};
|
package/lib/learn.js
CHANGED
|
@@ -59,6 +59,14 @@ class STD {
|
|
|
59
59
|
schema.e = {};
|
|
60
60
|
}
|
|
61
61
|
this.extra = schema.e;
|
|
62
|
+
if (!schema.d) {
|
|
63
|
+
schema.d = {};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* deprecated tables and columns, d.tables[table] and
|
|
67
|
+
* d.columns[table][column], see Release
|
|
68
|
+
*/
|
|
69
|
+
this.deprecated = schema.d;
|
|
62
70
|
if (unlearn) {
|
|
63
71
|
this.modC = [];
|
|
64
72
|
} else {
|
|
@@ -137,6 +145,15 @@ class STD {
|
|
|
137
145
|
}
|
|
138
146
|
|
|
139
147
|
renameTable (t, n) {
|
|
148
|
+
// deprecations move with the table
|
|
149
|
+
['tables', 'columns'].forEach(kind => {
|
|
150
|
+
const d = this.deprecated[kind];
|
|
151
|
+
if (d && d[t] !== undefined) {
|
|
152
|
+
d[n] = d[t];
|
|
153
|
+
delete d[t];
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
140
157
|
if (this.schema[t]) {
|
|
141
158
|
this.schema[n] = this.schema[t];
|
|
142
159
|
delete this.schema[t];
|
|
@@ -310,7 +327,9 @@ class STD {
|
|
|
310
327
|
const columns = hasColumns ? this.schema[t].columns : this.schema[t];
|
|
311
328
|
|
|
312
329
|
this.modS[t][c] = columns[c];
|
|
313
|
-
|
|
330
|
+
// a copy, the previous spec is kept to revert it. Reverting restores
|
|
331
|
+
// the previous spec as a whole.
|
|
332
|
+
columns[c] = Object.assign({}, this.unlearn ? {} : columns[c], s);
|
|
314
333
|
|
|
315
334
|
this.modC.push({ t: 1, a: 'changeColumn', c: [t, c] });
|
|
316
335
|
|
|
@@ -419,6 +438,38 @@ class STD {
|
|
|
419
438
|
return Promise.resolve(alter);
|
|
420
439
|
}
|
|
421
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Marks a table or column as deprecated, or removes the mark with entry
|
|
443
|
+
* null. Only learned, nothing is executed.
|
|
444
|
+
*/
|
|
445
|
+
setDeprecated (kind, t, c, entry) {
|
|
446
|
+
const d = this.deprecated;
|
|
447
|
+
if (!d[kind]) d[kind] = {};
|
|
448
|
+
|
|
449
|
+
let previous;
|
|
450
|
+
if (kind === 'tables') {
|
|
451
|
+
previous = d.tables[t] || null;
|
|
452
|
+
if (entry) {
|
|
453
|
+
d.tables[t] = entry;
|
|
454
|
+
} else {
|
|
455
|
+
delete d.tables[t];
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
if (!d.columns[t]) d.columns[t] = {};
|
|
459
|
+
previous = d.columns[t][c] || null;
|
|
460
|
+
if (entry) {
|
|
461
|
+
d.columns[t][c] = entry;
|
|
462
|
+
} else {
|
|
463
|
+
delete d.columns[t][c];
|
|
464
|
+
if (!Object.keys(d.columns[t]).length) delete d.columns[t];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
this.modC.push({ t: 2, a: 'setDeprecated', c: [kind, t, c, previous] });
|
|
469
|
+
|
|
470
|
+
return Promise.resolve();
|
|
471
|
+
}
|
|
472
|
+
|
|
422
473
|
//
|
|
423
474
|
// checkDBMS: dummy,
|
|
424
475
|
//
|
|
@@ -13,6 +13,7 @@ module.exports = {
|
|
|
13
13
|
return State.done(pdriver, internals.migrationOp, internals);
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// learned only, no method of the driver
|
|
17
|
+
return Shadow.overshadow(driver, { setDeprecated: done() }, done);
|
|
17
18
|
}
|
|
18
19
|
};
|
package/lib/release.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const log = require('db-migrate-shared').log;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Releases and the deprecation of tables and columns.
|
|
8
|
+
*
|
|
9
|
+
* A migration starts a release with _meta.release, any label. The releases
|
|
10
|
+
* are counted in the order of the migrations, a migration without a label
|
|
11
|
+
* belongs to the release before it, those before the first label to release
|
|
12
|
+
* 0. The labels themselves are not interpreted.
|
|
13
|
+
*
|
|
14
|
+
* db.deprecateTable and db.deprecateColumn mark a table or column in the
|
|
15
|
+
* release of their migration R. Once a migration starts a later release,
|
|
16
|
+
* db-migrate runs the steps due before it, as a migration of its own named
|
|
17
|
+
* __dbmigrate_release__:<label>:
|
|
18
|
+
*
|
|
19
|
+
* R+1: the table or column is renamed to __dbm_deprecated_<name>_<time>, so
|
|
20
|
+
* whatever still uses it fails, while the data is still there
|
|
21
|
+
* R+N: it is dropped, N = releases (4 by default), with drop 'auto' only.
|
|
22
|
+
* With drop 'manual' (default) db-migrate warns it is due, until
|
|
23
|
+
* db.dropDeprecated drops it.
|
|
24
|
+
*
|
|
25
|
+
* Reverting the first migration of a release reverts the steps run before
|
|
26
|
+
* it as well.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const PREFIX = '__dbmigrate_release__:';
|
|
30
|
+
const PURGES = '__dbmigrate_purges__';
|
|
31
|
+
const DEFAULTS = { releases: 4, drop: 'manual' };
|
|
32
|
+
const DROPS = ['auto', 'manual'];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The release of every migration: { label, index }, by name.
|
|
36
|
+
*/
|
|
37
|
+
const releases = files => {
|
|
38
|
+
const map = {};
|
|
39
|
+
const index = {};
|
|
40
|
+
let current = { label: null, index: 0 };
|
|
41
|
+
|
|
42
|
+
files
|
|
43
|
+
.slice()
|
|
44
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
45
|
+
.forEach(file => {
|
|
46
|
+
let label;
|
|
47
|
+
try {
|
|
48
|
+
label = (file.get()._meta || {}).release;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// an old migration which can not be loaded anymore
|
|
51
|
+
log.verbose(`[release] can not load ${file.name}: ${err.message}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (label !== undefined && label !== null && String(label) !== current.label) {
|
|
55
|
+
const key = String(label);
|
|
56
|
+
if (index[key] === undefined) {
|
|
57
|
+
index[key] = Object.keys(index).length + 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
current = { label: key, index: index[key] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
map[file.name] = current;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return { map, index };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The name a deprecated table or column is renamed to.
|
|
71
|
+
*/
|
|
72
|
+
const hiddenName = name => {
|
|
73
|
+
const time = Math.floor(Date.now() / 1000);
|
|
74
|
+
const plain = `__dbm_deprecated_${name}_${time}`;
|
|
75
|
+
|
|
76
|
+
if (plain.length <= 60) {
|
|
77
|
+
return plain;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const hash = crypto.createHash('sha256').update(name).digest('hex');
|
|
81
|
+
return `__dbm_deprecated_${hash.slice(0, 16)}_${time}`;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const checkOptions = (options = {}) => {
|
|
85
|
+
const out = {};
|
|
86
|
+
|
|
87
|
+
if (options.releases !== undefined) {
|
|
88
|
+
const n = Number(options.releases);
|
|
89
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`releases is the number of releases until dropping, at least 1, not ${options.releases}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
out.releases = n;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.drop !== undefined) {
|
|
99
|
+
if (DROPS.indexOf(options.drop) === -1) {
|
|
100
|
+
throw new Error(`drop is 'auto' or 'manual', not ${options.drop}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
out.drop = options.drop;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return out;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The options of an entry, given for it, else of the project, else the
|
|
111
|
+
* defaults. The project sets them as deprecation: { releases, drop }, in
|
|
112
|
+
* the rc file or the options of the programmable API.
|
|
113
|
+
*/
|
|
114
|
+
const settings = (internals, entry) => {
|
|
115
|
+
const project = checkOptions((internals.argv && internals.argv.deprecation) || {});
|
|
116
|
+
return Object.assign({}, DEFAULTS, project, entry.o || {});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const entry = (internals, name, options) => {
|
|
120
|
+
const e = { r: internals.releaseLabel || null, to: hiddenName(name) };
|
|
121
|
+
const o = checkOptions(options);
|
|
122
|
+
|
|
123
|
+
if (Object.keys(o).length) {
|
|
124
|
+
e.o = o;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return e;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const columnsOf = (schema, table) => {
|
|
131
|
+
const t = schema.c[table];
|
|
132
|
+
return t ? t.columns || t : null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* All deprecations with their state:
|
|
137
|
+
*
|
|
138
|
+
* name: the current name, renamed or not
|
|
139
|
+
* renamed: whether it was renamed
|
|
140
|
+
* age: releases since deprecated, up to index
|
|
141
|
+
*/
|
|
142
|
+
const list = (internals, index, releaseIndex) => {
|
|
143
|
+
const schema = internals.schema;
|
|
144
|
+
const d = schema.d || {};
|
|
145
|
+
const out = [];
|
|
146
|
+
const age = e => index - (e.r === null ? 0 : releaseIndex[e.r] || 0);
|
|
147
|
+
|
|
148
|
+
Object.keys(d.tables || {}).forEach(t => {
|
|
149
|
+
const e = d.tables[t];
|
|
150
|
+
const renamed = !!schema.c[e.to];
|
|
151
|
+
if (!renamed && !schema.c[t]) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
out.push({ kind: 'tables', t, entry: e, renamed, name: renamed ? e.to : t, age: age(e) });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
Object.keys(d.columns || {}).forEach(t => {
|
|
159
|
+
const columns = columnsOf(schema, t) || {};
|
|
160
|
+
Object.keys(d.columns[t]).forEach(c => {
|
|
161
|
+
const e = d.columns[t][c];
|
|
162
|
+
const renamed = !!columns[e.to];
|
|
163
|
+
if (!renamed && !columns[c]) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
out.push({ kind: 'columns', t, c, entry: e, renamed, name: renamed ? e.to : c, age: age(e) });
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return out;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const describe = item =>
|
|
175
|
+
item.kind === 'tables'
|
|
176
|
+
? `table "${item.t}"`
|
|
177
|
+
: `column "${item.c}" of "${item.t}"`;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Drops a deprecated table or column and forgets the deprecation, within a
|
|
181
|
+
* v2 migration.
|
|
182
|
+
*/
|
|
183
|
+
const drop = async (db, item) => {
|
|
184
|
+
if (item.kind === 'tables') {
|
|
185
|
+
await db.dropTable(item.name);
|
|
186
|
+
} else {
|
|
187
|
+
await db.removeColumn(item.t, item.name);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await db.run('setDeprecated', [item.kind, item.t, item.c || null, null], 'learn');
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The steps due when the release with index starts.
|
|
195
|
+
*/
|
|
196
|
+
const due = (internals, index, releaseIndex) => {
|
|
197
|
+
const renames = [];
|
|
198
|
+
const drops = [];
|
|
199
|
+
|
|
200
|
+
list(internals, index, releaseIndex).forEach(item => {
|
|
201
|
+
const { releases, drop } = settings(internals, item.entry);
|
|
202
|
+
|
|
203
|
+
if (item.age >= releases && drop === 'auto') {
|
|
204
|
+
drops.push(item);
|
|
205
|
+
} else if (item.age >= 1 && !item.renamed) {
|
|
206
|
+
renames.push(item);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return { renames, drops };
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Rows deleted in soft mode with { purge }, to delete for good once enough
|
|
215
|
+
* releases passed: { [mark]: { t, key, r, o } }, stored in the state.
|
|
216
|
+
* Changed by compare and swap, background jobs delete in soft mode as well.
|
|
217
|
+
*/
|
|
218
|
+
const purges = async (pdriver, internals) => {
|
|
219
|
+
const row = await pdriver._getKV(internals.migrationState, PURGES);
|
|
220
|
+
return { row, value: row ? JSON.parse(row.value) : {} };
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const changePurges = async (pdriver, internals, mutate) => {
|
|
224
|
+
if (internals.dryRun) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (let attempt = 0; attempt < 50; attempt++) {
|
|
229
|
+
const { row, value } = await purges(pdriver, internals);
|
|
230
|
+
if (mutate(value) === false) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const next = JSON.stringify(value);
|
|
235
|
+
if (!row) {
|
|
236
|
+
try {
|
|
237
|
+
await pdriver._insertKV(internals.migrationState, PURGES, next);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
log.verbose('[release] purges inserted concurrently', err.message);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
await pdriver._updateKVC(internals.migrationState, PURGES, next, 'value', row.value);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const after = await pdriver._getKV(internals.migrationState, PURGES);
|
|
246
|
+
if (after && after.value === next) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
throw new Error('[release] could not change the purges, too much contention');
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Schedules purging the rows of a soft delete, { purge: true } takes the
|
|
256
|
+
* options of the project.
|
|
257
|
+
*/
|
|
258
|
+
const schedulePurge = (pdriver, internals, mark, t, key, purge) => {
|
|
259
|
+
const o = purge === true ? {} : checkOptions(purge);
|
|
260
|
+
const entry = { t, key, r: internals.releaseLabel || null };
|
|
261
|
+
if (Object.keys(o).length) {
|
|
262
|
+
entry.o = o;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return changePurges(pdriver, internals, value => {
|
|
266
|
+
value[mark] = entry;
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Forgets the purges done, of a soft delete or by db.purge, matching
|
|
272
|
+
* the table and the prefix of the mark.
|
|
273
|
+
*/
|
|
274
|
+
const forgetPurges = (pdriver, internals, t, prefix) =>
|
|
275
|
+
changePurges(pdriver, internals, value => {
|
|
276
|
+
const marks = Object.keys(value).filter(
|
|
277
|
+
mark => value[mark].t === t && mark.indexOf(prefix) === 0
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (!marks.length) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
marks.forEach(mark => delete value[mark]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const duePurges = async (pdriver, internals, index, releaseIndex) => {
|
|
288
|
+
const { value } = await purges(pdriver, internals);
|
|
289
|
+
|
|
290
|
+
return Object.keys(value).map(mark => {
|
|
291
|
+
const e = value[mark];
|
|
292
|
+
const age = index - (e.r === null ? 0 : releaseIndex[e.r] || 0);
|
|
293
|
+
return Object.assign({ mark, age }, e, settings(internals, e));
|
|
294
|
+
}).filter(e => e.age >= e.releases);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const releaseFile = (label, migrate) => ({
|
|
298
|
+
name: PREFIX + label,
|
|
299
|
+
path: null,
|
|
300
|
+
get: () => ({ _meta: { version: 2 }, migrate })
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The walker, without recording the release steps as migrations.
|
|
305
|
+
*/
|
|
306
|
+
const unrecorded = walker =>
|
|
307
|
+
Object.create(walker, {
|
|
308
|
+
writeMigrationRecord: { value: (file, cb) => cb() },
|
|
309
|
+
deleteMigrationRecord: { value: (file, cb) => cb() }
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Runs the steps due before the first migration of the release label.
|
|
314
|
+
*/
|
|
315
|
+
const start = async (walker, label, index, releaseIndex) => {
|
|
316
|
+
const internals = walker.internals;
|
|
317
|
+
const { renames, drops } = due(internals, index, releaseIndex);
|
|
318
|
+
const purged = (await duePurges(walker._pdriver, internals, index, releaseIndex)).filter(
|
|
319
|
+
e => e.drop === 'auto'
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (purged.length) {
|
|
323
|
+
await purge(walker, label, purged);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!renames.length && !drops.length) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
log.info(`[release] starting release ${label}`);
|
|
331
|
+
renames.forEach(item =>
|
|
332
|
+
log.info(`[release] renaming the deprecated ${describe(item)} to ${item.entry.to}`)
|
|
333
|
+
);
|
|
334
|
+
drops.forEach(item => log.info(`[release] dropping the deprecated ${describe(item)}`));
|
|
335
|
+
|
|
336
|
+
const file = releaseFile(label, async db => {
|
|
337
|
+
for (const item of renames) {
|
|
338
|
+
if (item.kind === 'tables') {
|
|
339
|
+
await db.renameTable(item.t, item.entry.to);
|
|
340
|
+
} else {
|
|
341
|
+
await db.renameColumn(item.t, item.c, item.entry.to);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const item of drops) {
|
|
346
|
+
await drop(db, item);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
|
|
351
|
+
if (internals.dryRun) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
await require('./executors/versioned/v2').up(unrecorded(walker), walker.driver, file);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Deletes the rows of the soft deletes due for good, before the first
|
|
360
|
+
* migration of the release label. This can not be reverted, the steps of
|
|
361
|
+
* the release are recorded as irreversible.
|
|
362
|
+
*/
|
|
363
|
+
const purge = async (walker, label, due) => {
|
|
364
|
+
const internals = walker.internals;
|
|
365
|
+
const pdriver = walker._pdriver;
|
|
366
|
+
const name = PREFIX + label;
|
|
367
|
+
const { Dml, FLAG } = require('./dml');
|
|
368
|
+
const file = releaseFile(label, async () => {});
|
|
369
|
+
const d = new Dml(walker, file, internals, pdriver, null, {
|
|
370
|
+
step: async () => {},
|
|
371
|
+
learned: async () => {},
|
|
372
|
+
done: async () => {}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
for (const e of due) {
|
|
376
|
+
log.info(`[release] purging the rows of "${e.t}" deleted in soft mode by ${e.mark.slice(5)}`);
|
|
377
|
+
if (!internals.dryRun) {
|
|
378
|
+
await d.untilDone(
|
|
379
|
+
e.t,
|
|
380
|
+
e.key,
|
|
381
|
+
{ sql: `${d.ddl(FLAG)} LIKE ? ESCAPE '!'`, params: [`%${e.mark.replace(/[!%_]/g, c => '!' + c)}%`] },
|
|
382
|
+
{},
|
|
383
|
+
match => walker._driver.runSql(`DELETE FROM ${d.ddl(e.t)} WHERE ${match.sql}`, match.params)
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (internals.dryRun) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await changePurges(pdriver, internals, value => {
|
|
393
|
+
due.forEach(e => delete value[e.mark]);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// the release can not be reverted anymore
|
|
397
|
+
const row = await pdriver._getKV(internals.migrationState, name);
|
|
398
|
+
const state = row ? JSON.parse(row.value) : { i: {}, c: {}, f: {}, s: [] };
|
|
399
|
+
state.s = (state.s || []).concat(
|
|
400
|
+
due.map(e => ({ t: 3, a: 'purge', c: [e.t], n: 0 }))
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
if (row) {
|
|
404
|
+
await pdriver._updateKV(internals.migrationState, name, JSON.stringify(state));
|
|
405
|
+
} else {
|
|
406
|
+
await pdriver._insertKV(internals.migrationState, name, JSON.stringify(state));
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Reverts the steps run before the first migration of the release label,
|
|
412
|
+
* once its last migration is reverted.
|
|
413
|
+
*/
|
|
414
|
+
const revert = async (walker, label) => {
|
|
415
|
+
const internals = walker.internals;
|
|
416
|
+
const name = PREFIX + label;
|
|
417
|
+
const state = await walker._pdriver._getKV(internals.migrationState, name);
|
|
418
|
+
|
|
419
|
+
if (!state || internals.dryRun) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
log.info(`[release] reverting the steps of release ${label}`);
|
|
424
|
+
internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
|
|
425
|
+
await require('./executors/versioned/v2').down(
|
|
426
|
+
unrecorded(walker),
|
|
427
|
+
walker.driver,
|
|
428
|
+
releaseFile(label, async () => {})
|
|
429
|
+
);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Refuses reverting a release whose steps can not be reverted, rows purged.
|
|
434
|
+
*/
|
|
435
|
+
const assertRevertible = async (walker, label) => {
|
|
436
|
+
const row = await walker._pdriver._getKV(
|
|
437
|
+
walker.internals.migrationState,
|
|
438
|
+
PREFIX + label
|
|
439
|
+
);
|
|
440
|
+
const steps = row ? JSON.parse(row.value).s || [] : [];
|
|
441
|
+
const irreversible = steps.filter(entry => entry.t === 3);
|
|
442
|
+
|
|
443
|
+
if (irreversible.length) {
|
|
444
|
+
const err = new Error(
|
|
445
|
+
`Release ${label} can not be reverted, it ${irreversible
|
|
446
|
+
.map(entry => `purged "${entry.c[0]}"`)
|
|
447
|
+
.join(', ')} before its first migration.`
|
|
448
|
+
);
|
|
449
|
+
err.irreversible = true;
|
|
450
|
+
throw err;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Warns about what is due for dropping with drop 'manual'.
|
|
456
|
+
*/
|
|
457
|
+
const warnDuePurges = async (pdriver, internals, index, releaseIndex) => {
|
|
458
|
+
(await duePurges(pdriver, internals, index, releaseIndex))
|
|
459
|
+
.filter(e => e.drop === 'manual')
|
|
460
|
+
.forEach(e =>
|
|
461
|
+
log.warn(
|
|
462
|
+
`[release] the rows of "${e.t}" deleted in soft mode by ` +
|
|
463
|
+
`${e.mark.slice(5)} are due for purging, deleted ${e.age} releases ` +
|
|
464
|
+
`ago. Purge them in a dml migration with db.purge(${JSON.stringify(e.t)}, ` +
|
|
465
|
+
`${JSON.stringify(e.mark.slice(5, e.mark.lastIndexOf('#')))}).`
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const warnDue = (internals, index, releaseIndex) => {
|
|
471
|
+
list(internals, index, releaseIndex).forEach(item => {
|
|
472
|
+
const { releases, drop } = settings(internals, item.entry);
|
|
473
|
+
|
|
474
|
+
if (drop === 'manual' && item.age >= releases) {
|
|
475
|
+
log.warn(
|
|
476
|
+
`[release] the deprecated ${describe(item)} is due for dropping, ` +
|
|
477
|
+
`deprecated ${item.age} releases ago. Drop it in a migration with ` +
|
|
478
|
+
`db.dropDeprecated(${JSON.stringify(item.t)}${
|
|
479
|
+
item.c ? `, ${JSON.stringify(item.c)}` : ''
|
|
480
|
+
}).`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
module.exports = {
|
|
487
|
+
PREFIX,
|
|
488
|
+
PURGES,
|
|
489
|
+
schedulePurge,
|
|
490
|
+
forgetPurges,
|
|
491
|
+
warnDuePurges,
|
|
492
|
+
releases,
|
|
493
|
+
entry,
|
|
494
|
+
list,
|
|
495
|
+
settings,
|
|
496
|
+
drop,
|
|
497
|
+
start,
|
|
498
|
+
revert,
|
|
499
|
+
assertRevertible,
|
|
500
|
+
warnDue,
|
|
501
|
+
columnsOf
|
|
502
|
+
};
|
package/lib/state.js
CHANGED
|
@@ -97,7 +97,7 @@ const createConcurrently = async create => {
|
|
|
97
97
|
* The stored schema starts as '{}', keep the structure the walker expects.
|
|
98
98
|
*/
|
|
99
99
|
const parseSchema = value =>
|
|
100
|
-
Object.assign({ i: {}, c: {}, f: {}, e: {} }, JSON.parse(value));
|
|
100
|
+
Object.assign({ i: {}, c: {}, f: {}, e: {}, d: {} }, JSON.parse(value));
|
|
101
101
|
|
|
102
102
|
const stopHeartbeat = () => {
|
|
103
103
|
if (heartbeat) {
|
package/lib/walker.js
CHANGED
|
@@ -7,6 +7,7 @@ const Promise = require('bluebird');
|
|
|
7
7
|
const File = require('./file.js');
|
|
8
8
|
const Dml = require('./dml');
|
|
9
9
|
const Jobs = require('./jobs');
|
|
10
|
+
const Release = require('./release');
|
|
10
11
|
|
|
11
12
|
const INTERFACES = {
|
|
12
13
|
migration: require('./interface/migratorInterface.js'),
|
|
@@ -39,7 +40,7 @@ const Walker = function (driver, directory, mode, intern, prefix, opts = {}) {
|
|
|
39
40
|
* f: foreignKey
|
|
40
41
|
* e: extra items for the schema (like ENUM types)
|
|
41
42
|
*/
|
|
42
|
-
this.internals.schema = { i: {}, c: {}, f: {}, e: {} };
|
|
43
|
+
this.internals.schema = { i: {}, c: {}, f: {}, e: {}, d: {} };
|
|
43
44
|
/**
|
|
44
45
|
* s: commands
|
|
45
46
|
* i: index
|
|
@@ -300,14 +301,18 @@ Walker.prototype = {
|
|
|
300
301
|
this.internals
|
|
301
302
|
)
|
|
302
303
|
])
|
|
303
|
-
.spread((allFiles, completedFiles) =>
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
.spread((allFiles, completedFiles) => {
|
|
305
|
+
this._releases(allFiles, completedFiles);
|
|
306
|
+
return dbmUtil.filterUp(allFiles, completedFiles, partialName, count);
|
|
307
|
+
})
|
|
306
308
|
.then(toRun => this._withoutJobs(toRun));
|
|
307
309
|
|
|
308
310
|
if (this.internals.check) {
|
|
309
311
|
return determine()
|
|
310
|
-
.then(toRun =>
|
|
312
|
+
.then(async toRun => {
|
|
313
|
+
await this._warnDue();
|
|
314
|
+
return this._checkResult(toRun);
|
|
315
|
+
})
|
|
311
316
|
.nodeify(callback);
|
|
312
317
|
}
|
|
313
318
|
|
|
@@ -317,8 +322,10 @@ Walker.prototype = {
|
|
|
317
322
|
log.info(this.title + 'Nothing to run');
|
|
318
323
|
}
|
|
319
324
|
|
|
320
|
-
return Promise.each(toRun, file => {
|
|
325
|
+
return Promise.each(toRun, async file => {
|
|
321
326
|
log.verbose(this.title + 'preparing to run up:', file.name);
|
|
327
|
+
await this._enterRelease(file).catch(failedIn(file));
|
|
328
|
+
|
|
322
329
|
if (Dml.isBackground(file)) {
|
|
323
330
|
return this._registerJob(file).catch(failedIn(file));
|
|
324
331
|
}
|
|
@@ -333,11 +340,94 @@ Walker.prototype = {
|
|
|
333
340
|
file
|
|
334
341
|
)
|
|
335
342
|
).catch(failedIn(file));
|
|
343
|
+
}).then(async res => {
|
|
344
|
+
await this._warnDue();
|
|
345
|
+
return res;
|
|
336
346
|
});
|
|
337
347
|
})
|
|
338
348
|
).nodeify(callback);
|
|
339
349
|
},
|
|
340
350
|
|
|
351
|
+
/**
|
|
352
|
+
* The releases of the migrations, and the latest release migrated.
|
|
353
|
+
*/
|
|
354
|
+
_releases: function (allFiles, completedFiles) {
|
|
355
|
+
const release = Release.releases(allFiles);
|
|
356
|
+
this._release = release;
|
|
357
|
+
this._applied = completedFiles.reduce(
|
|
358
|
+
(max, file) => Math.max(max, (release.map[file.name] || { index: 0 }).index),
|
|
359
|
+
0
|
|
360
|
+
);
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Runs the steps due, before the first migration of a new release.
|
|
365
|
+
*/
|
|
366
|
+
_enterRelease: async function (file) {
|
|
367
|
+
const release = this._release || { map: {}, index: {} };
|
|
368
|
+
const r = release.map[file.name] || { label: null, index: 0 };
|
|
369
|
+
|
|
370
|
+
if (r.index > this._applied) {
|
|
371
|
+
this.internals.releaseIndex = release.index;
|
|
372
|
+
await Release.start(this, r.label, r.index, release.index);
|
|
373
|
+
this._applied = r.index;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.internals.releaseLabel = r.label;
|
|
377
|
+
this.internals.releaseIndex = release.index;
|
|
378
|
+
this.internals.releaseCurrent = this._applied;
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* The label of the release whose steps are reverted after reverting file,
|
|
383
|
+
* its last migration. Refuses before reverting anything, if the steps of
|
|
384
|
+
* the release can not be reverted.
|
|
385
|
+
*/
|
|
386
|
+
_leavingRelease: async function (file) {
|
|
387
|
+
const release = this._release;
|
|
388
|
+
const r = release && release.map[file.name];
|
|
389
|
+
|
|
390
|
+
if (!r || !r.label) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const completed = await File.loadFromDatabase(
|
|
395
|
+
this.directory,
|
|
396
|
+
this.prefix,
|
|
397
|
+
this._driver,
|
|
398
|
+
this.internals
|
|
399
|
+
);
|
|
400
|
+
const remaining = completed
|
|
401
|
+
.filter(f => f.name !== file.name)
|
|
402
|
+
.reduce(
|
|
403
|
+
(max, f) => Math.max(max, (release.map[f.name] || { index: 0 }).index),
|
|
404
|
+
0
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
if (remaining >= r.index) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
await Release.assertRevertible(this, r.label);
|
|
412
|
+
return r.label;
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
_warnDue: async function () {
|
|
416
|
+
if (!this._release) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
Release.warnDue(this.internals, this._applied, this._release.index);
|
|
421
|
+
if (this._pdriver && typeof this._pdriver._getKV === 'function') {
|
|
422
|
+
await Release.warnDuePurges(
|
|
423
|
+
this._pdriver,
|
|
424
|
+
this.internals,
|
|
425
|
+
this._applied,
|
|
426
|
+
this._release.index
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
|
|
341
431
|
down: function (options, callback) {
|
|
342
432
|
const partialName = options.destination;
|
|
343
433
|
const count = options.count;
|
|
@@ -347,14 +437,28 @@ Walker.prototype = {
|
|
|
347
437
|
this.prefix,
|
|
348
438
|
this._driver,
|
|
349
439
|
this.internals
|
|
350
|
-
).then(async completedFiles =>
|
|
351
|
-
|
|
440
|
+
).then(async completedFiles => {
|
|
441
|
+
// the releases, from the migrations still there
|
|
442
|
+
const allFiles = await File.loadFromFileystem(
|
|
443
|
+
this.directory,
|
|
444
|
+
this.prefix,
|
|
445
|
+
this.internals
|
|
446
|
+
).catch(err => {
|
|
447
|
+
if (err.code === 'ENOENT') {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
throw err;
|
|
452
|
+
});
|
|
453
|
+
this._releases(allFiles, completedFiles);
|
|
454
|
+
|
|
455
|
+
return dbmUtil.filterDown(
|
|
352
456
|
// jobs of background migrations are the latest, still running
|
|
353
457
|
(await this._jobFiles()).concat(completedFiles),
|
|
354
458
|
partialName,
|
|
355
459
|
count
|
|
356
|
-
)
|
|
357
|
-
);
|
|
460
|
+
);
|
|
461
|
+
});
|
|
358
462
|
|
|
359
463
|
if (this.internals.check) {
|
|
360
464
|
return determine()
|
|
@@ -377,13 +481,13 @@ Walker.prototype = {
|
|
|
377
481
|
const _meta = file.get()._meta || {};
|
|
378
482
|
const version = _meta.version || 1;
|
|
379
483
|
this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
|
|
380
|
-
return
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
484
|
+
return this._leavingRelease(file)
|
|
485
|
+
.then(leaving =>
|
|
486
|
+
require(`./executors/versioned/v${version}`)
|
|
487
|
+
.down(this, this.driver, file)
|
|
488
|
+
.then(() => leaving && Release.revert(this, leaving))
|
|
385
489
|
)
|
|
386
|
-
|
|
490
|
+
.catch(failedIn(file));
|
|
387
491
|
});
|
|
388
492
|
})
|
|
389
493
|
).nodeify(callback);
|
package/lib/work.js
CHANGED
|
@@ -8,6 +8,7 @@ const State = require('./state');
|
|
|
8
8
|
const File = require('./file');
|
|
9
9
|
const Jobs = require('./jobs');
|
|
10
10
|
const Dml = require('./dml');
|
|
11
|
+
const Release = require('./release');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Runs the jobs of background migrations, see Jobs.
|
|
@@ -100,6 +101,8 @@ const runJob = async (walker, name, id, options, stopping, result, seen) => {
|
|
|
100
101
|
internals
|
|
101
102
|
);
|
|
102
103
|
const file = files.find(f => f.name === name);
|
|
104
|
+
// the release of the migration, a soft delete schedules its purge by it
|
|
105
|
+
internals.releaseLabel = (Release.releases(files).map[name] || {}).label || null;
|
|
103
106
|
|
|
104
107
|
if (!file) {
|
|
105
108
|
await update(job => {
|