db-migrate 1.0.0 → 1.2.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/README.md +14 -0
- package/api.js +12 -2
- package/connect.js +60 -180
- package/lib/chain.js +81 -0
- package/lib/commands/check.js +6 -1
- package/lib/commands/create-migration.js +2 -6
- package/lib/commands/db.js +42 -45
- package/lib/commands/down.js +6 -1
- package/lib/commands/fix.js +6 -1
- package/lib/commands/fn/plugin.js +1 -1
- package/lib/commands/generated.js +1 -0
- package/lib/commands/helper/scopes.js +60 -0
- package/lib/commands/set-default-argv.js +22 -3
- package/lib/commands/sync.js +6 -1
- package/lib/commands/up.js +6 -1
- package/lib/config.js +3 -0
- package/lib/executors/versioned/v1.js +27 -6
- package/lib/executors/versioned/v2.js +19 -1
- package/lib/file.js +5 -0
- package/lib/learn.js +79 -12
- package/lib/methods/v2/conventions.js +3 -0
- package/lib/methods/v2/migrate.js +3 -0
- package/lib/methods/v2/translatestate.js +24 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -20,6 +20,12 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
|
|
|
20
20
|
|
|
21
21
|
# Usage
|
|
22
22
|
|
|
23
|
+
## Upgrading from 0.11
|
|
24
|
+
|
|
25
|
+
db-migrate 0.11 and older are end of life. See the
|
|
26
|
+
[changelog of 1.0.0](CHANGELOG.md) for what changed, v1 migrations keep
|
|
27
|
+
working unchanged.
|
|
28
|
+
|
|
23
29
|
## Requirements
|
|
24
30
|
|
|
25
31
|
Officially supported is Node.js 24 and newer. Older versions may work, but
|
|
@@ -61,6 +67,14 @@ db-migrate to use the `tunnel` setting of your database config:
|
|
|
61
67
|
|
|
62
68
|
$ npm install db-migrate-plugin-tunnel-ssh
|
|
63
69
|
|
|
70
|
+
## db-migrate for meta
|
|
71
|
+
|
|
72
|
+
[meta-db-migrate](https://github.com/db-migrate/meta-db-migrate) is db-migrate
|
|
73
|
+
rewritten in meta: a program ships with its migrations compiled in, as one
|
|
74
|
+
binary. It uses the same migrations table and the same state as db-migrate,
|
|
75
|
+
v2 migrations included, so a database migrated by one of them can be carried
|
|
76
|
+
on by the other.
|
|
77
|
+
|
|
64
78
|
## Resources and usage instructions
|
|
65
79
|
|
|
66
80
|
Please follow the link below, for usage instructions examples and the full
|
package/api.js
CHANGED
|
@@ -154,6 +154,14 @@ dbmigrate.prototype = {
|
|
|
154
154
|
* @return boolean
|
|
155
155
|
*/
|
|
156
156
|
addConfiguration: function (description, args, type) {
|
|
157
|
+
// the command line options are parsed already when the API is available
|
|
158
|
+
if (typeof this.internals.argv.describe !== 'function') {
|
|
159
|
+
log.warn(
|
|
160
|
+
'addConfiguration has no effect, the options are parsed already.'
|
|
161
|
+
);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
157
165
|
var name = args.shift();
|
|
158
166
|
this.internals.argv.describe(name, description);
|
|
159
167
|
|
|
@@ -368,7 +376,8 @@ dbmigrate.prototype = {
|
|
|
368
376
|
*/
|
|
369
377
|
createDatabase: function (dbname, callback) {
|
|
370
378
|
var executeDB = load('db');
|
|
371
|
-
|
|
379
|
+
// in module mode there are no positional arguments yet
|
|
380
|
+
this.internals.argv._ = (this.internals.argv._ || []).concat(dbname);
|
|
372
381
|
this.internals.mode = 'create';
|
|
373
382
|
return Promise.resolve(executeDB(this.internals, this.config)).asCallback(
|
|
374
383
|
callback
|
|
@@ -380,7 +389,8 @@ dbmigrate.prototype = {
|
|
|
380
389
|
*/
|
|
381
390
|
dropDatabase: function (dbname, callback) {
|
|
382
391
|
var executeDB = load('db');
|
|
383
|
-
|
|
392
|
+
// in module mode there are no positional arguments yet
|
|
393
|
+
this.internals.argv._ = (this.internals.argv._ || []).concat(dbname);
|
|
384
394
|
this.internals.mode = 'drop';
|
|
385
395
|
return Promise.resolve(executeDB(this.internals, this.config)).asCallback(
|
|
386
396
|
callback
|
package/connect.js
CHANGED
|
@@ -2,15 +2,37 @@
|
|
|
2
2
|
* This file is going to disappear.
|
|
3
3
|
* Only still here for backwards compatibility.
|
|
4
4
|
* */
|
|
5
|
-
var recursive = require('final-fs').readdirRecursive;
|
|
6
5
|
var fs = require('fs');
|
|
7
6
|
var driver = require('./lib/driver');
|
|
8
7
|
var path = require('path');
|
|
9
8
|
var log = require('db-migrate-shared').log;
|
|
9
|
+
var resolveEnv = require('./lib/config').resolveEnv;
|
|
10
10
|
const Promise = require('bluebird');
|
|
11
11
|
|
|
12
12
|
Promise.promisifyAll(driver);
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The config.json of a scope, with { "ENV": ... } resolved, or null.
|
|
16
|
+
*/
|
|
17
|
+
function scopeConfig (dirPath, scope) {
|
|
18
|
+
const file = path.resolve(dirPath, scope, 'config.json');
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(file)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
log.info('loaded extra config for migration subfolder: "' + scope + '/config.json"');
|
|
25
|
+
return resolveEnv(JSON.parse(fs.readFileSync(file, 'utf8')));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A scope config with more than database or schema connects on its own,
|
|
30
|
+
* inheriting the settings of the environment it does not set itself.
|
|
31
|
+
*/
|
|
32
|
+
const SWITCH_ONLY = ['database', 'schema'];
|
|
33
|
+
const ownConnection = conf =>
|
|
34
|
+
Object.keys(conf).some(key => SWITCH_ONLY.indexOf(key) === -1);
|
|
35
|
+
|
|
14
36
|
exports.connect = async function (config, PassedClass) {
|
|
15
37
|
var internals = {};
|
|
16
38
|
var prefix = 'migration';
|
|
@@ -20,123 +42,47 @@ exports.connect = async function (config, PassedClass) {
|
|
|
20
42
|
config = config.config;
|
|
21
43
|
}
|
|
22
44
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (err) {
|
|
28
|
-
callback(err);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const realClose = db.close;
|
|
33
|
-
// close both lines with one disconnect action
|
|
34
|
-
db.close = function (cb) {
|
|
35
|
-
db2.close(function () {});
|
|
36
|
-
db.close = realClose;
|
|
37
|
-
db.close(cb);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
var dirPath = path.resolve(
|
|
41
|
-
internals.argv['migrations-dir'] || 'migrations'
|
|
42
|
-
);
|
|
43
|
-
if (internals.migrationMode) {
|
|
44
|
-
if (internals.migrationMode !== 'all') {
|
|
45
|
-
var switched = false;
|
|
46
|
-
var newConf;
|
|
45
|
+
const dirPath = path.resolve(internals.argv['migrations-dir'] || 'migrations');
|
|
46
|
+
const scope = internals.migrationMode;
|
|
47
|
+
const conf = scope ? scopeConfig(dirPath, scope) : null;
|
|
48
|
+
let switchTo = null;
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'loaded extra config for migration subfolder: "' +
|
|
55
|
-
internals.migrationMode +
|
|
56
|
-
'/config.json"'
|
|
57
|
-
);
|
|
58
|
-
switched = true;
|
|
59
|
-
} catch (e) {}
|
|
60
|
-
|
|
61
|
-
if (switched) {
|
|
62
|
-
db.switchDatabase(newConf, function (err) {
|
|
63
|
-
if (err) {
|
|
64
|
-
return callback(err);
|
|
65
|
-
}
|
|
66
|
-
internals.locTitle = internals.migrationMode;
|
|
67
|
-
callback(
|
|
68
|
-
null,
|
|
69
|
-
new PassedClass(
|
|
70
|
-
db,
|
|
71
|
-
dirPath,
|
|
72
|
-
internals.mode !== 'static',
|
|
73
|
-
internals,
|
|
74
|
-
prefix,
|
|
75
|
-
{ db2 }
|
|
76
|
-
)
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
} else {
|
|
80
|
-
internals.locTitle = internals.migrationMode;
|
|
81
|
-
callback(
|
|
82
|
-
null,
|
|
83
|
-
new PassedClass(
|
|
84
|
-
db,
|
|
85
|
-
dirPath,
|
|
86
|
-
internals.mode !== 'static',
|
|
87
|
-
internals,
|
|
88
|
-
prefix,
|
|
89
|
-
{ db2 }
|
|
90
|
-
)
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
recursive(
|
|
95
|
-
dirPath,
|
|
96
|
-
false,
|
|
97
|
-
internals.argv['migrations-dir'] || 'migrations'
|
|
98
|
-
).then(function (files) {
|
|
99
|
-
var oldClose = db.close;
|
|
100
|
-
|
|
101
|
-
files = files.filter(function (file) {
|
|
102
|
-
return file !== 'migrations' && fs.statSync(file).isDirectory();
|
|
103
|
-
});
|
|
50
|
+
if (conf && ownConnection(conf)) {
|
|
51
|
+
// a database of its own, with its own migrations and state tables
|
|
52
|
+
config = Object.assign({}, config, conf);
|
|
53
|
+
} else if (conf && (conf.database || conf.schema)) {
|
|
54
|
+
switchTo = conf;
|
|
55
|
+
}
|
|
104
56
|
|
|
105
|
-
|
|
57
|
+
const db2 = await driver.connectAsync(config, internals);
|
|
58
|
+
const db = await driver.connectAsync(config, internals);
|
|
59
|
+
|
|
60
|
+
const realClose = db.close;
|
|
61
|
+
// close both lines with one disconnect action
|
|
62
|
+
db.close = function (cb) {
|
|
63
|
+
db2.close(function () {});
|
|
64
|
+
db.close = realClose;
|
|
65
|
+
db.close(cb);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (switchTo) {
|
|
69
|
+
// both lines, so the state is kept in the database of the scope as well
|
|
70
|
+
await Promise.promisify(db.switchDatabase, { context: db })(switchTo);
|
|
71
|
+
await Promise.promisify(db2.switchDatabase, { context: db2 })(switchTo);
|
|
72
|
+
}
|
|
106
73
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
callback,
|
|
111
|
-
config,
|
|
112
|
-
internals,
|
|
113
|
-
PassedClass,
|
|
114
|
-
db,
|
|
115
|
-
oldClose,
|
|
116
|
-
prefix,
|
|
117
|
-
db2,
|
|
118
|
-
cb
|
|
119
|
-
);
|
|
120
|
-
};
|
|
74
|
+
if (scope) {
|
|
75
|
+
internals.locTitle = scope;
|
|
76
|
+
}
|
|
121
77
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
dirPath,
|
|
131
|
-
internals.mode !== 'static',
|
|
132
|
-
internals,
|
|
133
|
-
prefix,
|
|
134
|
-
{ db2 }
|
|
135
|
-
)
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
});
|
|
78
|
+
return new PassedClass(
|
|
79
|
+
db,
|
|
80
|
+
dirPath,
|
|
81
|
+
internals.mode !== 'static',
|
|
82
|
+
internals,
|
|
83
|
+
prefix,
|
|
84
|
+
{ db2 }
|
|
85
|
+
);
|
|
140
86
|
};
|
|
141
87
|
|
|
142
88
|
exports.driver = function (config, callback) {
|
|
@@ -148,69 +94,3 @@ exports.driver = function (config, callback) {
|
|
|
148
94
|
|
|
149
95
|
driver.connect(config, internals, callback);
|
|
150
96
|
};
|
|
151
|
-
|
|
152
|
-
function migrationFiles (
|
|
153
|
-
files,
|
|
154
|
-
callback,
|
|
155
|
-
config,
|
|
156
|
-
internals,
|
|
157
|
-
PassedClass,
|
|
158
|
-
db,
|
|
159
|
-
close,
|
|
160
|
-
prefix,
|
|
161
|
-
db2,
|
|
162
|
-
cb
|
|
163
|
-
) {
|
|
164
|
-
var file;
|
|
165
|
-
var switched = false;
|
|
166
|
-
var newConf;
|
|
167
|
-
|
|
168
|
-
if (files.length === 1) {
|
|
169
|
-
db.close = close;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
file = files.pop();
|
|
173
|
-
log.info('Enter scope "' + (file !== '' ? file : '/') + '"');
|
|
174
|
-
|
|
175
|
-
if (file !== '') {
|
|
176
|
-
try {
|
|
177
|
-
fs.statSync(path.resolve(file + '/config.json'));
|
|
178
|
-
newConf = require(path.resolve(file + '/config.json'));
|
|
179
|
-
log.info(
|
|
180
|
-
'loaded extra config for migration subfolder: "' +
|
|
181
|
-
file +
|
|
182
|
-
'/config.json"'
|
|
183
|
-
);
|
|
184
|
-
switched = true;
|
|
185
|
-
} catch (e) {}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
db.switchDatabase(switched ? newConf : config.database, function () {
|
|
189
|
-
internals.matching = file.substr(
|
|
190
|
-
file.indexOf(internals.argv['migrations-dir'] || 'migrations') +
|
|
191
|
-
(internals.argv['migrations-dir'] || 'migrations').length +
|
|
192
|
-
1
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
if (internals.matching.length === 0) {
|
|
196
|
-
internals.matching = '';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
internals.locTitle = internals.matching;
|
|
200
|
-
callback(
|
|
201
|
-
null,
|
|
202
|
-
new PassedClass(
|
|
203
|
-
db,
|
|
204
|
-
internals.argv['migrations-dir'],
|
|
205
|
-
internals.mode !== 'static',
|
|
206
|
-
internals,
|
|
207
|
-
prefix,
|
|
208
|
-
{ db2 }
|
|
209
|
-
)
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
if (typeof cb === 'function') {
|
|
213
|
-
cb();
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
package/lib/chain.js
CHANGED
|
@@ -32,6 +32,43 @@ const describe = (method, args) =>
|
|
|
32
32
|
.map(a => JSON.stringify(a))
|
|
33
33
|
.join(', ')})`;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Instructions declaring an object, which can adopt objects created outside
|
|
37
|
+
* of v2 migrations into the schema, see adopt.
|
|
38
|
+
*/
|
|
39
|
+
const ADOPTABLE = /^(create|add)/;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Adopting is for objects unknown to the schema only.
|
|
43
|
+
*/
|
|
44
|
+
const assertUnknown = (m, args, schema) => {
|
|
45
|
+
const [t, a, b] = args;
|
|
46
|
+
let known = false;
|
|
47
|
+
|
|
48
|
+
switch (m) {
|
|
49
|
+
case 'createTable':
|
|
50
|
+
case 'createCollection':
|
|
51
|
+
known = !!schema.c[t];
|
|
52
|
+
break;
|
|
53
|
+
case 'addColumn':
|
|
54
|
+
known = !!(schema.c[t] && (schema.c[t][a] || schema.c[t].columns?.[a]));
|
|
55
|
+
break;
|
|
56
|
+
case 'addIndex':
|
|
57
|
+
known = !!(schema.i[t] && schema.i[t][a]);
|
|
58
|
+
break;
|
|
59
|
+
case 'addForeignKey':
|
|
60
|
+
known = !!(schema.f[t] && schema.f[t][b]);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (known) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`${describe(m, args)} is known to the schema already, adopt is for ` +
|
|
67
|
+
'objects created outside of v2 migrations only.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
35
72
|
let rollbackCounter = 0;
|
|
36
73
|
let rollbackSignal = null;
|
|
37
74
|
let signal = null;
|
|
@@ -107,6 +144,13 @@ class Chain {
|
|
|
107
144
|
this.op = 0;
|
|
108
145
|
this.recovery = null;
|
|
109
146
|
|
|
147
|
+
/**
|
|
148
|
+
* adopt: declares objects created outside of v2 migrations, e.g. by v1
|
|
149
|
+
* migrations, so v2 migrations can work with them. Only learns them,
|
|
150
|
+
* nothing is executed, and reverting it only forgets them again.
|
|
151
|
+
*/
|
|
152
|
+
this.adopt = {};
|
|
153
|
+
|
|
110
154
|
DEFAULT.concat(Object.keys(this.context.learnable || [])).forEach(
|
|
111
155
|
method => {
|
|
112
156
|
this[method] = function (...args) {
|
|
@@ -116,6 +160,15 @@ class Chain {
|
|
|
116
160
|
|
|
117
161
|
return this.exec.apply(this, [method].concat(args));
|
|
118
162
|
};
|
|
163
|
+
|
|
164
|
+
if (ADOPTABLE.test(method)) {
|
|
165
|
+
this.adopt[method] = async (...args) => {
|
|
166
|
+
this.transferInt();
|
|
167
|
+
assertUnknown(method, args, this.internals.schema);
|
|
168
|
+
|
|
169
|
+
return this.run(method, args, 'adopt');
|
|
170
|
+
};
|
|
171
|
+
}
|
|
119
172
|
}
|
|
120
173
|
);
|
|
121
174
|
}
|
|
@@ -153,6 +206,21 @@ class Chain {
|
|
|
153
206
|
}
|
|
154
207
|
|
|
155
208
|
async exec (m, ...args) {
|
|
209
|
+
return this.run(m, args, null);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Learn an instruction only, without executing it, to revert an adopt.
|
|
214
|
+
*/
|
|
215
|
+
async learnOnly (m, ...args) {
|
|
216
|
+
return this.run(m, args, 'learn');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* mode: null to execute the instruction, 'adopt' or 'learn' to only learn
|
|
221
|
+
* it, skipping the links executing it on the database
|
|
222
|
+
*/
|
|
223
|
+
async run (m, args, mode) {
|
|
156
224
|
let ret;
|
|
157
225
|
const stat = [];
|
|
158
226
|
const op = ++this.op;
|
|
@@ -183,9 +251,22 @@ class Chain {
|
|
|
183
251
|
continue;
|
|
184
252
|
}
|
|
185
253
|
|
|
254
|
+
if (mode && this._step.executes) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const mod = this.internals.modSchema;
|
|
259
|
+
const recorded = mod ? mod.s.length : 0;
|
|
186
260
|
ret = await this._interface[m].apply(this._interface, args);
|
|
187
261
|
stat.push(ret);
|
|
188
262
|
|
|
263
|
+
// an adopted object is only forgotten again when reverting
|
|
264
|
+
if (mode === 'adopt' && this._step.learns && mod) {
|
|
265
|
+
mod.s.slice(recorded).forEach(entry => {
|
|
266
|
+
entry.t = 2;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
189
270
|
if (this._step.hasStateUsage) {
|
|
190
271
|
this._step.useState(ret);
|
|
191
272
|
}
|
package/lib/commands/check.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var path = require('path');
|
|
2
2
|
var log = require('db-migrate-shared').log;
|
|
3
3
|
var migrationHook = require('./helper/migration-hook.js');
|
|
4
|
+
var eachScope = require('./helper/scopes.js');
|
|
4
5
|
|
|
5
6
|
async function prepare (internals, config) {
|
|
6
7
|
try {
|
|
@@ -40,7 +41,7 @@ async function prepare (internals, config) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
async function execute (internals, config) {
|
|
44
45
|
const migrator = await prepare(internals, config);
|
|
45
46
|
|
|
46
47
|
try {
|
|
@@ -49,4 +50,8 @@ module.exports = async function (internals, config) {
|
|
|
49
50
|
} catch (err) {
|
|
50
51
|
return internals.onComplete(migrator, internals, err);
|
|
51
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = function (internals, config) {
|
|
56
|
+
return eachScope(internals, () => execute(internals, config));
|
|
52
57
|
};
|
|
@@ -199,12 +199,8 @@ function shouldCreateV2File (internals, config) {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
async function createSqlFiles (internals, config) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (internals.migrationMode && internals.migrationMode !== 'all') {
|
|
205
|
-
migrationsDir =
|
|
206
|
-
internals.argv['migrations-dir'] + '/' + internals.migrationMode;
|
|
207
|
-
}
|
|
202
|
+
// the directory of the migration, its scope included already
|
|
203
|
+
const migrationsDir = internals.argv['migrations-dir'];
|
|
208
204
|
|
|
209
205
|
const sqlDir = migrationsDir + '/sqls';
|
|
210
206
|
try {
|
package/lib/commands/db.js
CHANGED
|
@@ -1,63 +1,60 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var log = require('db-migrate-shared').log;
|
|
4
|
-
var
|
|
4
|
+
var Promise = require('bluebird');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creates or drops a database, resolving once it is done.
|
|
8
|
+
*/
|
|
6
9
|
function executeDB (internals, config, callback) {
|
|
7
10
|
var index = require('../../connect');
|
|
8
11
|
|
|
9
|
-
if (internals.argv._.length
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return;
|
|
12
|
+
if (internals.argv._.length === 0) {
|
|
13
|
+
return Promise.reject(
|
|
14
|
+
new Error('You must enter a database name!')
|
|
15
|
+
).asCallback(callback);
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
internals.argv.dbname = internals.argv._.shift().toString();
|
|
16
19
|
delete config.getCurrent().settings.database;
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function (err) {
|
|
28
|
-
if (err) {
|
|
29
|
-
if (err.error) err = err.error;
|
|
30
|
-
log.error('Error: Failed to create database!', err);
|
|
31
|
-
} else {
|
|
32
|
-
log.info('Created database "' + internals.argv.dbname + '"');
|
|
33
|
-
}
|
|
21
|
+
var create = internals.mode === 'create';
|
|
22
|
+
if (!create && internals.mode !== 'drop') {
|
|
23
|
+
return Promise.reject(
|
|
24
|
+
new Error(
|
|
25
|
+
(internals.mode ? 'Unknown db command "db:' + internals.mode + '"' : 'Missing db command') +
|
|
26
|
+
', use db:create or db:drop'
|
|
27
|
+
)
|
|
28
|
+
).asCallback(callback);
|
|
29
|
+
}
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
return Promise.fromCallback(function (cb) {
|
|
32
|
+
index.driver(config.getCurrent().settings, cb);
|
|
33
|
+
})
|
|
34
|
+
.then(function (db) {
|
|
35
|
+
return Promise.fromCallback(function (cb) {
|
|
36
|
+
if (create) {
|
|
37
|
+
db.createDatabase(internals.argv.dbname, { ifNotExists: true }, cb);
|
|
38
|
+
} else {
|
|
39
|
+
db.dropDatabase(internals.argv.dbname, { ifExists: true }, cb);
|
|
38
40
|
}
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
ifExists: true
|
|
45
|
-
},
|
|
46
|
-
function (err) {
|
|
47
|
-
if (err) {
|
|
48
|
-
if (err.error) err = err.error;
|
|
49
|
-
log.error('Error: Failed to drop database!', err);
|
|
50
|
-
} else {
|
|
51
|
-
log.info('Deleted database "' + internals.argv.dbname + '"');
|
|
52
|
-
}
|
|
53
|
-
|
|
41
|
+
})
|
|
42
|
+
.catch(function (err) {
|
|
43
|
+
throw err && err.error ? err.error : err;
|
|
44
|
+
})
|
|
45
|
+
.finally(function () {
|
|
54
46
|
db.close();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
});
|
|
48
|
+
})
|
|
49
|
+
.then(function () {
|
|
50
|
+
log.info(
|
|
51
|
+
(create ? 'Created' : 'Deleted') +
|
|
52
|
+
' database "' +
|
|
53
|
+
internals.argv.dbname +
|
|
54
|
+
'"'
|
|
58
55
|
);
|
|
59
|
-
}
|
|
60
|
-
|
|
56
|
+
})
|
|
57
|
+
.asCallback(callback);
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
module.exports = executeDB;
|
package/lib/commands/down.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var path = require('path');
|
|
2
2
|
var log = require('db-migrate-shared').log;
|
|
3
3
|
var migrationHook = require('./helper/migration-hook.js');
|
|
4
|
+
var eachScope = require('./helper/scopes.js');
|
|
4
5
|
|
|
5
6
|
async function prepare (internals, config) {
|
|
6
7
|
try {
|
|
@@ -33,7 +34,7 @@ async function prepare (internals, config) {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
async function execute (internals, config) {
|
|
37
38
|
const migrator = await prepare(internals, config);
|
|
38
39
|
|
|
39
40
|
try {
|
|
@@ -42,4 +43,8 @@ module.exports = async function (internals, config) {
|
|
|
42
43
|
} catch (err) {
|
|
43
44
|
return internals.onComplete(migrator, internals, err);
|
|
44
45
|
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = function (internals, config) {
|
|
49
|
+
return eachScope(internals, () => execute(internals, config));
|
|
45
50
|
};
|
package/lib/commands/fix.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var path = require('path');
|
|
2
2
|
var log = require('db-migrate-shared').log;
|
|
3
3
|
var migrationHook = require('./helper/migration-hook.js');
|
|
4
|
+
var eachScope = require('./helper/scopes.js');
|
|
4
5
|
|
|
5
6
|
async function prepare (internals, config) {
|
|
6
7
|
try {
|
|
@@ -41,7 +42,7 @@ async function prepare (internals, config) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
async function execute (internals, config) {
|
|
45
46
|
const migrator = await prepare(internals, config);
|
|
46
47
|
|
|
47
48
|
try {
|
|
@@ -50,4 +51,8 @@ module.exports = async function (internals, config) {
|
|
|
50
51
|
} catch (err) {
|
|
51
52
|
return internals.onComplete(migrator, internals, err);
|
|
52
53
|
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = function (internals, config) {
|
|
57
|
+
return eachScope(internals, () => execute(internals, config));
|
|
53
58
|
};
|
|
@@ -16,6 +16,7 @@ const files = {
|
|
|
16
16
|
"helper/load-config": require('./helper/load-config.js'),
|
|
17
17
|
"helper/migration-hook": require('./helper/migration-hook.js'),
|
|
18
18
|
"helper/register-events": require('./helper/register-events.js'),
|
|
19
|
+
"helper/scopes": require('./helper/scopes.js'),
|
|
19
20
|
"index": require('./index.js'),
|
|
20
21
|
"on-complete": require('./on-complete.js'),
|
|
21
22
|
"run": require('./run.js'),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const log = require('db-migrate-shared').log;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The scopes below a migrations directory, nested ones as "a/b". Folders
|
|
9
|
+
* named sqls hold the files of sql-file migrations, they are no scopes.
|
|
10
|
+
*/
|
|
11
|
+
function listScopes (dir, prefix = '') {
|
|
12
|
+
return fs
|
|
13
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
14
|
+
.filter(entry => entry.isDirectory() && entry.name !== 'sqls')
|
|
15
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
16
|
+
.reduce((scopes, entry) => {
|
|
17
|
+
const scope = prefix + entry.name;
|
|
18
|
+
return scopes.concat(
|
|
19
|
+
scope,
|
|
20
|
+
listScopes(path.join(dir, entry.name), scope + '/')
|
|
21
|
+
);
|
|
22
|
+
}, []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Runs a command for its scope, or with the scope "all" once for the
|
|
27
|
+
* migrations directory itself and once for every scope, one after another.
|
|
28
|
+
* The results of all scopes are returned together.
|
|
29
|
+
*/
|
|
30
|
+
module.exports = async function eachScope (internals, run) {
|
|
31
|
+
if (internals.migrationMode !== 'all') {
|
|
32
|
+
return run();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const dir = path.resolve(internals.argv['migrations-dir'] || 'migrations');
|
|
36
|
+
const scopes = [''].concat(listScopes(dir));
|
|
37
|
+
const forceExit = internals.argv['force-exit'];
|
|
38
|
+
let results = [];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
for (let i = 0; i < scopes.length; ++i) {
|
|
42
|
+
const scope = scopes[i];
|
|
43
|
+
log.info('Enter scope "' + (scope || '/') + '"');
|
|
44
|
+
|
|
45
|
+
internals.migrationMode = scope || undefined;
|
|
46
|
+
internals.matching = scope;
|
|
47
|
+
internals.locTitle = undefined;
|
|
48
|
+
// exiting is only up to the last scope
|
|
49
|
+
internals.argv['force-exit'] = forceExit && i === scopes.length - 1;
|
|
50
|
+
|
|
51
|
+
const result = await run();
|
|
52
|
+
results = results.concat(result === undefined ? [] : result);
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
internals.migrationMode = 'all';
|
|
56
|
+
internals.argv['force-exit'] = forceExit;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return results;
|
|
60
|
+
};
|
|
@@ -79,8 +79,9 @@ module.exports = function (internals, isModule) {
|
|
|
79
79
|
.boolean('coffee-file')
|
|
80
80
|
.describe(
|
|
81
81
|
'ignore-on-init',
|
|
82
|
-
'
|
|
83
|
-
'
|
|
82
|
+
'With create, create sql-file migrations whose up is skipped when ' +
|
|
83
|
+
'running with --ignore-on-init, e.g. to initialize a database ' +
|
|
84
|
+
'from a dump (sql-file migrations only)'
|
|
84
85
|
)
|
|
85
86
|
.boolean('ignore-on-init')
|
|
86
87
|
.describe(
|
|
@@ -146,7 +147,25 @@ module.exports = function (internals, isModule) {
|
|
|
146
147
|
});
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
|
|
150
|
+
// in module mode the arguments of the process belong to the application
|
|
151
|
+
var rcConfig = rc('db-migrate', {}, isModule ? {} : undefined);
|
|
152
|
+
|
|
153
|
+
// the long names of options are aliases on the command line only
|
|
154
|
+
[
|
|
155
|
+
['state-table', 'state'],
|
|
156
|
+
['migration-table', 'table']
|
|
157
|
+
].forEach(function ([alias, name]) {
|
|
158
|
+
if (rcConfig[alias] !== undefined && rcConfig[name] === undefined) {
|
|
159
|
+
rcConfig[name] = rcConfig[alias];
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
internals.argv = deepExtend(internals.argv, rcConfig);
|
|
164
|
+
|
|
165
|
+
// positional arguments, the API adds its own ones in module mode
|
|
166
|
+
if (!Array.isArray(internals.argv._)) {
|
|
167
|
+
internals.argv._ = [];
|
|
168
|
+
}
|
|
150
169
|
internals.argv.rcconfig = internals.argv.config;
|
|
151
170
|
internals.argv.config = internals.argv.configFile || _config;
|
|
152
171
|
|
package/lib/commands/sync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var path = require('path');
|
|
2
2
|
var log = require('db-migrate-shared').log;
|
|
3
3
|
var migrationHook = require('./helper/migration-hook.js');
|
|
4
|
+
var eachScope = require('./helper/scopes.js');
|
|
4
5
|
|
|
5
6
|
async function prepare (internals, config) {
|
|
6
7
|
try {
|
|
@@ -40,7 +41,7 @@ async function prepare (internals, config) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
async function execute (internals, config) {
|
|
44
45
|
const migrator = await prepare(internals, config);
|
|
45
46
|
|
|
46
47
|
try {
|
|
@@ -49,4 +50,8 @@ module.exports = async function (internals, config) {
|
|
|
49
50
|
} catch (err) {
|
|
50
51
|
return internals.onComplete(migrator, internals, err);
|
|
51
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = function (internals, config) {
|
|
56
|
+
return eachScope(internals, () => execute(internals, config));
|
|
52
57
|
};
|
package/lib/commands/up.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var path = require('path');
|
|
2
2
|
var log = require('db-migrate-shared').log;
|
|
3
3
|
var migrationHook = require('./helper/migration-hook.js');
|
|
4
|
+
var eachScope = require('./helper/scopes.js');
|
|
4
5
|
|
|
5
6
|
async function prepare (internals, config) {
|
|
6
7
|
try {
|
|
@@ -40,7 +41,7 @@ async function prepare (internals, config) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
async function execute (internals, config) {
|
|
44
45
|
const migrator = await prepare(internals, config);
|
|
45
46
|
|
|
46
47
|
try {
|
|
@@ -49,4 +50,8 @@ module.exports = async function (internals, config) {
|
|
|
49
50
|
} catch (err) {
|
|
50
51
|
return internals.onComplete(migrator, internals, err);
|
|
51
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = function (internals, config) {
|
|
56
|
+
return eachScope(internals, () => execute(internals, config));
|
|
52
57
|
};
|
package/lib/config.js
CHANGED
|
@@ -94,6 +94,9 @@ exports.loadFile = function (fileName, currentEnv, plugins) {
|
|
|
94
94
|
return exports.loadObject(config, currentEnv);
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
+
// resolves { "ENV": ... } entries of a config, in place
|
|
98
|
+
exports.resolveEnv = walkConfig;
|
|
99
|
+
|
|
97
100
|
function walkConfig (level) {
|
|
98
101
|
for (var configEntry in level) {
|
|
99
102
|
if (level[configEntry] && level[configEntry].ENV) {
|
|
@@ -2,11 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
4
|
const maybePromised = require('../../temputils.js').maybePromised;
|
|
5
|
+
const log = require('db-migrate-shared').log;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A migration runs inside a transaction of the driver, unless it opts out
|
|
9
|
+
* with _meta.transactions = false, e.g. for statements which can not run
|
|
10
|
+
* inside one, like CREATE INDEX CONCURRENTLY of PostgreSQL.
|
|
11
|
+
*/
|
|
12
|
+
const transactional = file => {
|
|
13
|
+
const _meta = file.get()._meta || {};
|
|
14
|
+
|
|
15
|
+
if (_meta.transactions === false) {
|
|
16
|
+
log.verbose(`[migration] ${file.name} runs without a transaction`);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return true;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const start = (driver, file) =>
|
|
24
|
+
transactional(file) ? driver.startMigration() : Promise.resolve();
|
|
25
|
+
|
|
26
|
+
const end = (driver, file) =>
|
|
27
|
+
transactional(file) ? driver.endMigration() : Promise.resolve();
|
|
5
28
|
|
|
6
29
|
const execUnit = {
|
|
7
30
|
up: function (context, driver, file) {
|
|
8
|
-
return context.driver
|
|
9
|
-
.startMigration()
|
|
31
|
+
return start(context.driver, file)
|
|
10
32
|
.then(() => {
|
|
11
33
|
const _file = file.get();
|
|
12
34
|
|
|
@@ -21,12 +43,11 @@ const execUnit = {
|
|
|
21
43
|
file
|
|
22
44
|
);
|
|
23
45
|
})
|
|
24
|
-
.then(
|
|
46
|
+
.then(() => end(context.driver, file));
|
|
25
47
|
},
|
|
26
48
|
|
|
27
49
|
down: function (context, driver, file) {
|
|
28
|
-
return driver
|
|
29
|
-
.startMigration()
|
|
50
|
+
return start(driver, file)
|
|
30
51
|
.then(() => {
|
|
31
52
|
const _file = file.get();
|
|
32
53
|
|
|
@@ -41,7 +62,7 @@ const execUnit = {
|
|
|
41
62
|
file
|
|
42
63
|
);
|
|
43
64
|
})
|
|
44
|
-
.then(
|
|
65
|
+
.then(() => end(context.driver, file));
|
|
45
66
|
}
|
|
46
67
|
};
|
|
47
68
|
|
|
@@ -181,6 +181,15 @@ const execUnit = {
|
|
|
181
181
|
);
|
|
182
182
|
describeFailure(err, chain, context.internals);
|
|
183
183
|
|
|
184
|
+
if (context.internals.modSchema.s.some(entry => entry.t === 3)) {
|
|
185
|
+
log.error(
|
|
186
|
+
`Migration "${file.name}" failed and can not be rolled back, it ` +
|
|
187
|
+
'ran a step with { irreversible: true }. The steps executed ' +
|
|
188
|
+
'stay, the next run continues after them.'
|
|
189
|
+
);
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
|
|
184
193
|
log.error(
|
|
185
194
|
`Migration "${file.name}" failed, rolling back: ${
|
|
186
195
|
(err && err.message) || err
|
|
@@ -246,7 +255,16 @@ const execUnit = {
|
|
|
246
255
|
});
|
|
247
256
|
}
|
|
248
257
|
// start migration, see up comments
|
|
249
|
-
|
|
258
|
+
try {
|
|
259
|
+
await TranslateState(context._driver, file, driver, context.internals, context._pdriver);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
// refused before changing anything, the state is left as it was
|
|
262
|
+
if (err.irreversible && abort !== true) {
|
|
263
|
+
await State.endMigration(context._pdriver, file, context.internals);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
250
268
|
await State.endMigration(context._pdriver, file, context.internals);
|
|
251
269
|
return Promise.promisify(context.deleteMigrationRecord.bind(context))(file);
|
|
252
270
|
// end migration, see up comments
|
package/lib/file.js
CHANGED
|
@@ -200,6 +200,11 @@ File.loadFromFileystem = function (dir, prefix, internals) {
|
|
|
200
200
|
};
|
|
201
201
|
|
|
202
202
|
File.loadFromDatabase = function (dir, prefix, driver, internals) {
|
|
203
|
+
// --ignore-completed-migrations starts at the first migration
|
|
204
|
+
if (internals.ignoreCompleted) {
|
|
205
|
+
return Promise.resolve([]);
|
|
206
|
+
}
|
|
207
|
+
|
|
203
208
|
log.verbose(`[${prefix}] loading from database`);
|
|
204
209
|
return driver[LOADER[prefix]]()
|
|
205
210
|
.catch(function (err) {
|
package/lib/learn.js
CHANGED
|
@@ -1,15 +1,52 @@
|
|
|
1
1
|
const Promise = require('bluebird');
|
|
2
2
|
const Shadow = require('./driver/shadow');
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Objects unknown to the schema were not created by a v2 migration, but e.g.
|
|
6
|
+
* by a v1 migration or by hand. The errors point to the ways of handling them:
|
|
7
|
+
* adopting them into the schema first, or dropping them irreversibly.
|
|
8
|
+
*/
|
|
9
|
+
const IRREVERSIBLE = ', or pass { irreversible: true } to drop it without being able to revert it';
|
|
10
|
+
|
|
11
|
+
const unknown = (what, adopt, drop) =>
|
|
12
|
+
new Error(
|
|
13
|
+
`${what} is unknown to the schema of db-migrate, it was not created by a ` +
|
|
14
|
+
`v2 migration. Declare it first with db.adopt.${adopt}` +
|
|
15
|
+
(drop ? IRREVERSIBLE : '') +
|
|
16
|
+
'.'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const unknownTable = (t, drop) =>
|
|
20
|
+
unknown(`The table "${t}"`, `createTable("${t}", columns)`, drop);
|
|
21
|
+
|
|
22
|
+
const unknownColumn = (t, c, drop) =>
|
|
23
|
+
unknown(`The column "${c}" of "${t}"`, `addColumn("${t}", "${c}", spec)`, drop);
|
|
24
|
+
|
|
25
|
+
const unknownIndex = (t, i) =>
|
|
26
|
+
unknown(`The index "${i}" of "${t}"`, `addIndex("${t}", "${i}", columns)`);
|
|
27
|
+
|
|
28
|
+
const unknownForeignKey = (t, k, drop) =>
|
|
29
|
+
unknown(
|
|
30
|
+
`The foreign key "${k}" of "${t}"`,
|
|
31
|
+
`addForeignKey("${t}", referencedTable, "${k}", mapping)`,
|
|
32
|
+
drop
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A step dropping an object unknown to the schema, asked for explicitly.
|
|
37
|
+
* It can not be reverted, a rollback refuses to start because of it.
|
|
38
|
+
*/
|
|
39
|
+
const irreversible = (action, args) => ({ t: 3, a: action, c: args });
|
|
40
|
+
|
|
4
41
|
class STD {
|
|
5
42
|
constructor ({ schema, modSchema: mod, unlearn }, driver) {
|
|
6
|
-
this.checkColumn = function (t, c) {
|
|
43
|
+
this.checkColumn = function (t, c, drop) {
|
|
7
44
|
if (!this.schema[t]) {
|
|
8
|
-
throw
|
|
45
|
+
throw unknownTable(t, drop);
|
|
9
46
|
}
|
|
10
47
|
|
|
11
48
|
if (!this.schema[t][c] && !this.schema[t].columns?.[c]) {
|
|
12
|
-
throw
|
|
49
|
+
throw unknownColumn(t, c, drop);
|
|
13
50
|
}
|
|
14
51
|
};
|
|
15
52
|
this.unlearn = unlearn;
|
|
@@ -32,10 +69,19 @@ class STD {
|
|
|
32
69
|
this.modF = mod.f;
|
|
33
70
|
}
|
|
34
71
|
|
|
35
|
-
dropTable (t) {
|
|
72
|
+
dropTable (t, o = {}) {
|
|
36
73
|
let alter = {};
|
|
37
74
|
alter = { c: {}, i: {}, f: {} };
|
|
38
75
|
|
|
76
|
+
if (!this.schema[t] && !this.unlearn) {
|
|
77
|
+
if (!o || o.irreversible !== true) {
|
|
78
|
+
throw unknownTable(t, true);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.modC.push(irreversible('dropTable', [t]));
|
|
82
|
+
return Promise.resolve(alter);
|
|
83
|
+
}
|
|
84
|
+
|
|
39
85
|
if (this.schema[t]) {
|
|
40
86
|
alter.c[t] = this.schema[t];
|
|
41
87
|
this.modS[t] = this.schema[t];
|
|
@@ -125,7 +171,19 @@ class STD {
|
|
|
125
171
|
|
|
126
172
|
removeColumn (t, c, o = {}) {
|
|
127
173
|
const alter = {};
|
|
128
|
-
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
o &&
|
|
177
|
+
o.irreversible === true &&
|
|
178
|
+
!this.unlearn &&
|
|
179
|
+
(!this.schema[t] ||
|
|
180
|
+
(!this.schema[t][c] && !this.schema[t].columns?.[c]))
|
|
181
|
+
) {
|
|
182
|
+
this.modC.push(irreversible('removeColumn', [t, c]));
|
|
183
|
+
return Promise.resolve(alter);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.checkColumn(t, c, true);
|
|
129
187
|
const hasColumns = this.schema[t].columns !== undefined;
|
|
130
188
|
const columns = hasColumns ? this.schema[t].columns : this.schema[t];
|
|
131
189
|
|
|
@@ -230,7 +288,7 @@ class STD {
|
|
|
230
288
|
|
|
231
289
|
addColumn (t, c, s) {
|
|
232
290
|
if (!this.schema[t]) {
|
|
233
|
-
throw
|
|
291
|
+
throw unknownTable(t);
|
|
234
292
|
}
|
|
235
293
|
this.schema[t] = this.schema[t] || {};
|
|
236
294
|
const hasColumns = this.schema[t].columns !== undefined;
|
|
@@ -292,11 +350,11 @@ class STD {
|
|
|
292
350
|
}
|
|
293
351
|
|
|
294
352
|
if (!this.schema[t]) {
|
|
295
|
-
throw
|
|
353
|
+
throw unknownTable(t);
|
|
296
354
|
}
|
|
297
355
|
|
|
298
356
|
if (!this.indizies[t] || !this.indizies[t][i]) {
|
|
299
|
-
throw
|
|
357
|
+
throw unknownIndex(t, i);
|
|
300
358
|
}
|
|
301
359
|
|
|
302
360
|
alter.i[t] = {};
|
|
@@ -312,11 +370,11 @@ class STD {
|
|
|
312
370
|
|
|
313
371
|
addForeignKey (t, rt, k, m, r) {
|
|
314
372
|
if (!this.schema[t]) {
|
|
315
|
-
throw
|
|
373
|
+
throw unknownTable(t);
|
|
316
374
|
}
|
|
317
375
|
|
|
318
376
|
if (!this.schema[rt]) {
|
|
319
|
-
throw
|
|
377
|
+
throw unknownTable(rt);
|
|
320
378
|
}
|
|
321
379
|
|
|
322
380
|
if (!this.foreign[t]) this.foreign[t] = {};
|
|
@@ -335,13 +393,19 @@ class STD {
|
|
|
335
393
|
removeForeignKey (t, k, o) {
|
|
336
394
|
let alter = {};
|
|
337
395
|
alter = { c: {}, i: {}, f: {} };
|
|
396
|
+
const known = this.schema[t] && this.foreign[t] && this.foreign[t][k];
|
|
397
|
+
|
|
398
|
+
if (!known && o && o.irreversible === true && !this.unlearn) {
|
|
399
|
+
this.modC.push(irreversible('removeForeignKey', [t, k]));
|
|
400
|
+
return Promise.resolve(alter);
|
|
401
|
+
}
|
|
338
402
|
|
|
339
403
|
if (!this.schema[t]) {
|
|
340
|
-
throw
|
|
404
|
+
throw unknownTable(t, true);
|
|
341
405
|
}
|
|
342
406
|
|
|
343
407
|
if (!this.foreign[t] || !this.foreign[t][k]) {
|
|
344
|
-
throw
|
|
408
|
+
throw unknownForeignKey(t, k, true);
|
|
345
409
|
}
|
|
346
410
|
|
|
347
411
|
alter.f[t] = {};
|
|
@@ -377,6 +441,9 @@ module.exports = {
|
|
|
377
441
|
// learns the schema and records the reverse operations
|
|
378
442
|
learns: true,
|
|
379
443
|
|
|
444
|
+
unknownTable,
|
|
445
|
+
unknownColumn,
|
|
446
|
+
|
|
380
447
|
getInterface: (context, file, driver, internals) => {
|
|
381
448
|
if (context.learnable) {
|
|
382
449
|
const _std = new STD(internals, context);
|
|
@@ -28,6 +28,9 @@ const noAction = prop => {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
module.exports = {
|
|
31
|
+
// adds the default column to tables created on the database
|
|
32
|
+
executes: true,
|
|
33
|
+
|
|
31
34
|
getInterface: (context, file, driver, internals) => {
|
|
32
35
|
if (context.conventions) {
|
|
33
36
|
const st = new AddConventions(internals, file, context);
|
|
@@ -100,6 +100,10 @@ async function processEntry (
|
|
|
100
100
|
case 1:
|
|
101
101
|
await f[action](driver, args, internals);
|
|
102
102
|
break;
|
|
103
|
+
case 2:
|
|
104
|
+
// an adopted object, only forgotten again
|
|
105
|
+
await driver.learnOnly.apply(driver, [action].concat(args));
|
|
106
|
+
break;
|
|
103
107
|
|
|
104
108
|
default:
|
|
105
109
|
throw new Error(`Invalid state record, of type ${type}`);
|
|
@@ -110,8 +114,28 @@ async function processEntry (
|
|
|
110
114
|
await State.update(pdriver, file, internals.modSchema, internals);
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Steps run with { irreversible: true } can not be reverted, the migration
|
|
119
|
+
* is not reverted at all then, instead of only partially.
|
|
120
|
+
*/
|
|
121
|
+
const irreversibleSteps = internals =>
|
|
122
|
+
internals.modSchema.s
|
|
123
|
+
.filter(entry => entry.t === 3)
|
|
124
|
+
.map(entry => `step ${entry.n} ${entry.a}(${entry.c.map(x => JSON.stringify(x)).join(', ')})`);
|
|
125
|
+
|
|
113
126
|
module.exports = async (context, file, driver, internals, pdriver) => {
|
|
114
127
|
const mod = internals.modSchema;
|
|
128
|
+
const irreversible = irreversibleSteps(internals);
|
|
129
|
+
|
|
130
|
+
if (irreversible.length > 0) {
|
|
131
|
+
const err = new Error(
|
|
132
|
+
`Migration "${file.name}" can not be reverted, it ran ` +
|
|
133
|
+
`${irreversible.join(', ')} with { irreversible: true }.`
|
|
134
|
+
);
|
|
135
|
+
// nothing was changed
|
|
136
|
+
err.irreversible = true;
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
115
139
|
|
|
116
140
|
const chain = new Chain(context, file, driver, internals, pdriver);
|
|
117
141
|
internals.unlearn = true;
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"programmable",
|
|
32
32
|
"api"
|
|
33
33
|
],
|
|
34
|
-
"version": "1.
|
|
34
|
+
"version": "1.2.0",
|
|
35
35
|
"bugs": {
|
|
36
36
|
"url": "https://github.com/db-migrate/node-db-migrate/issues"
|
|
37
37
|
},
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"@hapi/code": "9.x",
|
|
58
58
|
"@hapi/eslint-plugin": "^6.0.0",
|
|
59
59
|
"@hapi/lab": "25.x",
|
|
60
|
-
"db-migrate-base": "^2.
|
|
61
|
-
"db-migrate-mysql": "^
|
|
62
|
-
"db-migrate-sqlite3": "^
|
|
60
|
+
"db-migrate-base": "^2.4.1",
|
|
61
|
+
"db-migrate-mysql": "^3.1.1",
|
|
62
|
+
"db-migrate-sqlite3": "^1.1.1",
|
|
63
63
|
"eslint": "^7.32.0",
|
|
64
64
|
"eslint-config-standard": "^14.1.1",
|
|
65
65
|
"eslint-plugin-import": "^2.26.0",
|
|
@@ -79,5 +79,9 @@
|
|
|
79
79
|
"pretest": "eslint *.js lib/*.js",
|
|
80
80
|
"test": "lab",
|
|
81
81
|
"test:coverage": "lab -r html -o coverage.html"
|
|
82
|
+
},
|
|
83
|
+
"overrides": {
|
|
84
|
+
"tar": "^7.5.21",
|
|
85
|
+
"node-gyp": "^12.0.0"
|
|
82
86
|
}
|
|
83
87
|
}
|