knex 0.21.20 → 0.21.21

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CONTRIBUTING.md +184 -184
  3. package/LICENSE +22 -22
  4. package/README.md +95 -95
  5. package/bin/cli.js +414 -414
  6. package/bin/utils/cli-config-utils.js +151 -151
  7. package/bin/utils/constants.js +7 -7
  8. package/bin/utils/migrationsLister.js +37 -37
  9. package/knex.js +8 -8
  10. package/lib/client.js +413 -413
  11. package/lib/config-resolver.js +61 -61
  12. package/lib/constants.js +44 -44
  13. package/lib/dialects/mssql/index.js +390 -390
  14. package/lib/dialects/mssql/query/compiler.js +444 -444
  15. package/lib/dialects/mssql/schema/columncompiler.js +103 -103
  16. package/lib/dialects/mssql/schema/compiler.js +59 -59
  17. package/lib/dialects/mssql/schema/tablecompiler.js +245 -245
  18. package/lib/dialects/mssql/transaction.js +97 -97
  19. package/lib/dialects/mysql/index.js +191 -191
  20. package/lib/dialects/mysql/query/compiler.js +142 -142
  21. package/lib/dialects/mysql/schema/columncompiler.js +171 -171
  22. package/lib/dialects/mysql/schema/compiler.js +60 -60
  23. package/lib/dialects/mysql/schema/tablecompiler.js +262 -262
  24. package/lib/dialects/mysql/transaction.js +48 -48
  25. package/lib/dialects/mysql2/index.js +35 -35
  26. package/lib/dialects/mysql2/transaction.js +46 -46
  27. package/lib/dialects/oracle/DEAD_CODE.md +5 -5
  28. package/lib/dialects/oracle/formatter.js +20 -20
  29. package/lib/dialects/oracle/index.js +79 -79
  30. package/lib/dialects/oracle/query/compiler.js +327 -327
  31. package/lib/dialects/oracle/schema/columnbuilder.js +18 -18
  32. package/lib/dialects/oracle/schema/columncompiler.js +139 -139
  33. package/lib/dialects/oracle/schema/compiler.js +81 -81
  34. package/lib/dialects/oracle/schema/tablecompiler.js +165 -165
  35. package/lib/dialects/oracle/schema/trigger.js +126 -126
  36. package/lib/dialects/oracle/utils.js +86 -86
  37. package/lib/dialects/oracledb/index.js +489 -489
  38. package/lib/dialects/oracledb/query/compiler.js +363 -363
  39. package/lib/dialects/oracledb/schema/columncompiler.js +35 -35
  40. package/lib/dialects/oracledb/transaction.js +76 -76
  41. package/lib/dialects/oracledb/utils.js +14 -14
  42. package/lib/dialects/postgres/index.js +319 -319
  43. package/lib/dialects/postgres/query/compiler.js +206 -206
  44. package/lib/dialects/postgres/schema/columncompiler.js +125 -125
  45. package/lib/dialects/postgres/schema/compiler.js +109 -109
  46. package/lib/dialects/postgres/schema/tablecompiler.js +183 -183
  47. package/lib/dialects/redshift/index.js +73 -73
  48. package/lib/dialects/redshift/query/compiler.js +119 -119
  49. package/lib/dialects/redshift/schema/columnbuilder.js +20 -20
  50. package/lib/dialects/redshift/schema/columncompiler.js +60 -60
  51. package/lib/dialects/redshift/schema/compiler.js +14 -14
  52. package/lib/dialects/redshift/schema/tablecompiler.js +123 -123
  53. package/lib/dialects/redshift/transaction.js +18 -18
  54. package/lib/dialects/sqlite3/formatter.js +21 -21
  55. package/lib/dialects/sqlite3/index.js +169 -169
  56. package/lib/dialects/sqlite3/query/compiler.js +222 -222
  57. package/lib/dialects/sqlite3/schema/columncompiler.js +27 -27
  58. package/lib/dialects/sqlite3/schema/compiler.js +49 -49
  59. package/lib/dialects/sqlite3/schema/ddl.js +525 -525
  60. package/lib/dialects/sqlite3/schema/tablecompiler.js +238 -238
  61. package/lib/formatter.js +295 -295
  62. package/lib/functionhelper.js +14 -14
  63. package/lib/helpers.js +92 -92
  64. package/lib/index.js +3 -3
  65. package/lib/interface.js +115 -115
  66. package/lib/knex.js +42 -42
  67. package/lib/logger.js +76 -76
  68. package/lib/migrate/MigrationGenerator.js +82 -82
  69. package/lib/migrate/Migrator.js +611 -611
  70. package/lib/migrate/configuration-merger.js +60 -60
  71. package/lib/migrate/migrate-stub.js +17 -17
  72. package/lib/migrate/migration-list-resolver.js +36 -36
  73. package/lib/migrate/sources/fs-migrations.js +99 -99
  74. package/lib/migrate/stub/cjs.stub +15 -15
  75. package/lib/migrate/stub/coffee.stub +13 -13
  76. package/lib/migrate/stub/eg.stub +14 -14
  77. package/lib/migrate/stub/js.stub +15 -15
  78. package/lib/migrate/stub/knexfile-coffee.stub +34 -34
  79. package/lib/migrate/stub/knexfile-eg.stub +43 -43
  80. package/lib/migrate/stub/knexfile-js.stub +44 -44
  81. package/lib/migrate/stub/knexfile-ls.stub +35 -35
  82. package/lib/migrate/stub/knexfile-ts.stub +44 -44
  83. package/lib/migrate/stub/ls.stub +14 -14
  84. package/lib/migrate/stub/ts.stub +21 -21
  85. package/lib/migrate/table-creator.js +67 -67
  86. package/lib/migrate/table-resolver.js +27 -27
  87. package/lib/query/builder.js +1372 -1372
  88. package/lib/query/compiler.js +889 -889
  89. package/lib/query/constants.js +13 -13
  90. package/lib/query/joinclause.js +263 -263
  91. package/lib/query/methods.js +92 -92
  92. package/lib/query/string.js +190 -190
  93. package/lib/raw.js +188 -188
  94. package/lib/ref.js +39 -39
  95. package/lib/runner.js +285 -285
  96. package/lib/schema/builder.js +82 -82
  97. package/lib/schema/columnbuilder.js +117 -117
  98. package/lib/schema/columncompiler.js +177 -177
  99. package/lib/schema/compiler.js +101 -101
  100. package/lib/schema/helpers.js +51 -51
  101. package/lib/schema/tablebuilder.js +288 -288
  102. package/lib/schema/tablecompiler.js +296 -296
  103. package/lib/seed/Seeder.js +203 -203
  104. package/lib/seed/seed-stub.js +13 -13
  105. package/lib/seed/stub/coffee.stub +9 -9
  106. package/lib/seed/stub/eg.stub +11 -11
  107. package/lib/seed/stub/js.stub +13 -13
  108. package/lib/seed/stub/ls.stub +11 -11
  109. package/lib/seed/stub/ts.stub +13 -13
  110. package/lib/transaction.js +363 -363
  111. package/lib/util/batchInsert.js +59 -59
  112. package/lib/util/delay.js +6 -6
  113. package/lib/util/fake-client.js +9 -9
  114. package/lib/util/finally-mixin.js +13 -13
  115. package/lib/util/fs.js +76 -76
  116. package/lib/util/import-file.js +13 -13
  117. package/lib/util/is-module-type.js +14 -14
  118. package/lib/util/is.js +32 -32
  119. package/lib/util/make-knex.js +338 -338
  120. package/lib/util/nanoid.js +29 -29
  121. package/lib/util/noop.js +1 -1
  122. package/lib/util/parse-connection.js +66 -66
  123. package/lib/util/save-async-stack.js +14 -14
  124. package/lib/util/template.js +52 -52
  125. package/lib/util/timeout.js +29 -29
  126. package/lib/util/timestamp.js +16 -16
  127. package/package.json +1 -1
  128. package/scripts/build.js +125 -125
  129. package/scripts/docker-compose.yml +111 -111
  130. package/scripts/next-release-howto.md +24 -24
  131. package/scripts/release.sh +34 -34
  132. package/scripts/runkit-example.js +34 -34
  133. package/scripts/stress-test/README.txt +18 -18
  134. package/scripts/stress-test/docker-compose.yml +47 -47
  135. package/scripts/stress-test/knex-stress-test.js +196 -196
  136. package/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js +145 -145
  137. package/scripts/stress-test/mysql2-sudden-exit-without-error.js +100 -100
  138. package/scripts/stress-test/reconnect-test-mysql-based-drivers.js +184 -184
  139. package/types/index.d.ts +2249 -2249
  140. package/types/result.d.ts +27 -27
  141. package/types/tables.d.ts +4 -4
@@ -1,611 +1,611 @@
1
- // Migrator
2
- // -------
3
- const differenceWith = require('lodash/differenceWith');
4
- const get = require('lodash/get');
5
- const isEmpty = require('lodash/isEmpty');
6
- const max = require('lodash/max');
7
- const { inherits } = require('util');
8
- const {
9
- getLockTableName,
10
- getTable,
11
- getTableName,
12
- } = require('./table-resolver');
13
- const { getSchemaBuilder } = require('./table-creator');
14
- const migrationListResolver = require('./migration-list-resolver');
15
- const MigrationGenerator = require('./MigrationGenerator');
16
- const { getMergedConfig } = require('./configuration-merger');
17
- const { isBoolean, isFunction } = require('../util/is');
18
-
19
- function LockError(msg) {
20
- this.name = 'MigrationLocked';
21
- this.message = msg;
22
- }
23
-
24
- inherits(LockError, Error);
25
-
26
- // The new migration we're performing, typically called from the `knex.migrate`
27
- // interface on the main `knex` object. Passes the `knex` instance performing
28
- // the migration.
29
- class Migrator {
30
- constructor(knex) {
31
- // Clone knex instance and remove post-processing that is unnecessary for internal queries from a cloned config
32
- if (isFunction(knex)) {
33
- if (!knex.isTransaction) {
34
- this.knex = knex.withUserParams({
35
- ...knex.userParams,
36
- });
37
- } else {
38
- this.knex = knex;
39
- }
40
- } else {
41
- this.knex = Object.assign({}, knex);
42
- this.knex.userParams = this.knex.userParams || {};
43
- }
44
-
45
- this.config = getMergedConfig(
46
- this.knex.client.config.migrations,
47
- undefined,
48
- this.knex.client.logger
49
- );
50
- this.generator = new MigrationGenerator(
51
- this.knex.client.config.migrations,
52
- this.knex.client.logger
53
- );
54
- this._activeMigration = {
55
- fileName: null,
56
- };
57
- }
58
-
59
- // Migrators to the latest configuration.
60
- async latest(config) {
61
- this._disableProcessing();
62
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
63
-
64
- const allAndCompleted = await migrationListResolver.listAllAndCompleted(
65
- this.config,
66
- this.knex
67
- );
68
-
69
- if (!this.config.disableMigrationsListValidation) {
70
- validateMigrationList(this.config.migrationSource, allAndCompleted);
71
- }
72
-
73
- const [all, completed] = allAndCompleted;
74
-
75
- const migrations = getNewMigrations(
76
- this.config.migrationSource,
77
- all,
78
- completed
79
- );
80
-
81
- const transactionForAll =
82
- !this.config.disableTransactions &&
83
- !(
84
- await Promise.all(
85
- migrations.map(async (migration) => {
86
- const migrationContents = await this.config.migrationSource.getMigration(
87
- migration
88
- );
89
- return !this._useTransaction(migrationContents);
90
- })
91
- )
92
- ).some((isTransactionUsed) => isTransactionUsed);
93
-
94
- if (transactionForAll) {
95
- return this.knex.transaction((trx) => {
96
- return this._runBatch(migrations, 'up', trx);
97
- });
98
- } else {
99
- return this._runBatch(migrations, 'up');
100
- }
101
- }
102
-
103
- // Runs the next migration that has not yet been run
104
- up(config) {
105
- this._disableProcessing();
106
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
107
-
108
- return migrationListResolver
109
- .listAllAndCompleted(this.config, this.knex)
110
- .then((value) => {
111
- if (!this.config.disableMigrationsListValidation) {
112
- validateMigrationList(this.config.migrationSource, value);
113
- }
114
- return value;
115
- })
116
- .then(([all, completed]) => {
117
- const newMigrations = getNewMigrations(
118
- this.config.migrationSource,
119
- all,
120
- completed
121
- );
122
-
123
- let migrationToRun;
124
- const name = this.config.name;
125
- if (name) {
126
- if (!completed.includes(name)) {
127
- migrationToRun = newMigrations.find((migration) => {
128
- return (
129
- this.config.migrationSource.getMigrationName(migration) === name
130
- );
131
- });
132
- if (!migrationToRun) {
133
- throw new Error(`Migration "${name}" not found.`);
134
- }
135
- }
136
- } else {
137
- migrationToRun = newMigrations[0];
138
- }
139
-
140
- return {
141
- migrationToRun,
142
- useTransaction:
143
- !migrationToRun ||
144
- this._useTransaction(
145
- this.config.migrationSource.getMigration(migrationToRun)
146
- ),
147
- };
148
- })
149
- .then(({ migrationToRun, useTransaction }) => {
150
- const migrationsToRun = [];
151
- if (migrationToRun) {
152
- migrationsToRun.push(migrationToRun);
153
- }
154
-
155
- const transactionForAll =
156
- !this.config.disableTransactions &&
157
- (!migrationToRun || useTransaction);
158
-
159
- if (transactionForAll) {
160
- return this.knex.transaction((trx) => {
161
- return this._runBatch(migrationsToRun, 'up', trx);
162
- });
163
- } else {
164
- return this._runBatch(migrationsToRun, 'up');
165
- }
166
- });
167
- }
168
-
169
- // Rollback the last "batch", or all, of migrations that were run.
170
- rollback(config, all = false) {
171
- this._disableProcessing();
172
- return new Promise((resolve, reject) => {
173
- try {
174
- this.config = getMergedConfig(
175
- config,
176
- this.config,
177
- this.knex.client.logger
178
- );
179
- } catch (e) {
180
- reject(e);
181
- }
182
- migrationListResolver
183
- .listAllAndCompleted(this.config, this.knex)
184
- .then((value) => {
185
- if (!this.config.disableMigrationsListValidation) {
186
- validateMigrationList(this.config.migrationSource, value);
187
- }
188
- return value;
189
- })
190
- .then((val) => {
191
- const [allMigrations, completedMigrations] = val;
192
-
193
- return all
194
- ? allMigrations
195
- .filter((migration) => {
196
- return completedMigrations.includes(
197
- this.config.migrationSource.getMigrationName(migration)
198
- );
199
- })
200
- .reverse()
201
- : this._getLastBatch(val);
202
- })
203
- .then((migrations) => {
204
- return this._runBatch(migrations, 'down');
205
- })
206
- .then(resolve, reject);
207
- });
208
- }
209
-
210
- down(config) {
211
- this._disableProcessing();
212
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
213
-
214
- return migrationListResolver
215
- .listAllAndCompleted(this.config, this.knex)
216
- .then((value) => {
217
- if (!this.config.disableMigrationsListValidation) {
218
- validateMigrationList(this.config.migrationSource, value);
219
- }
220
- return value;
221
- })
222
- .then(([all, completed]) => {
223
- const completedMigrations = all.filter((migration) => {
224
- return completed.includes(
225
- this.config.migrationSource.getMigrationName(migration)
226
- );
227
- });
228
-
229
- let migrationToRun;
230
- const name = this.config.name;
231
- if (name) {
232
- migrationToRun = completedMigrations.find((migration) => {
233
- return (
234
- this.config.migrationSource.getMigrationName(migration) === name
235
- );
236
- });
237
- if (!migrationToRun) {
238
- throw new Error(`Migration "${name}" was not run.`);
239
- }
240
- } else {
241
- migrationToRun = completedMigrations[completedMigrations.length - 1];
242
- }
243
-
244
- const migrationsToRun = [];
245
- if (migrationToRun) {
246
- migrationsToRun.push(migrationToRun);
247
- }
248
-
249
- return this._runBatch(migrationsToRun, 'down');
250
- });
251
- }
252
-
253
- status(config) {
254
- this._disableProcessing();
255
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
256
-
257
- return Promise.all([
258
- getTable(this.knex, this.config.tableName, this.config.schemaName).select(
259
- '*'
260
- ),
261
- migrationListResolver.listAll(this.config.migrationSource),
262
- ]).then(([db, code]) => db.length - code.length);
263
- }
264
-
265
- // Retrieves and returns the current migration version we're on, as a promise.
266
- // If no migrations have been run yet, return "none".
267
- currentVersion(config) {
268
- this._disableProcessing();
269
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
270
-
271
- return migrationListResolver
272
- .listCompleted(this.config.tableName, this.config.schemaName, this.knex)
273
- .then((completed) => {
274
- const val = max(completed.map((value) => value.split('_')[0]));
275
- return val === undefined ? 'none' : val;
276
- });
277
- }
278
-
279
- // list all migrations
280
- async list(config) {
281
- this._disableProcessing();
282
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
283
-
284
- const [all, completed] = await migrationListResolver.listAllAndCompleted(
285
- this.config,
286
- this.knex
287
- );
288
-
289
- if (!this.config.disableMigrationsListValidation) {
290
- validateMigrationList(this.config.migrationSource, [all, completed]);
291
- }
292
-
293
- const newMigrations = getNewMigrations(
294
- this.config.migrationSource,
295
- all,
296
- completed
297
- );
298
- return [completed, newMigrations];
299
- }
300
-
301
- async forceFreeMigrationsLock(config) {
302
- this.config = getMergedConfig(config, this.config, this.knex.client.logger);
303
- const { schemaName, tableName } = this.config;
304
- const lockTableName = getLockTableName(tableName);
305
- const { knex } = this;
306
- const getLockTable = () => getTable(knex, lockTableName, schemaName);
307
- const tableExists = await getSchemaBuilder(knex, schemaName).hasTable(
308
- lockTableName
309
- );
310
- if (tableExists) {
311
- await getLockTable().del();
312
- await getLockTable().insert({
313
- is_locked: 0,
314
- });
315
- }
316
- }
317
-
318
- // Creates a new migration, with a given name.
319
- make(name, config) {
320
- return this.generator.make(name, config, this.knex.client.logger);
321
- }
322
-
323
- _disableProcessing() {
324
- if (this.knex.disableProcessing) {
325
- this.knex.disableProcessing();
326
- }
327
- }
328
-
329
- _lockMigrations(trx) {
330
- const tableName = getLockTableName(this.config.tableName);
331
- return getTable(this.knex, tableName, this.config.schemaName)
332
- .transacting(trx)
333
- .where('is_locked', '=', 0)
334
- .update({ is_locked: 1 })
335
- .then((rowCount) => {
336
- if (rowCount != 1) {
337
- throw new Error('Migration table is already locked');
338
- }
339
- });
340
- }
341
-
342
- _getLock(trx) {
343
- const transact = trx ? (fn) => fn(trx) : (fn) => this.knex.transaction(fn);
344
- return transact((trx) => {
345
- return this._lockMigrations(trx);
346
- }).catch((err) => {
347
- throw new LockError(err.message);
348
- });
349
- }
350
-
351
- _freeLock(trx = this.knex) {
352
- const tableName = getLockTableName(this.config.tableName);
353
- return getTable(trx, tableName, this.config.schemaName).update({
354
- is_locked: 0,
355
- });
356
- }
357
-
358
- // Run a batch of current migrations, in sequence.
359
- _runBatch(migrations, direction, trx) {
360
- return (
361
- this._getLock(trx)
362
- // When there is a wrapping transaction, some migrations
363
- // could have been done while waiting for the lock:
364
- .then(() =>
365
- trx
366
- ? migrationListResolver.listCompleted(
367
- this.config.tableName,
368
- this.config.schemaName,
369
- trx
370
- )
371
- : []
372
- )
373
- .then(
374
- (completed) =>
375
- (migrations = getNewMigrations(
376
- this.config.migrationSource,
377
- migrations,
378
- completed
379
- ))
380
- )
381
- .then(() =>
382
- Promise.all(
383
- migrations.map(this._validateMigrationStructure.bind(this))
384
- )
385
- )
386
- .then(() => this._latestBatchNumber(trx))
387
- .then((batchNo) => {
388
- if (direction === 'up') batchNo++;
389
- return batchNo;
390
- })
391
- .then((batchNo) => {
392
- return this._waterfallBatch(batchNo, migrations, direction, trx);
393
- })
394
- .then(async (res) => {
395
- await this._freeLock(trx);
396
- return res;
397
- })
398
- .catch(async (error) => {
399
- let cleanupReady = Promise.resolve();
400
-
401
- if (error instanceof LockError) {
402
- // If locking error do not free the lock.
403
- this.knex.client.logger.warn(
404
- `Can't take lock to run migrations: ${error.message}`
405
- );
406
- this.knex.client.logger.warn(
407
- 'If you are sure migrations are not running you can release the ' +
408
- "lock manually by running 'knex migrate:unlock'"
409
- );
410
- } else {
411
- if (this._activeMigration.fileName) {
412
- this.knex.client.logger.warn(
413
- `migration file "${this._activeMigration.fileName}" failed`
414
- );
415
- }
416
- this.knex.client.logger.warn(
417
- `migration failed with error: ${error.message}`
418
- );
419
- // If the error was not due to a locking issue, then remove the lock.
420
- cleanupReady = this._freeLock(trx);
421
- }
422
-
423
- try {
424
- await cleanupReady;
425
- // eslint-disable-next-line no-empty
426
- } catch (e) {}
427
- throw error;
428
- })
429
- );
430
- }
431
-
432
- // Validates some migrations by requiring and checking for an `up` and `down`
433
- // function.
434
- async _validateMigrationStructure(migration) {
435
- const migrationName = this.config.migrationSource.getMigrationName(
436
- migration
437
- );
438
- // maybe promise
439
- const migrationContent = await this.config.migrationSource.getMigration(
440
- migration
441
- );
442
- if (
443
- typeof migrationContent.up !== 'function' ||
444
- typeof migrationContent.down !== 'function'
445
- ) {
446
- throw new Error(
447
- `Invalid migration: ${migrationName} must have both an up and down function`
448
- );
449
- }
450
-
451
- return migration;
452
- }
453
-
454
- // Get the last batch of migrations, by name, ordered by insert id in reverse
455
- // order.
456
- async _getLastBatch([allMigrations]) {
457
- const { tableName, schemaName } = this.config;
458
- const migrationNames = await getTable(this.knex, tableName, schemaName)
459
- .where('batch', function (qb) {
460
- qb.max('batch').from(getTableName(tableName, schemaName));
461
- })
462
- .orderBy('id', 'desc');
463
-
464
- const lastBatchMigrations = migrationNames.map((migration) => {
465
- return allMigrations.find((entry) => {
466
- return (
467
- this.config.migrationSource.getMigrationName(entry) === migration.name
468
- );
469
- });
470
- });
471
- return Promise.all(lastBatchMigrations);
472
- }
473
-
474
- // Returns the latest batch number.
475
- _latestBatchNumber(trx = this.knex) {
476
- return trx
477
- .from(getTableName(this.config.tableName, this.config.schemaName))
478
- .max('batch as max_batch')
479
- .then((obj) => obj[0].max_batch || 0);
480
- }
481
-
482
- // If transaction config for a single migration is defined, use that.
483
- // Otherwise, rely on the common config. This allows enabling/disabling
484
- // transaction for a single migration at will, regardless of the common
485
- // config.
486
- _useTransaction(migrationContent, allTransactionsDisabled) {
487
- const singleTransactionValue = get(migrationContent, 'config.transaction');
488
-
489
- return isBoolean(singleTransactionValue)
490
- ? singleTransactionValue
491
- : !allTransactionsDisabled;
492
- }
493
-
494
- // Runs a batch of `migrations` in a specified `direction`, saving the
495
- // appropriate database information as the migrations are run.
496
- _waterfallBatch(batchNo, migrations, direction, trx) {
497
- const trxOrKnex = trx || this.knex;
498
- const { tableName, schemaName, disableTransactions } = this.config;
499
- let current = Promise.resolve();
500
- const log = [];
501
- migrations.forEach((migration) => {
502
- const name = this.config.migrationSource.getMigrationName(migration);
503
- this._activeMigration.fileName = name;
504
- const migrationContent = this.config.migrationSource.getMigration(
505
- migration
506
- );
507
-
508
- // We're going to run each of the migrations in the current "up".
509
- current = current
510
- .then(async () => await migrationContent) //maybe promise
511
- .then((migrationContent) => {
512
- this._activeMigration.fileName = name;
513
- if (
514
- !trx &&
515
- this._useTransaction(migrationContent, disableTransactions)
516
- ) {
517
- this.knex.enableProcessing();
518
- return this._transaction(
519
- this.knex,
520
- migrationContent,
521
- direction,
522
- name
523
- );
524
- }
525
-
526
- trxOrKnex.enableProcessing();
527
- return checkPromise(
528
- this.knex.client.logger,
529
- migrationContent[direction](trxOrKnex),
530
- name
531
- );
532
- })
533
- .then(() => {
534
- trxOrKnex.disableProcessing();
535
- this.knex.disableProcessing();
536
- log.push(name);
537
- if (direction === 'up') {
538
- return trxOrKnex.into(getTableName(tableName, schemaName)).insert({
539
- name,
540
- batch: batchNo,
541
- migration_time: new Date(),
542
- });
543
- }
544
- if (direction === 'down') {
545
- return trxOrKnex
546
- .from(getTableName(tableName, schemaName))
547
- .where({ name })
548
- .del();
549
- }
550
- });
551
- });
552
-
553
- return current.then(() => [batchNo, log]);
554
- }
555
-
556
- _transaction(knex, migrationContent, direction, name) {
557
- return knex.transaction((trx) => {
558
- return checkPromise(
559
- knex.client.logger,
560
- migrationContent[direction](trx),
561
- name,
562
- () => {
563
- trx.commit();
564
- }
565
- );
566
- });
567
- }
568
- }
569
-
570
- // Validates that migrations are present in the appropriate directories.
571
- function validateMigrationList(migrationSource, migrations) {
572
- const [all, completed] = migrations;
573
- const diff = getMissingMigrations(migrationSource, completed, all);
574
- if (!isEmpty(diff)) {
575
- throw new Error(
576
- `The migration directory is corrupt, the following files are missing: ${diff.join(
577
- ', '
578
- )}`
579
- );
580
- }
581
- }
582
-
583
- function getMissingMigrations(migrationSource, completed, all) {
584
- return differenceWith(completed, all, (completedMigration, allMigration) => {
585
- return (
586
- completedMigration === migrationSource.getMigrationName(allMigration)
587
- );
588
- });
589
- }
590
-
591
- function getNewMigrations(migrationSource, all, completed) {
592
- return differenceWith(all, completed, (allMigration, completedMigration) => {
593
- return (
594
- completedMigration === migrationSource.getMigrationName(allMigration)
595
- );
596
- });
597
- }
598
-
599
- function checkPromise(logger, migrationPromise, name, commitFn) {
600
- if (!migrationPromise || typeof migrationPromise.then !== 'function') {
601
- logger.warn(`migration ${name} did not return a promise`);
602
- if (commitFn) {
603
- commitFn();
604
- }
605
- }
606
- return migrationPromise;
607
- }
608
-
609
- module.exports = {
610
- Migrator,
611
- };
1
+ // Migrator
2
+ // -------
3
+ const differenceWith = require('lodash/differenceWith');
4
+ const get = require('lodash/get');
5
+ const isEmpty = require('lodash/isEmpty');
6
+ const max = require('lodash/max');
7
+ const { inherits } = require('util');
8
+ const {
9
+ getLockTableName,
10
+ getTable,
11
+ getTableName,
12
+ } = require('./table-resolver');
13
+ const { getSchemaBuilder } = require('./table-creator');
14
+ const migrationListResolver = require('./migration-list-resolver');
15
+ const MigrationGenerator = require('./MigrationGenerator');
16
+ const { getMergedConfig } = require('./configuration-merger');
17
+ const { isBoolean, isFunction } = require('../util/is');
18
+
19
+ function LockError(msg) {
20
+ this.name = 'MigrationLocked';
21
+ this.message = msg;
22
+ }
23
+
24
+ inherits(LockError, Error);
25
+
26
+ // The new migration we're performing, typically called from the `knex.migrate`
27
+ // interface on the main `knex` object. Passes the `knex` instance performing
28
+ // the migration.
29
+ class Migrator {
30
+ constructor(knex) {
31
+ // Clone knex instance and remove post-processing that is unnecessary for internal queries from a cloned config
32
+ if (isFunction(knex)) {
33
+ if (!knex.isTransaction) {
34
+ this.knex = knex.withUserParams({
35
+ ...knex.userParams,
36
+ });
37
+ } else {
38
+ this.knex = knex;
39
+ }
40
+ } else {
41
+ this.knex = Object.assign({}, knex);
42
+ this.knex.userParams = this.knex.userParams || {};
43
+ }
44
+
45
+ this.config = getMergedConfig(
46
+ this.knex.client.config.migrations,
47
+ undefined,
48
+ this.knex.client.logger
49
+ );
50
+ this.generator = new MigrationGenerator(
51
+ this.knex.client.config.migrations,
52
+ this.knex.client.logger
53
+ );
54
+ this._activeMigration = {
55
+ fileName: null,
56
+ };
57
+ }
58
+
59
+ // Migrators to the latest configuration.
60
+ async latest(config) {
61
+ this._disableProcessing();
62
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
63
+
64
+ const allAndCompleted = await migrationListResolver.listAllAndCompleted(
65
+ this.config,
66
+ this.knex
67
+ );
68
+
69
+ if (!this.config.disableMigrationsListValidation) {
70
+ validateMigrationList(this.config.migrationSource, allAndCompleted);
71
+ }
72
+
73
+ const [all, completed] = allAndCompleted;
74
+
75
+ const migrations = getNewMigrations(
76
+ this.config.migrationSource,
77
+ all,
78
+ completed
79
+ );
80
+
81
+ const transactionForAll =
82
+ !this.config.disableTransactions &&
83
+ !(
84
+ await Promise.all(
85
+ migrations.map(async (migration) => {
86
+ const migrationContents = await this.config.migrationSource.getMigration(
87
+ migration
88
+ );
89
+ return !this._useTransaction(migrationContents);
90
+ })
91
+ )
92
+ ).some((isTransactionUsed) => isTransactionUsed);
93
+
94
+ if (transactionForAll) {
95
+ return this.knex.transaction((trx) => {
96
+ return this._runBatch(migrations, 'up', trx);
97
+ });
98
+ } else {
99
+ return this._runBatch(migrations, 'up');
100
+ }
101
+ }
102
+
103
+ // Runs the next migration that has not yet been run
104
+ up(config) {
105
+ this._disableProcessing();
106
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
107
+
108
+ return migrationListResolver
109
+ .listAllAndCompleted(this.config, this.knex)
110
+ .then((value) => {
111
+ if (!this.config.disableMigrationsListValidation) {
112
+ validateMigrationList(this.config.migrationSource, value);
113
+ }
114
+ return value;
115
+ })
116
+ .then(([all, completed]) => {
117
+ const newMigrations = getNewMigrations(
118
+ this.config.migrationSource,
119
+ all,
120
+ completed
121
+ );
122
+
123
+ let migrationToRun;
124
+ const name = this.config.name;
125
+ if (name) {
126
+ if (!completed.includes(name)) {
127
+ migrationToRun = newMigrations.find((migration) => {
128
+ return (
129
+ this.config.migrationSource.getMigrationName(migration) === name
130
+ );
131
+ });
132
+ if (!migrationToRun) {
133
+ throw new Error(`Migration "${name}" not found.`);
134
+ }
135
+ }
136
+ } else {
137
+ migrationToRun = newMigrations[0];
138
+ }
139
+
140
+ return {
141
+ migrationToRun,
142
+ useTransaction:
143
+ !migrationToRun ||
144
+ this._useTransaction(
145
+ this.config.migrationSource.getMigration(migrationToRun)
146
+ ),
147
+ };
148
+ })
149
+ .then(({ migrationToRun, useTransaction }) => {
150
+ const migrationsToRun = [];
151
+ if (migrationToRun) {
152
+ migrationsToRun.push(migrationToRun);
153
+ }
154
+
155
+ const transactionForAll =
156
+ !this.config.disableTransactions &&
157
+ (!migrationToRun || useTransaction);
158
+
159
+ if (transactionForAll) {
160
+ return this.knex.transaction((trx) => {
161
+ return this._runBatch(migrationsToRun, 'up', trx);
162
+ });
163
+ } else {
164
+ return this._runBatch(migrationsToRun, 'up');
165
+ }
166
+ });
167
+ }
168
+
169
+ // Rollback the last "batch", or all, of migrations that were run.
170
+ rollback(config, all = false) {
171
+ this._disableProcessing();
172
+ return new Promise((resolve, reject) => {
173
+ try {
174
+ this.config = getMergedConfig(
175
+ config,
176
+ this.config,
177
+ this.knex.client.logger
178
+ );
179
+ } catch (e) {
180
+ reject(e);
181
+ }
182
+ migrationListResolver
183
+ .listAllAndCompleted(this.config, this.knex)
184
+ .then((value) => {
185
+ if (!this.config.disableMigrationsListValidation) {
186
+ validateMigrationList(this.config.migrationSource, value);
187
+ }
188
+ return value;
189
+ })
190
+ .then((val) => {
191
+ const [allMigrations, completedMigrations] = val;
192
+
193
+ return all
194
+ ? allMigrations
195
+ .filter((migration) => {
196
+ return completedMigrations.includes(
197
+ this.config.migrationSource.getMigrationName(migration)
198
+ );
199
+ })
200
+ .reverse()
201
+ : this._getLastBatch(val);
202
+ })
203
+ .then((migrations) => {
204
+ return this._runBatch(migrations, 'down');
205
+ })
206
+ .then(resolve, reject);
207
+ });
208
+ }
209
+
210
+ down(config) {
211
+ this._disableProcessing();
212
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
213
+
214
+ return migrationListResolver
215
+ .listAllAndCompleted(this.config, this.knex)
216
+ .then((value) => {
217
+ if (!this.config.disableMigrationsListValidation) {
218
+ validateMigrationList(this.config.migrationSource, value);
219
+ }
220
+ return value;
221
+ })
222
+ .then(([all, completed]) => {
223
+ const completedMigrations = all.filter((migration) => {
224
+ return completed.includes(
225
+ this.config.migrationSource.getMigrationName(migration)
226
+ );
227
+ });
228
+
229
+ let migrationToRun;
230
+ const name = this.config.name;
231
+ if (name) {
232
+ migrationToRun = completedMigrations.find((migration) => {
233
+ return (
234
+ this.config.migrationSource.getMigrationName(migration) === name
235
+ );
236
+ });
237
+ if (!migrationToRun) {
238
+ throw new Error(`Migration "${name}" was not run.`);
239
+ }
240
+ } else {
241
+ migrationToRun = completedMigrations[completedMigrations.length - 1];
242
+ }
243
+
244
+ const migrationsToRun = [];
245
+ if (migrationToRun) {
246
+ migrationsToRun.push(migrationToRun);
247
+ }
248
+
249
+ return this._runBatch(migrationsToRun, 'down');
250
+ });
251
+ }
252
+
253
+ status(config) {
254
+ this._disableProcessing();
255
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
256
+
257
+ return Promise.all([
258
+ getTable(this.knex, this.config.tableName, this.config.schemaName).select(
259
+ '*'
260
+ ),
261
+ migrationListResolver.listAll(this.config.migrationSource),
262
+ ]).then(([db, code]) => db.length - code.length);
263
+ }
264
+
265
+ // Retrieves and returns the current migration version we're on, as a promise.
266
+ // If no migrations have been run yet, return "none".
267
+ currentVersion(config) {
268
+ this._disableProcessing();
269
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
270
+
271
+ return migrationListResolver
272
+ .listCompleted(this.config.tableName, this.config.schemaName, this.knex)
273
+ .then((completed) => {
274
+ const val = max(completed.map((value) => value.split('_')[0]));
275
+ return val === undefined ? 'none' : val;
276
+ });
277
+ }
278
+
279
+ // list all migrations
280
+ async list(config) {
281
+ this._disableProcessing();
282
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
283
+
284
+ const [all, completed] = await migrationListResolver.listAllAndCompleted(
285
+ this.config,
286
+ this.knex
287
+ );
288
+
289
+ if (!this.config.disableMigrationsListValidation) {
290
+ validateMigrationList(this.config.migrationSource, [all, completed]);
291
+ }
292
+
293
+ const newMigrations = getNewMigrations(
294
+ this.config.migrationSource,
295
+ all,
296
+ completed
297
+ );
298
+ return [completed, newMigrations];
299
+ }
300
+
301
+ async forceFreeMigrationsLock(config) {
302
+ this.config = getMergedConfig(config, this.config, this.knex.client.logger);
303
+ const { schemaName, tableName } = this.config;
304
+ const lockTableName = getLockTableName(tableName);
305
+ const { knex } = this;
306
+ const getLockTable = () => getTable(knex, lockTableName, schemaName);
307
+ const tableExists = await getSchemaBuilder(knex, schemaName).hasTable(
308
+ lockTableName
309
+ );
310
+ if (tableExists) {
311
+ await getLockTable().del();
312
+ await getLockTable().insert({
313
+ is_locked: 0,
314
+ });
315
+ }
316
+ }
317
+
318
+ // Creates a new migration, with a given name.
319
+ make(name, config) {
320
+ return this.generator.make(name, config, this.knex.client.logger);
321
+ }
322
+
323
+ _disableProcessing() {
324
+ if (this.knex.disableProcessing) {
325
+ this.knex.disableProcessing();
326
+ }
327
+ }
328
+
329
+ _lockMigrations(trx) {
330
+ const tableName = getLockTableName(this.config.tableName);
331
+ return getTable(this.knex, tableName, this.config.schemaName)
332
+ .transacting(trx)
333
+ .where('is_locked', '=', 0)
334
+ .update({ is_locked: 1 })
335
+ .then((rowCount) => {
336
+ if (rowCount != 1) {
337
+ throw new Error('Migration table is already locked');
338
+ }
339
+ });
340
+ }
341
+
342
+ _getLock(trx) {
343
+ const transact = trx ? (fn) => fn(trx) : (fn) => this.knex.transaction(fn);
344
+ return transact((trx) => {
345
+ return this._lockMigrations(trx);
346
+ }).catch((err) => {
347
+ throw new LockError(err.message);
348
+ });
349
+ }
350
+
351
+ _freeLock(trx = this.knex) {
352
+ const tableName = getLockTableName(this.config.tableName);
353
+ return getTable(trx, tableName, this.config.schemaName).update({
354
+ is_locked: 0,
355
+ });
356
+ }
357
+
358
+ // Run a batch of current migrations, in sequence.
359
+ _runBatch(migrations, direction, trx) {
360
+ return (
361
+ this._getLock(trx)
362
+ // When there is a wrapping transaction, some migrations
363
+ // could have been done while waiting for the lock:
364
+ .then(() =>
365
+ trx
366
+ ? migrationListResolver.listCompleted(
367
+ this.config.tableName,
368
+ this.config.schemaName,
369
+ trx
370
+ )
371
+ : []
372
+ )
373
+ .then(
374
+ (completed) =>
375
+ (migrations = getNewMigrations(
376
+ this.config.migrationSource,
377
+ migrations,
378
+ completed
379
+ ))
380
+ )
381
+ .then(() =>
382
+ Promise.all(
383
+ migrations.map(this._validateMigrationStructure.bind(this))
384
+ )
385
+ )
386
+ .then(() => this._latestBatchNumber(trx))
387
+ .then((batchNo) => {
388
+ if (direction === 'up') batchNo++;
389
+ return batchNo;
390
+ })
391
+ .then((batchNo) => {
392
+ return this._waterfallBatch(batchNo, migrations, direction, trx);
393
+ })
394
+ .then(async (res) => {
395
+ await this._freeLock(trx);
396
+ return res;
397
+ })
398
+ .catch(async (error) => {
399
+ let cleanupReady = Promise.resolve();
400
+
401
+ if (error instanceof LockError) {
402
+ // If locking error do not free the lock.
403
+ this.knex.client.logger.warn(
404
+ `Can't take lock to run migrations: ${error.message}`
405
+ );
406
+ this.knex.client.logger.warn(
407
+ 'If you are sure migrations are not running you can release the ' +
408
+ "lock manually by running 'knex migrate:unlock'"
409
+ );
410
+ } else {
411
+ if (this._activeMigration.fileName) {
412
+ this.knex.client.logger.warn(
413
+ `migration file "${this._activeMigration.fileName}" failed`
414
+ );
415
+ }
416
+ this.knex.client.logger.warn(
417
+ `migration failed with error: ${error.message}`
418
+ );
419
+ // If the error was not due to a locking issue, then remove the lock.
420
+ cleanupReady = this._freeLock(trx);
421
+ }
422
+
423
+ try {
424
+ await cleanupReady;
425
+ // eslint-disable-next-line no-empty
426
+ } catch (e) {}
427
+ throw error;
428
+ })
429
+ );
430
+ }
431
+
432
+ // Validates some migrations by requiring and checking for an `up` and `down`
433
+ // function.
434
+ async _validateMigrationStructure(migration) {
435
+ const migrationName = this.config.migrationSource.getMigrationName(
436
+ migration
437
+ );
438
+ // maybe promise
439
+ const migrationContent = await this.config.migrationSource.getMigration(
440
+ migration
441
+ );
442
+ if (
443
+ typeof migrationContent.up !== 'function' ||
444
+ typeof migrationContent.down !== 'function'
445
+ ) {
446
+ throw new Error(
447
+ `Invalid migration: ${migrationName} must have both an up and down function`
448
+ );
449
+ }
450
+
451
+ return migration;
452
+ }
453
+
454
+ // Get the last batch of migrations, by name, ordered by insert id in reverse
455
+ // order.
456
+ async _getLastBatch([allMigrations]) {
457
+ const { tableName, schemaName } = this.config;
458
+ const migrationNames = await getTable(this.knex, tableName, schemaName)
459
+ .where('batch', function (qb) {
460
+ qb.max('batch').from(getTableName(tableName, schemaName));
461
+ })
462
+ .orderBy('id', 'desc');
463
+
464
+ const lastBatchMigrations = migrationNames.map((migration) => {
465
+ return allMigrations.find((entry) => {
466
+ return (
467
+ this.config.migrationSource.getMigrationName(entry) === migration.name
468
+ );
469
+ });
470
+ });
471
+ return Promise.all(lastBatchMigrations);
472
+ }
473
+
474
+ // Returns the latest batch number.
475
+ _latestBatchNumber(trx = this.knex) {
476
+ return trx
477
+ .from(getTableName(this.config.tableName, this.config.schemaName))
478
+ .max('batch as max_batch')
479
+ .then((obj) => obj[0].max_batch || 0);
480
+ }
481
+
482
+ // If transaction config for a single migration is defined, use that.
483
+ // Otherwise, rely on the common config. This allows enabling/disabling
484
+ // transaction for a single migration at will, regardless of the common
485
+ // config.
486
+ _useTransaction(migrationContent, allTransactionsDisabled) {
487
+ const singleTransactionValue = get(migrationContent, 'config.transaction');
488
+
489
+ return isBoolean(singleTransactionValue)
490
+ ? singleTransactionValue
491
+ : !allTransactionsDisabled;
492
+ }
493
+
494
+ // Runs a batch of `migrations` in a specified `direction`, saving the
495
+ // appropriate database information as the migrations are run.
496
+ _waterfallBatch(batchNo, migrations, direction, trx) {
497
+ const trxOrKnex = trx || this.knex;
498
+ const { tableName, schemaName, disableTransactions } = this.config;
499
+ let current = Promise.resolve();
500
+ const log = [];
501
+ migrations.forEach((migration) => {
502
+ const name = this.config.migrationSource.getMigrationName(migration);
503
+ this._activeMigration.fileName = name;
504
+ const migrationContent = this.config.migrationSource.getMigration(
505
+ migration
506
+ );
507
+
508
+ // We're going to run each of the migrations in the current "up".
509
+ current = current
510
+ .then(async () => await migrationContent) //maybe promise
511
+ .then((migrationContent) => {
512
+ this._activeMigration.fileName = name;
513
+ if (
514
+ !trx &&
515
+ this._useTransaction(migrationContent, disableTransactions)
516
+ ) {
517
+ this.knex.enableProcessing();
518
+ return this._transaction(
519
+ this.knex,
520
+ migrationContent,
521
+ direction,
522
+ name
523
+ );
524
+ }
525
+
526
+ trxOrKnex.enableProcessing();
527
+ return checkPromise(
528
+ this.knex.client.logger,
529
+ migrationContent[direction](trxOrKnex),
530
+ name
531
+ );
532
+ })
533
+ .then(() => {
534
+ trxOrKnex.disableProcessing();
535
+ this.knex.disableProcessing();
536
+ log.push(name);
537
+ if (direction === 'up') {
538
+ return trxOrKnex.into(getTableName(tableName, schemaName)).insert({
539
+ name,
540
+ batch: batchNo,
541
+ migration_time: new Date(),
542
+ });
543
+ }
544
+ if (direction === 'down') {
545
+ return trxOrKnex
546
+ .from(getTableName(tableName, schemaName))
547
+ .where({ name })
548
+ .del();
549
+ }
550
+ });
551
+ });
552
+
553
+ return current.then(() => [batchNo, log]);
554
+ }
555
+
556
+ _transaction(knex, migrationContent, direction, name) {
557
+ return knex.transaction((trx) => {
558
+ return checkPromise(
559
+ knex.client.logger,
560
+ migrationContent[direction](trx),
561
+ name,
562
+ () => {
563
+ trx.commit();
564
+ }
565
+ );
566
+ });
567
+ }
568
+ }
569
+
570
+ // Validates that migrations are present in the appropriate directories.
571
+ function validateMigrationList(migrationSource, migrations) {
572
+ const [all, completed] = migrations;
573
+ const diff = getMissingMigrations(migrationSource, completed, all);
574
+ if (!isEmpty(diff)) {
575
+ throw new Error(
576
+ `The migration directory is corrupt, the following files are missing: ${diff.join(
577
+ ', '
578
+ )}`
579
+ );
580
+ }
581
+ }
582
+
583
+ function getMissingMigrations(migrationSource, completed, all) {
584
+ return differenceWith(completed, all, (completedMigration, allMigration) => {
585
+ return (
586
+ completedMigration === migrationSource.getMigrationName(allMigration)
587
+ );
588
+ });
589
+ }
590
+
591
+ function getNewMigrations(migrationSource, all, completed) {
592
+ return differenceWith(all, completed, (allMigration, completedMigration) => {
593
+ return (
594
+ completedMigration === migrationSource.getMigrationName(allMigration)
595
+ );
596
+ });
597
+ }
598
+
599
+ function checkPromise(logger, migrationPromise, name, commitFn) {
600
+ if (!migrationPromise || typeof migrationPromise.then !== 'function') {
601
+ logger.warn(`migration ${name} did not return a promise`);
602
+ if (commitFn) {
603
+ commitFn();
604
+ }
605
+ }
606
+ return migrationPromise;
607
+ }
608
+
609
+ module.exports = {
610
+ Migrator,
611
+ };