db-migrate 1.0.0-beta.9 → 1.0.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/walker.js CHANGED
@@ -6,36 +6,38 @@ const log = require('db-migrate-shared').log;
6
6
  const Promise = require('bluebird');
7
7
  const File = require('./file.js');
8
8
 
9
- // Not sure what will happen to this yet
10
- function SeedLink (driver, internals) {
11
- this.seeder = require('./seeder.js')(
12
- driver,
13
- internals.argv['vcseeder-dir'],
14
- true,
15
- internals
16
- );
17
- this.internals = internals;
18
- this.links = [];
19
- }
20
-
21
9
  const INTERFACES = {
22
10
  migration: require('./interface/migratorInterface.js'),
23
11
  seed: require('./interface/seederInterface.js'),
24
12
  'static-seed': require('./interface/seederInterface.js')
25
13
  };
26
14
 
27
- const Walker = function (driver, directory, mode, intern, prefix) {
15
+ /**
16
+ * Name the migration an error belongs to, for the error output.
17
+ */
18
+ const failedIn = file => err => {
19
+ if (err && typeof err === 'object' && err.migration === undefined) {
20
+ err.migration = file.name;
21
+ }
22
+
23
+ throw err;
24
+ };
25
+
26
+ const Walker = function (driver, directory, mode, intern, prefix, opts = {}) {
28
27
  this.driver = dbmUtil.reduceToInterface(driver, INTERFACES[prefix]);
29
28
  this._driver = driver;
29
+ this._pdriver = opts.db2;
30
30
  Promise.promisifyAll(this._driver);
31
+ Promise.promisifyAll(this._pdriver);
31
32
  this.directory = directory;
32
33
  this.internals = intern;
33
34
  /**
34
35
  * i: index
35
36
  * c: schema
36
37
  * f: foreignKey
38
+ * e: extra items for the schema (like ENUM types)
37
39
  */
38
- this.internals.schema = { i: {}, c: {}, f: {} };
40
+ this.internals.schema = { i: {}, c: {}, f: {}, e: {} };
39
41
  /**
40
42
  * s: commands
41
43
  * i: index
@@ -51,19 +53,17 @@ const Walker = function (driver, directory, mode, intern, prefix) {
51
53
  this.title = `[${prefix}] `;
52
54
 
53
55
  // keep it until we decide how we do the cross linking
54
- if (intern.linked === false) {
55
- this.seedLink = new SeedLink(driver, intern);
56
- intern.linked = true;
57
- }
58
56
  };
59
57
 
60
58
  Walker.prototype = {
61
- createTables: async function () {
62
- await State.init(this._driver, this.internals);
63
- return this._driver._createList(this.internals.migrationTable);
59
+ createTables: async function (options) {
60
+ await State.init(this._pdriver, this.internals, options);
61
+ return State.createConcurrently(() =>
62
+ this._driver._createList(this.internals.migrationTable)
63
+ );
64
64
  },
65
65
 
66
- createMigrationsTable: function () {
66
+ createMigrationsTable: function (options = {}) {
67
67
  if (
68
68
  typeof this._driver._createList !== 'function' ||
69
69
  typeof this._driver._getList !== 'function' ||
@@ -84,7 +84,53 @@ Walker.prototype = {
84
84
  }
85
85
  }
86
86
 
87
- return this.createTables();
87
+ return this.createTables(options);
88
+ },
89
+
90
+ /**
91
+ * Determine what to run first, only lock when there is actually something
92
+ * to run. If another process holds the lock, wait for its release and
93
+ * determine again, the other process might have done the work already.
94
+ * Once we hold the lock, state and pending migrations are read again.
95
+ */
96
+ _withLock: async function (determine, run) {
97
+ if (this.prefix !== 'migration' || this.internals.dryRun) {
98
+ return run(await determine());
99
+ }
100
+
101
+ if (!State.supportsLock(this._pdriver)) {
102
+ log.warn(
103
+ this.title +
104
+ 'Your database driver does not support migration locking, ' +
105
+ 'concurrent processes are not prevented from migrating at the ' +
106
+ 'same time. Update your driver to a version supporting it.'
107
+ );
108
+ return run(await determine());
109
+ }
110
+
111
+ let stale = null;
112
+ let toRun = await determine();
113
+
114
+ while (toRun.length > 0) {
115
+ if (await State.acquire(this._pdriver, this.internals, stale)) {
116
+ break;
117
+ }
118
+
119
+ log.info(this.title + 'Waiting for the migration lock of another process');
120
+ stale = await State.waitForRelease(this._pdriver, this.internals);
121
+ toRun = await determine();
122
+ }
123
+
124
+ if (toRun.length === 0) {
125
+ return run(toRun);
126
+ }
127
+
128
+ try {
129
+ await State.reloadSchema(this._pdriver, this.internals);
130
+ return await run(await determine());
131
+ } finally {
132
+ await State.release(this._pdriver, this.internals);
133
+ }
88
134
  },
89
135
 
90
136
  writeMigrationRecord: function (migration, callback) {
@@ -146,96 +192,156 @@ Walker.prototype = {
146
192
  up: function (options, callback) {
147
193
  const partialName = options.destination;
148
194
  const count = options.count;
149
- const files = Promise.all([
150
- File.loadFromFileystem(this.directory, this.prefix, this.internals),
195
+ const determine = () =>
196
+ Promise.all([
197
+ File.loadFromFileystem(this.directory, this.prefix, this.internals),
198
+ File.loadFromDatabase(
199
+ this.directory,
200
+ this.prefix,
201
+ this._driver,
202
+ this.internals
203
+ )
204
+ ]).spread((allFiles, completedFiles) =>
205
+ dbmUtil.filterUp(allFiles, completedFiles, partialName, count)
206
+ );
207
+
208
+ if (this.internals.check) {
209
+ return determine()
210
+ .then(toRun => this._checkResult(toRun))
211
+ .nodeify(callback);
212
+ }
213
+
214
+ return Promise.resolve(
215
+ this._withLock(determine, toRun => {
216
+ if (toRun.length === 0) {
217
+ log.info(this.title + 'Nothing to run');
218
+ }
219
+
220
+ return Promise.each(toRun, file => {
221
+ log.verbose(this.title + 'preparing to run up:', file.name);
222
+ const _meta = file.get()._meta || {};
223
+ const version = _meta.version || 1;
224
+ this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
225
+ return Promise.resolve(
226
+ require(`./executors/versioned/v${version}`).up(
227
+ this,
228
+ this.driver,
229
+ file
230
+ )
231
+ ).catch(failedIn(file));
232
+ });
233
+ })
234
+ ).nodeify(callback);
235
+ },
236
+
237
+ down: function (options, callback) {
238
+ const partialName = options.destination;
239
+ const count = options.count;
240
+ const determine = () =>
151
241
  File.loadFromDatabase(
152
242
  this.directory,
153
243
  this.prefix,
154
244
  this._driver,
155
245
  this.internals
156
- )
157
- ]).spread((allFiles, completedFiles) => {
158
- const toRun = dbmUtil.filterUp(
159
- allFiles,
160
- completedFiles,
161
- partialName,
162
- count
246
+ ).then(completedFiles =>
247
+ dbmUtil.filterDown(completedFiles, partialName, count)
163
248
  );
164
249
 
165
- if (toRun.length === 0) {
166
- log.info(this.title + 'Nothing to run');
167
- }
168
-
169
- if (this.internals.check) {
170
- const toRunNames = toRun.map(migration => migration.name);
171
- log.info(this.title + 'run:', toRunNames);
172
- return toRunNames;
173
- }
174
-
175
- return toRun;
176
- });
177
-
178
250
  if (this.internals.check) {
179
- return files.nodeify(callback);
251
+ return determine()
252
+ .then(toRun => this._checkResult(toRun))
253
+ .nodeify(callback);
180
254
  }
181
255
 
182
- return files
183
- .each(file => {
184
- log.verbose(this.title + 'preparing to run up:', file.name);
185
- const _meta = file.get()._meta || {};
186
- const version = _meta.version || 1;
187
- this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
188
- return require(`./executors/versioned/v${version}`).up(
189
- this,
190
- this.driver,
191
- file
192
- );
256
+ return Promise.resolve(
257
+ this._withLock(determine, toRun => {
258
+ if (toRun.length === 0) {
259
+ log.info(this.title + 'Nothing to run');
260
+ }
261
+
262
+ return Promise.each(toRun, file => {
263
+ log.verbose(this.title + 'preparing to run down:', file.name);
264
+ const _meta = file.get()._meta || {};
265
+ const version = _meta.version || 1;
266
+ this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
267
+ return Promise.resolve(
268
+ require(`./executors/versioned/v${version}`).down(
269
+ this,
270
+ this.driver,
271
+ file
272
+ )
273
+ ).catch(failedIn(file));
274
+ });
193
275
  })
194
- .nodeify(callback);
276
+ ).nodeify(callback);
195
277
  },
196
278
 
197
- down: function (options, callback) {
279
+ fix: function (options, callback) {
198
280
  const partialName = options.destination;
199
281
  const count = options.count;
282
+ const sortFn = function (a, b) {
283
+ a = a.name.slice(0, a.name.indexOf('-'));
284
+ b = b.name.slice(0, b.name.indexOf('-'));
200
285
 
201
- const files = File.loadFromDatabase(
202
- this.directory,
203
- this.prefix,
204
- this._driver,
205
- this.internals
206
- ).then(completedFiles => {
207
- const toRun = dbmUtil.filterDown(completedFiles, partialName, count);
208
-
209
- if (toRun.length === 0) {
210
- log.info(this.title + 'Nothing to run');
286
+ if (!isNaN(a)) {
287
+ return a - b;
211
288
  }
212
289
 
213
- if (this.internals.check) {
214
- const toRunNames = toRun.map(migration => migration.name);
215
- log.info(this.title + 'run:', toRunNames);
216
- return toRunNames;
217
- }
290
+ return a.localeCompare(b);
291
+ };
218
292
 
219
- return toRun;
220
- });
293
+ const determine = () =>
294
+ File.loadFromDatabase(
295
+ this.directory,
296
+ this.prefix,
297
+ this._driver,
298
+ this.internals
299
+ ).then(completedFiles =>
300
+ dbmUtil.filterDown(completedFiles, partialName, count).sort(sortFn)
301
+ );
221
302
 
222
303
  if (this.internals.check) {
223
- return files.nodeify(callback);
304
+ return determine()
305
+ .then(toRun => this._checkResult(toRun))
306
+ .nodeify(callback);
224
307
  }
225
308
 
226
- return files
227
- .each(file => {
228
- log.verbose(this.title + 'preparing to run down:', file.name);
229
- const _meta = file.get()._meta || {};
230
- const version = _meta.version || 1;
231
- this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
232
- return require(`./executors/versioned/v${version}`).down(
233
- this,
234
- this.driver,
235
- file
236
- );
309
+ return Promise.resolve(
310
+ this._withLock(determine, toRun => {
311
+ if (toRun.length === 0) {
312
+ log.info(this.title + 'Nothing to run');
313
+ }
314
+
315
+ return Promise.each(toRun, file => {
316
+ log.verbose(this.title + 'preparing to run fix:', file.name);
317
+ const _meta = file.get()._meta || {};
318
+ const version = _meta.version || 1;
319
+ if (version < 2) {
320
+ log.warn(`${this.title} skipping "${file.name}" as v1 migrations do not maintain a schema`);
321
+ return Promise.resolve();
322
+ }
323
+
324
+ this.internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
325
+ return Promise.resolve(
326
+ require(`./executors/versioned/v${version}`).fix(
327
+ this,
328
+ this.driver,
329
+ file
330
+ )
331
+ ).catch(failedIn(file));
332
+ });
237
333
  })
238
- .nodeify(callback);
334
+ ).nodeify(callback);
335
+ },
336
+
337
+ _checkResult: function (toRun) {
338
+ if (toRun.length === 0) {
339
+ log.info(this.title + 'Nothing to run');
340
+ }
341
+
342
+ const toRunNames = toRun.map(migration => migration.name);
343
+ log.info(this.title + 'run:', toRunNames);
344
+ return toRunNames;
239
345
  },
240
346
 
241
347
  check: function (options, callback) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "db-migrate",
3
3
  "description": "Database migration framework for node.js",
4
- "author": "Tobias Gurtick",
4
+ "author": "Tobias Gurtzick",
5
5
  "bin": "./bin/db-migrate",
6
6
  "keywords": [
7
7
  "database",
@@ -27,14 +27,11 @@
27
27
  "seeds",
28
28
  "seeder",
29
29
  "migrator",
30
- "programatic",
31
- "programable",
30
+ "programmatic",
31
+ "programmable",
32
32
  "api"
33
33
  ],
34
- "version": "1.0.0-beta.9",
35
- "engines": {
36
- "node": ">=8.0.0"
37
- },
34
+ "version": "1.0.0",
38
35
  "bugs": {
39
36
  "url": "https://github.com/db-migrate/node-db-migrate/issues"
40
37
  },
@@ -44,32 +41,31 @@
44
41
  "url": "https://github.com/db-migrate/node-db-migrate.git"
45
42
  },
46
43
  "dependencies": {
47
- "bluebird": "^3.1.1",
44
+ "bluebird": "^3.7.2",
48
45
  "db-migrate-shared": "^1.2.0",
49
46
  "deep-extend": "^0.6.0",
50
- "dotenv": "^5.0.1",
47
+ "dotenv": "^16.3.1",
51
48
  "final-fs": "^1.6.0",
52
49
  "inflection": "^1.10.0",
53
- "mkdirp": "~0.5.0",
54
50
  "parse-database-url": "~0.3.0",
55
51
  "rc": "^1.2.8",
56
- "resolve": "^1.1.6",
57
- "semver": "^5.3.0",
58
- "tunnel-ssh": "^4.0.0",
59
- "yargs": "^15.3.1"
52
+ "resolve": "^1.19.0",
53
+ "yargs": "^17.5.1"
60
54
  },
61
55
  "devDependencies": {
62
- "@hapi/code": "5.x",
63
- "@hapi/lab": "18.x",
56
+ "@babel/eslint-parser": "^7.21.8",
57
+ "@hapi/code": "9.x",
58
+ "@hapi/eslint-plugin": "^6.0.0",
59
+ "@hapi/lab": "25.x",
64
60
  "db-migrate-base": "^2.1.1",
65
- "db-migrate-mysql": "^2.1.1",
66
- "db-migrate-sqlite3": "^0.4.0",
67
- "eslint": "^6.8.0",
61
+ "db-migrate-mysql": "^2.2.0",
62
+ "db-migrate-sqlite3": "^0.5.0",
63
+ "eslint": "^7.32.0",
68
64
  "eslint-config-standard": "^14.1.1",
69
- "eslint-plugin-import": "^2.20.2",
65
+ "eslint-plugin-import": "^2.26.0",
70
66
  "eslint-plugin-node": "^11.1.0",
71
- "eslint-plugin-promise": "^4.2.1",
72
- "eslint-plugin-standard": "^4.0.1",
67
+ "eslint-plugin-promise": "^4.3.1",
68
+ "eslint-plugin-standard": "^4.1.0",
73
69
  "proxyquire": "^1.4.0",
74
70
  "rimraf": "^3.0.2",
75
71
  "sinon": "^9.0.2"