db-migrate 1.0.0-beta.36 → 1.0.0-beta.38
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/connect.js +2 -1
- package/lib/chain.js +51 -0
- package/lib/executors/versioned/v2.js +119 -5
- package/lib/learn.js +10 -4
- package/lib/methods/v2/statedone.js +18 -0
- package/lib/methods/v2/statetravel.js +17 -3
- package/lib/methods/v2/translatestate.js +9 -2
- package/lib/state.js +105 -8
- package/package.json +1 -1
package/connect.js
CHANGED
package/lib/chain.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const log = require('db-migrate-shared').log;
|
|
2
|
+
|
|
1
3
|
const DEFAULT = [
|
|
2
4
|
'renameCollection',
|
|
3
5
|
'dropCollection',
|
|
@@ -15,6 +17,21 @@ const DEFAULT = [
|
|
|
15
17
|
'removeForeignKey'
|
|
16
18
|
];
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Human readable form of an instruction for the log, like
|
|
22
|
+
* addColumn("users", "email")
|
|
23
|
+
*/
|
|
24
|
+
const describe = (method, args) =>
|
|
25
|
+
`${method}(${args
|
|
26
|
+
.filter(
|
|
27
|
+
a =>
|
|
28
|
+
typeof a === 'string' ||
|
|
29
|
+
typeof a === 'number' ||
|
|
30
|
+
(Array.isArray(a) && a.every(x => typeof x === 'string'))
|
|
31
|
+
)
|
|
32
|
+
.map(a => JSON.stringify(a))
|
|
33
|
+
.join(', ')})`;
|
|
34
|
+
|
|
18
35
|
let rollbackCounter = 0;
|
|
19
36
|
let rollbackSignal = null;
|
|
20
37
|
let signal = null;
|
|
@@ -82,6 +99,14 @@ class Chain {
|
|
|
82
99
|
this._step = null;
|
|
83
100
|
this._interface = null;
|
|
84
101
|
|
|
102
|
+
/**
|
|
103
|
+
* op: number of the current step, the instructions of a migration
|
|
104
|
+
* recovery: progress of an interrupted previous run to resume from,
|
|
105
|
+
* see State.startMigration
|
|
106
|
+
*/
|
|
107
|
+
this.op = 0;
|
|
108
|
+
this.recovery = null;
|
|
109
|
+
|
|
85
110
|
DEFAULT.concat(Object.keys(this.context.learnable || [])).forEach(
|
|
86
111
|
method => {
|
|
87
112
|
this[method] = function (...args) {
|
|
@@ -130,7 +155,33 @@ class Chain {
|
|
|
130
155
|
async exec (m, ...args) {
|
|
131
156
|
let ret;
|
|
132
157
|
const stat = [];
|
|
158
|
+
const op = ++this.op;
|
|
159
|
+
const recovery = this.recovery;
|
|
160
|
+
this.internals.migrationOp = op;
|
|
161
|
+
|
|
162
|
+
if (recovery && op <= recovery.done) {
|
|
163
|
+
log.info(
|
|
164
|
+
`[recovery] ${this.file.name}: skipping already executed step ` +
|
|
165
|
+
`${op}/${recovery.done} ${describe(m, args)}`
|
|
166
|
+
);
|
|
167
|
+
return ret;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// the step was interrupted after its reverse operation was recorded,
|
|
171
|
+
// but before it finished on the database
|
|
172
|
+
const learned = recovery !== null && op <= recovery.learned;
|
|
173
|
+
if (learned) {
|
|
174
|
+
log.info(
|
|
175
|
+
`[recovery] ${this.file.name}: executing interrupted step ${op} ` +
|
|
176
|
+
`${describe(m, args)} without learning it again`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
133
180
|
while (this.step()) {
|
|
181
|
+
if (learned && this._step.learns) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
134
185
|
ret = await this._interface[m].apply(this._interface, args);
|
|
135
186
|
stat.push(ret);
|
|
136
187
|
|
|
@@ -8,10 +8,91 @@ const Chain = require('../../chain');
|
|
|
8
8
|
const StateTravel = require('../../methods/v2/statetravel')();
|
|
9
9
|
const StateNoStepTravel = require('../../methods/v2/statetravel')(false);
|
|
10
10
|
const Migrate = require('../../methods/v2/migrate');
|
|
11
|
+
const StateDone = require('../../methods/v2/statedone');
|
|
11
12
|
const TranslateState = require('../../methods/v2/translatestate');
|
|
12
13
|
const AddConventions = require('../../methods/v2/conventions');
|
|
13
14
|
const util = require('util');
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* How to recover from an interrupted run of a migration, set per migration
|
|
18
|
+
* by _meta.recovery:
|
|
19
|
+
*
|
|
20
|
+
* skip: skip the steps already executed and continue with the rest
|
|
21
|
+
* rollback: revert the steps already executed and run the migration again
|
|
22
|
+
*/
|
|
23
|
+
const RECOVERY_MODES = ['skip', 'rollback'];
|
|
24
|
+
const RECOVERY_DEFAULT = 'skip';
|
|
25
|
+
|
|
26
|
+
const recoveryMode = (file, interrupted) => {
|
|
27
|
+
const name = file.name;
|
|
28
|
+
const mode = file.get()._meta.recovery || RECOVERY_DEFAULT;
|
|
29
|
+
|
|
30
|
+
if (RECOVERY_MODES.indexOf(mode) === -1) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid recovery mode "${mode}" in migration "${name}", use one of ` +
|
|
33
|
+
RECOVERY_MODES.join(', ')
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (interrupted.rollback) {
|
|
38
|
+
if (mode !== 'rollback') {
|
|
39
|
+
log.warn(
|
|
40
|
+
`[recovery] ${name}: the previous run was interrupted while rolling ` +
|
|
41
|
+
'back, continuing the rollback'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'rollback';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (mode === 'skip' && interrupted.changed) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Migration "${name}" was interrupted at step ${interrupted.step} and ` +
|
|
51
|
+
'changed since, so the executed steps can not be skipped safely. ' +
|
|
52
|
+
"Set _meta.recovery to 'rollback' to revert them, or repair the " +
|
|
53
|
+
'state manually.'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return mode;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Revert the steps executed by an interrupted run. A step started but not
|
|
62
|
+
* done did not change the database, so its reverse operation is dropped.
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* Keep the reverse operations of the steps executed on the database. The
|
|
66
|
+
* failed step only changed the database, if its main statement went through
|
|
67
|
+
* before failing, signaled by the driver (e.g. a foreign key of createTable
|
|
68
|
+
* failed after the table was created).
|
|
69
|
+
*/
|
|
70
|
+
const executedSteps = (internals, failed, signaled) => {
|
|
71
|
+
const done = internals.migrationDone;
|
|
72
|
+
|
|
73
|
+
internals.modSchema.s = internals.modSchema.s.filter(
|
|
74
|
+
entry =>
|
|
75
|
+
entry.n <= done || (signaled && failed > done && entry.n === failed)
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const recoverByRollback = async (context, driver, file, interrupted) => {
|
|
80
|
+
const internals = context.internals;
|
|
81
|
+
const pdriver = context._pdriver;
|
|
82
|
+
|
|
83
|
+
await State.rollingBack(pdriver, internals);
|
|
84
|
+
internals.modSchema.s = internals.modSchema.s.filter(
|
|
85
|
+
entry => !(entry.n > interrupted.done)
|
|
86
|
+
);
|
|
87
|
+
await TranslateState(context._driver, file, driver, internals, pdriver);
|
|
88
|
+
|
|
89
|
+
internals.unlearn = false;
|
|
90
|
+
internals.rollback = false;
|
|
91
|
+
internals.rollbackContinue = false;
|
|
92
|
+
internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
|
|
93
|
+
await State.endMigration(pdriver, file, internals);
|
|
94
|
+
};
|
|
95
|
+
|
|
15
96
|
const execUnit = {
|
|
16
97
|
_extend: (context, type) => {
|
|
17
98
|
return {
|
|
@@ -38,8 +119,33 @@ const execUnit = {
|
|
|
38
119
|
chain.addChain(Learn);
|
|
39
120
|
chain.addChain(StateNoStepTravel);
|
|
40
121
|
chain.addChain(Migrate);
|
|
122
|
+
chain.addChain(StateDone);
|
|
123
|
+
|
|
124
|
+
const interrupted = await State.startMigration(
|
|
125
|
+
context._pdriver,
|
|
126
|
+
file,
|
|
127
|
+
context.internals,
|
|
128
|
+
{ recover: true }
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (interrupted) {
|
|
132
|
+
const mode = recoveryMode(file, interrupted);
|
|
133
|
+
log.warn(
|
|
134
|
+
`[recovery] ${file.name}: the previous run was interrupted at step ` +
|
|
135
|
+
`${interrupted.step}, ${interrupted.done} steps were executed, ` +
|
|
136
|
+
`recovering by ${mode}`
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (mode === 'rollback') {
|
|
140
|
+
await recoverByRollback(context, driver, file, interrupted);
|
|
141
|
+
await State.startMigration(context._pdriver, file, context.internals);
|
|
142
|
+
} else {
|
|
143
|
+
chain.recovery = interrupted;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
context.internals.migrationDone = chain.recovery ? chain.recovery.done : 0;
|
|
41
148
|
|
|
42
|
-
await State.startMigration(context._pdriver, file, context.internals);
|
|
43
149
|
// startMigration - needs secondary instance since we can not afford to
|
|
44
150
|
// loose state and the transaction start will include these for roll back
|
|
45
151
|
// we will disable them probably at all from DDL when the driver does not
|
|
@@ -51,15 +157,19 @@ const execUnit = {
|
|
|
51
157
|
dbm: context.internals.safeOptions.dbmigrate
|
|
52
158
|
});
|
|
53
159
|
} catch (err) {
|
|
54
|
-
context.internals.rollback = true;
|
|
55
|
-
|
|
56
160
|
// transfer last state
|
|
57
161
|
chain.transferInt();
|
|
162
|
+
executedSteps(
|
|
163
|
+
context.internals,
|
|
164
|
+
chain.op,
|
|
165
|
+
chain.udriver._counter.previousSignal() === true
|
|
166
|
+
);
|
|
58
167
|
|
|
59
168
|
log.error(
|
|
60
169
|
'An error occured. No alternative failure strategy defined. Rolling back!',
|
|
61
170
|
err
|
|
62
171
|
);
|
|
172
|
+
await State.rollingBack(context._pdriver, context.internals);
|
|
63
173
|
await execUnit.down(context, driver, file, { abort: true });
|
|
64
174
|
throw err;
|
|
65
175
|
}
|
|
@@ -77,7 +187,9 @@ const execUnit = {
|
|
|
77
187
|
chain.addChain(Learn);
|
|
78
188
|
chain.addChain(StateTravel);
|
|
79
189
|
|
|
80
|
-
await State.startMigration(context._pdriver, file, context.internals
|
|
190
|
+
await State.startMigration(context._pdriver, file, context.internals, {
|
|
191
|
+
op: 'fix'
|
|
192
|
+
});
|
|
81
193
|
// startMigration - needs secondary instance since we can not afford to
|
|
82
194
|
// loose state and the transaction start will include these for roll back
|
|
83
195
|
// we will disable them probably at all from DDL when the driver does not
|
|
@@ -110,7 +222,9 @@ const execUnit = {
|
|
|
110
222
|
// if we get the abort signal this means we are in a rollback routine
|
|
111
223
|
// which means in turn we want to avoid reloading the state
|
|
112
224
|
if (abort !== true) {
|
|
113
|
-
await State.startMigration(context._pdriver, file, context.internals
|
|
225
|
+
await State.startMigration(context._pdriver, file, context.internals, {
|
|
226
|
+
op: 'down'
|
|
227
|
+
});
|
|
114
228
|
}
|
|
115
229
|
// start migration, see up comments
|
|
116
230
|
await TranslateState(context._driver, file, driver, context.internals, context._pdriver);
|
package/lib/learn.js
CHANGED
|
@@ -175,7 +175,8 @@ class STD {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
// keep the columns removed before from the same table
|
|
179
|
+
if (!this.modS[t]) this.modS[t] = {};
|
|
179
180
|
|
|
180
181
|
if (columns[c].notNull === true && !this.unlearn) {
|
|
181
182
|
switch (o.columnStrategy) {
|
|
@@ -260,7 +261,7 @@ class STD {
|
|
|
260
261
|
|
|
261
262
|
addIndex (t, i, c, u) {
|
|
262
263
|
if (Array.isArray(c)) {
|
|
263
|
-
c.forEach(x => this.checkColumn(t, typeof x
|
|
264
|
+
c.forEach(x => this.checkColumn(t, typeof x === 'object' ? x.name : x));
|
|
264
265
|
} else {
|
|
265
266
|
this.checkColumn(t, c);
|
|
266
267
|
}
|
|
@@ -300,7 +301,7 @@ class STD {
|
|
|
300
301
|
|
|
301
302
|
alter.i[t] = {};
|
|
302
303
|
alter.i[t][i] = this.indizies[t][i];
|
|
303
|
-
this.modI[t] = {};
|
|
304
|
+
if (!this.modI[t]) this.modI[t] = {};
|
|
304
305
|
this.modI[t][i] = this.indizies[t][i];
|
|
305
306
|
delete this.indizies[t][i];
|
|
306
307
|
|
|
@@ -345,10 +346,12 @@ class STD {
|
|
|
345
346
|
|
|
346
347
|
alter.f[t] = {};
|
|
347
348
|
alter.f[t][k] = this.foreign[t][k];
|
|
348
|
-
this.modF[t] = {};
|
|
349
|
+
if (!this.modF[t]) this.modF[t] = {};
|
|
349
350
|
this.modF[t][k] = this.foreign[t][k];
|
|
350
351
|
delete this.foreign[t][k];
|
|
351
352
|
|
|
353
|
+
this.modC.push({ t: 1, a: 'addForeignKey', c: [t, k] });
|
|
354
|
+
|
|
352
355
|
return Promise.resolve(alter);
|
|
353
356
|
}
|
|
354
357
|
|
|
@@ -371,6 +374,9 @@ const noLearnError = prop => {
|
|
|
371
374
|
};
|
|
372
375
|
|
|
373
376
|
module.exports = {
|
|
377
|
+
// learns the schema and records the reverse operations
|
|
378
|
+
learns: true,
|
|
379
|
+
|
|
374
380
|
getInterface: (context, file, driver, internals) => {
|
|
375
381
|
if (context.learnable) {
|
|
376
382
|
const _std = new STD(internals, context);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const Shadow = require('../../driver/shadow');
|
|
2
|
+
const State = require('../../state');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Records a step as done, after it was executed on the database.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
getInterface: (context, file, driver, internals, pdriver) => {
|
|
9
|
+
const done = () =>
|
|
10
|
+
function () {
|
|
11
|
+
// in memory first, a failing write must not hide the executed step
|
|
12
|
+
internals.migrationDone = internals.migrationOp;
|
|
13
|
+
return State.done(pdriver, internals.migrationOp, internals);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return Shadow.overshadow(driver, {}, done);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -17,24 +17,38 @@ const DEFAULT = {
|
|
|
17
17
|
removeForeignKey: 'addForeignKey'
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Persists the state around learning a step. Before learning (step) it
|
|
22
|
+
* records the step as started, afterwards it tags the recorded reverse
|
|
23
|
+
* operations with their step and records the step as learned.
|
|
24
|
+
*/
|
|
20
25
|
class StateTravel {
|
|
21
26
|
constructor (internals, file, driver, pdriver, step = true) {
|
|
22
27
|
this.file = file;
|
|
23
28
|
this.internals = internals;
|
|
24
29
|
this.driver = driver;
|
|
25
30
|
this.pdriver = pdriver;
|
|
26
|
-
this._counter = 0;
|
|
27
31
|
this._default = async function _default () {
|
|
32
|
+
const op = this.internals.migrationOp;
|
|
33
|
+
|
|
28
34
|
if (step) {
|
|
29
|
-
await State.step(this.pdriver,
|
|
35
|
+
await State.step(this.pdriver, op, this.internals);
|
|
36
|
+
} else {
|
|
37
|
+
this.internals.modSchema.s.forEach(entry => {
|
|
38
|
+
if (entry.n === undefined) entry.n = op;
|
|
39
|
+
});
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
await State.update(
|
|
33
43
|
this.pdriver,
|
|
34
44
|
this.file,
|
|
35
45
|
this.internals.modSchema,
|
|
36
46
|
this.internals
|
|
37
47
|
);
|
|
48
|
+
|
|
49
|
+
if (!step) {
|
|
50
|
+
await State.learned(this.pdriver, op, this.internals);
|
|
51
|
+
}
|
|
38
52
|
};
|
|
39
53
|
}
|
|
40
54
|
}
|
|
@@ -46,6 +46,11 @@ const methods = {
|
|
|
46
46
|
return driver.changeColumn(t, c, internals.modSchema.c[t][c]);
|
|
47
47
|
},
|
|
48
48
|
|
|
49
|
+
addForeignKey: async (driver, [t, k], internals) => {
|
|
50
|
+
const foreign = internals.modSchema.f[t][k];
|
|
51
|
+
return driver.addForeignKey(t, foreign.rt, k, foreign.m, foreign.r);
|
|
52
|
+
},
|
|
53
|
+
|
|
49
54
|
addIndex: async (driver, [t, i], internals) => {
|
|
50
55
|
if (!internals.modSchema.i[t][i]) {
|
|
51
56
|
log.warn(
|
|
@@ -101,7 +106,7 @@ async function processEntry (
|
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
internals.modSchema.s.
|
|
109
|
+
internals.modSchema.s.pop();
|
|
105
110
|
await State.update(pdriver, file, internals.modSchema, internals);
|
|
106
111
|
}
|
|
107
112
|
|
|
@@ -113,7 +118,9 @@ module.exports = async (context, file, driver, internals, pdriver) => {
|
|
|
113
118
|
|
|
114
119
|
chain.addChain(Learn);
|
|
115
120
|
chain.addChain(Migrate);
|
|
116
|
-
|
|
121
|
+
// the stored records keep their order, so an interrupted rollback can
|
|
122
|
+
// be continued
|
|
123
|
+
await Promise.resolve(mod.s.slice().reverse()).each(args =>
|
|
117
124
|
processEntry(context, file, chain, internals, pdriver, args)
|
|
118
125
|
);
|
|
119
126
|
|
package/lib/state.js
CHANGED
|
@@ -29,6 +29,21 @@ let queue = Promise.resolve();
|
|
|
29
29
|
|
|
30
30
|
const nonce = () => crypto.randomBytes(8).toString('hex');
|
|
31
31
|
|
|
32
|
+
const hashFile = async file => {
|
|
33
|
+
if (!file.path) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
return crypto
|
|
39
|
+
.createHash('sha256')
|
|
40
|
+
.update(await fs.readFile(file.path))
|
|
41
|
+
.digest('hex');
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
32
47
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
33
48
|
|
|
34
49
|
const lockOptions = internals => {
|
|
@@ -194,7 +209,9 @@ module.exports = {
|
|
|
194
209
|
log.warn(
|
|
195
210
|
`[state] taking over a stale migration lock from ${s.date}` +
|
|
196
211
|
(s.fin === 0
|
|
197
|
-
? `, the previous run was interrupted during
|
|
212
|
+
? `, the previous run was interrupted during ${
|
|
213
|
+
s.f ? `migration "${s.f}"` : 'a migration'
|
|
214
|
+
} at step ${s.step}`
|
|
198
215
|
: '')
|
|
199
216
|
);
|
|
200
217
|
}
|
|
@@ -334,31 +351,80 @@ module.exports = {
|
|
|
334
351
|
}
|
|
335
352
|
},
|
|
336
353
|
|
|
337
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Start a migration in the state. With recover, an interrupted previous
|
|
356
|
+
* run of the same migration is not reset but returned, so the caller can
|
|
357
|
+
* decide how to recover:
|
|
358
|
+
*
|
|
359
|
+
* step: the last step started
|
|
360
|
+
* learned: the last step whose reverse operation was recorded
|
|
361
|
+
* done: the last step executed on the database
|
|
362
|
+
* rollback: the run was interrupted while rolling back
|
|
363
|
+
* changed: the migration file changed since
|
|
364
|
+
*/
|
|
365
|
+
startMigration: async function (
|
|
366
|
+
driver,
|
|
367
|
+
file,
|
|
368
|
+
internals,
|
|
369
|
+
{ op = 'up', recover = false } = {}
|
|
370
|
+
) {
|
|
338
371
|
const mig = await driver._getKV(internals.migrationState, file.name);
|
|
339
372
|
|
|
340
373
|
if (mig && mig.value !== '{}') {
|
|
341
374
|
internals.modSchema = JSON.parse(mig.value);
|
|
375
|
+
// records of older runs, they belong to no step of this run
|
|
376
|
+
(internals.modSchema.s || []).forEach(entry => {
|
|
377
|
+
if (entry.n === undefined) entry.n = 0;
|
|
378
|
+
});
|
|
342
379
|
}
|
|
343
380
|
|
|
344
381
|
if (internals.dryRun) {
|
|
345
|
-
return
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const h = await hashFile(file);
|
|
386
|
+
const row = await read(driver, internals);
|
|
387
|
+
const prev = row ? JSON.parse(row.value).s : null;
|
|
388
|
+
|
|
389
|
+
if (prev && prev.fin === 0 && prev.f) {
|
|
390
|
+
if (recover && prev.f === file.name && prev.o === 'up' && op === 'up') {
|
|
391
|
+
return {
|
|
392
|
+
step: prev.step || 0,
|
|
393
|
+
learned: prev.learned || 0,
|
|
394
|
+
done: prev.done || 0,
|
|
395
|
+
rollback: prev.rb === 1,
|
|
396
|
+
changed: !!prev.h && !!h && prev.h !== h
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
log.warn(
|
|
401
|
+
`[state] ignoring the interrupted ${prev.o} of migration ` +
|
|
402
|
+
`"${prev.f}" at step ${prev.step}`
|
|
403
|
+
);
|
|
346
404
|
}
|
|
347
405
|
|
|
348
406
|
await module.exports.progress(driver, internals, s => {
|
|
349
|
-
s
|
|
350
|
-
|
|
407
|
+
Object.assign(s, {
|
|
408
|
+
step: 0,
|
|
409
|
+
fin: 0,
|
|
410
|
+
f: file.name,
|
|
411
|
+
o: op,
|
|
412
|
+
learned: 0,
|
|
413
|
+
done: 0,
|
|
414
|
+
rb: 0,
|
|
415
|
+
h
|
|
416
|
+
});
|
|
351
417
|
});
|
|
352
418
|
|
|
353
419
|
if (!mig) {
|
|
354
|
-
|
|
420
|
+
await driver._insertKV(
|
|
355
421
|
internals.migrationState,
|
|
356
422
|
file.name,
|
|
357
423
|
JSON.stringify({})
|
|
358
424
|
);
|
|
359
|
-
} else {
|
|
360
|
-
return Promise.resolve();
|
|
361
425
|
}
|
|
426
|
+
|
|
427
|
+
return null;
|
|
362
428
|
},
|
|
363
429
|
|
|
364
430
|
/**
|
|
@@ -412,6 +478,36 @@ module.exports = {
|
|
|
412
478
|
return driver._getKV(internals.migrationState, file.name);
|
|
413
479
|
},
|
|
414
480
|
|
|
481
|
+
learned: async function (driver, step, internals) {
|
|
482
|
+
if (internals.dryRun) {
|
|
483
|
+
return Promise.resolve();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return module.exports.progress(driver, internals, s => {
|
|
487
|
+
s.learned = step;
|
|
488
|
+
});
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
done: async function (driver, step, internals) {
|
|
492
|
+
if (internals.dryRun) {
|
|
493
|
+
return Promise.resolve();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return module.exports.progress(driver, internals, s => {
|
|
497
|
+
s.done = step;
|
|
498
|
+
});
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
rollingBack: async function (driver, internals) {
|
|
502
|
+
if (internals.dryRun) {
|
|
503
|
+
return Promise.resolve();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return module.exports.progress(driver, internals, s => {
|
|
507
|
+
s.rb = 1;
|
|
508
|
+
});
|
|
509
|
+
},
|
|
510
|
+
|
|
415
511
|
step: async function (driver, step, internals) {
|
|
416
512
|
log.verbose(`[state] proceeded to step ${step}`);
|
|
417
513
|
if (internals.dryRun) {
|
|
@@ -435,6 +531,7 @@ module.exports = {
|
|
|
435
531
|
// the lock itself stays until the walker releases it
|
|
436
532
|
return module.exports.progress(driver, internals, s => {
|
|
437
533
|
s.fin = 1;
|
|
534
|
+
s.rb = 0;
|
|
438
535
|
});
|
|
439
536
|
}
|
|
440
537
|
};
|