db-migrate 1.0.0-beta.16

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 (51) hide show
  1. package/CHANGELOG.md +487 -0
  2. package/LICENSE +22 -0
  3. package/README.md +100 -0
  4. package/api.js +463 -0
  5. package/bin/db-migrate +39 -0
  6. package/connect.js +198 -0
  7. package/get.js +67 -0
  8. package/index.js +85 -0
  9. package/lib/chain.js +157 -0
  10. package/lib/class.js +75 -0
  11. package/lib/commands/check.js +52 -0
  12. package/lib/commands/create-migration.js +252 -0
  13. package/lib/commands/db.js +63 -0
  14. package/lib/commands/down.js +45 -0
  15. package/lib/commands/dump.js +22 -0
  16. package/lib/commands/fn/plugin.js +45 -0
  17. package/lib/commands/generated.js +32 -0
  18. package/lib/commands/helper/assert.js +15 -0
  19. package/lib/commands/helper/load-config.js +26 -0
  20. package/lib/commands/helper/migration-hook.js +4 -0
  21. package/lib/commands/helper/register-events.js +19 -0
  22. package/lib/commands/index.js +9 -0
  23. package/lib/commands/on-complete.js +28 -0
  24. package/lib/commands/run.js +122 -0
  25. package/lib/commands/seed.js +48 -0
  26. package/lib/commands/set-default-argv.js +169 -0
  27. package/lib/commands/sync.js +52 -0
  28. package/lib/commands/undo-seed.js +54 -0
  29. package/lib/commands/up.js +52 -0
  30. package/lib/config.js +207 -0
  31. package/lib/driver/index.js +156 -0
  32. package/lib/driver/shadow.js +74 -0
  33. package/lib/executors/versioned/v1.js +48 -0
  34. package/lib/executors/versioned/v2.js +78 -0
  35. package/lib/file.js +190 -0
  36. package/lib/interface/migratorInterface.js +127 -0
  37. package/lib/interface/seederInterface.js +145 -0
  38. package/lib/learn.js +369 -0
  39. package/lib/methods/v2/conventions.js +47 -0
  40. package/lib/methods/v2/migrate.js +5 -0
  41. package/lib/methods/v2/statetravel.js +86 -0
  42. package/lib/methods/v2/translatestate.js +117 -0
  43. package/lib/relation.js +37 -0
  44. package/lib/state.js +154 -0
  45. package/lib/template.js +312 -0
  46. package/lib/templates/v2.js +12 -0
  47. package/lib/temputils.js +62 -0
  48. package/lib/walker.js +266 -0
  49. package/package.json +87 -0
  50. package/pnpm-lock.yaml +2574 -0
  51. package/te.js +70 -0
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const Promise = require('bluebird');
4
+ const State = require('../../state');
5
+ const log = require('db-migrate-shared').log;
6
+ const Learn = require('../../learn');
7
+ const Chain = require('../../chain');
8
+ const StateTravel = require('../../methods/v2/statetravel');
9
+ const Migrate = require('../../methods/v2/migrate');
10
+ const TranslateState = require('../../methods/v2/translatestate');
11
+ const AddConventions = require('../../methods/v2/conventions');
12
+
13
+ const execUnit = {
14
+ _extend: (context, type) => {
15
+ return {
16
+ atomic: function (actions) {
17
+ const action = actions[type];
18
+ const reverse = actions[type === 'up' ? 'up' : 'down'];
19
+
20
+ if (!action || !reverse) {
21
+ return Promise.reject(new Error('invalid operation'));
22
+ }
23
+
24
+ return action().catch(() => reverse());
25
+ }
26
+ };
27
+ },
28
+
29
+ up: async function (context, driver, file) {
30
+ const _file = file.get();
31
+ const chain = new Chain(context._driver, file, driver, context.internals);
32
+ if (!_file._meta.noDefaultColumn) {
33
+ chain.addChain(AddConventions);
34
+ }
35
+ chain.addChain(Learn);
36
+ chain.addChain(StateTravel);
37
+ chain.addChain(Migrate);
38
+
39
+ await State.startMigration(context._driver, file, context.internals);
40
+ // startMigration - needs secondary instance since we can not afford to
41
+ // loose state and the transaction start will include these for roll back
42
+ // we will disable them probably at all from DDL when the driver does not
43
+ // explicitly signal DDL transaction support (like crdb)
44
+ try {
45
+ await _file.migrate(chain, {
46
+ options: context.internals.safeOptions,
47
+ seedLink: context.seedLink,
48
+ dbm: context.internals.safeOptions.dbmigrate
49
+ });
50
+ } catch (err) {
51
+ context.internals.rollback = true;
52
+
53
+ // transfer last state
54
+ chain.transferInt();
55
+
56
+ log.error(
57
+ 'An error occured. No alternative failure strategy defined. Rolling back!',
58
+ err
59
+ );
60
+ await execUnit.down(context, driver, file);
61
+ throw err;
62
+ }
63
+ await Promise.promisify(context.writeMigrationRecord.bind(context))(file);
64
+ return State.endMigration(context._driver, file, context.internals);
65
+ // end migration, same as start migration
66
+ },
67
+
68
+ down: async function (context, driver, file) {
69
+ // start migration, see up comments
70
+ await State.startMigration(context._driver, file, context.internals);
71
+ await TranslateState(context._driver, file, driver, context.internals);
72
+ await State.endMigration(context._driver, file, context.internals);
73
+ return Promise.promisify(context.deleteMigrationRecord.bind(context))(file);
74
+ // end migration, see up comments
75
+ }
76
+ };
77
+
78
+ module.exports = execUnit;
package/lib/file.js ADDED
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const log = require('db-migrate-shared').log;
6
+ const inflection = require('inflection');
7
+ const Promise = require('bluebird');
8
+ const lpad = require('db-migrate-shared').util.lpad;
9
+
10
+ Promise.promisifyAll(fs);
11
+
12
+ const LOADER = {
13
+ seeder: 'allLoadedSeedsAsync',
14
+ migration: 'allLoadedMigrationsAsync'
15
+ };
16
+
17
+ function formatPath (dir, name) {
18
+ return path.join(dir, name);
19
+ }
20
+
21
+ function formatName (title, date) {
22
+ return formatDate(date) + '-' + formatTitle(title);
23
+ }
24
+
25
+ function formatDate (date) {
26
+ return [
27
+ date.getUTCFullYear(),
28
+ lpad(date.getUTCMonth() + 1, '0', 2),
29
+ lpad(date.getUTCDate(), '0', 2),
30
+ lpad(date.getUTCHours(), '0', 2),
31
+ lpad(date.getUTCMinutes(), '0', 2),
32
+ lpad(date.getUTCSeconds(), '0', 2)
33
+ ].join('');
34
+ }
35
+
36
+ function formatTitle (title) {
37
+ return inflection.dasherize(title);
38
+ }
39
+
40
+ function parseDate (name) {
41
+ const date = new Date();
42
+ const match = name.match(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})-[^.]+/);
43
+ date.setUTCFullYear(match[1]);
44
+ date.setUTCDate(match[3]);
45
+ date.setUTCMonth(match[2] - 1);
46
+ date.setUTCHours(match[4]);
47
+ date.setUTCMinutes(match[5]);
48
+ date.setUTCSeconds(match[6]);
49
+ return date;
50
+ }
51
+
52
+ function parseTitle (name) {
53
+ const match = name.match(/\d{14}-([^.]+)/);
54
+ return inflection.humanize(match[1], true);
55
+ }
56
+
57
+ const filesRegEx = /\.js$/;
58
+
59
+ const File = function () {
60
+ if (arguments.length >= 3) {
61
+ this.title = arguments[0];
62
+ this.date = arguments[2];
63
+ this.name = this.formatName(this.title, this.date);
64
+ this.path = this.formatPath(arguments[1], this.name);
65
+ this.internals = arguments[4];
66
+ } else if (arguments.length === 2) {
67
+ this.path = arguments[0];
68
+ this.name = this.parseName(this.path);
69
+ this.date = this.parseDate(this.name);
70
+ this.title = this.parseTitle(this.name);
71
+ this.internals = arguments[1];
72
+ }
73
+
74
+ if (this.internals) this.internals = this.internals.safeOptions;
75
+ };
76
+
77
+ File.prototype = {
78
+ parseName: function (path) {
79
+ const match = path.match(/(\d{14}-[^.]+)(?:\.*?)?/);
80
+ return match[1];
81
+ },
82
+
83
+ get: function () {
84
+ return this._required || (this._required = require(this.path));
85
+ },
86
+
87
+ write: function (data) {
88
+ return fs.writeFileAsync(this.path, data);
89
+ },
90
+
91
+ parseTitle: parseTitle,
92
+ parseDate: parseDate,
93
+ formatTitle: formatTitle,
94
+ formatPath: formatPath,
95
+ formatName: formatName
96
+ };
97
+
98
+ File.registerHook = function (Plugin, internals) {
99
+ const plugin = [].concat(Plugin.hook('file:hook:require') || []).concat(
100
+ Plugin.hook('migrator:migration:hook:require') || [] // backwards compatible
101
+ );
102
+
103
+ internals.parser = internals.parser || {
104
+ filesRegEx: filesRegEx,
105
+ extensions: 'js'
106
+ };
107
+
108
+ if (!plugin) {
109
+ return Promise.resolve(null);
110
+ }
111
+
112
+ return Promise.resolve(plugin)
113
+ .map(function (plugin) {
114
+ // Backwards compatibility and notice for everyone so they
115
+ // can bug the plugin maintainer
116
+ if (plugin['migrator:migration:hook:require']) {
117
+ log.warn(
118
+ `The plugin '${plugin.name}' is outdated! The hook` +
119
+ `migrator:migration:hook:require was deprecated!`
120
+ );
121
+ log.warn(
122
+ `Report the maintainer of '${plugin.name}' to patch the ` +
123
+ `hook instead with file:hook:require.`
124
+ );
125
+
126
+ if (plugin.maintainer && plugin.maintainer.repository) {
127
+ log.verbose(
128
+ `The repo of the ${plugin.name} plugin is ${plugin.maintainer.repository}!`
129
+ );
130
+ }
131
+ return plugin['migrator:migration:hook:require']();
132
+ }
133
+
134
+ return plugin['file:hook:require']();
135
+ })
136
+ .each(function (parser) {
137
+ if (parser && parser.extensions) {
138
+ internals.parser.extensions =
139
+ internals.parser.extensions + '|' + parser.extensions;
140
+ }
141
+ })
142
+ .then(function () {
143
+ internals.parser.filesRegEx = new RegExp(
144
+ '\\.(' + internals.parser.extensions + ')$'
145
+ );
146
+
147
+ return internals.parser;
148
+ });
149
+ };
150
+
151
+ File.loadFromFileystem = function (dir, prefix, internals) {
152
+ log.verbose(`[${prefix}] loading from dir`, dir);
153
+
154
+ return fs
155
+ .readdirAsync(dir)
156
+ .filter(function (files) {
157
+ return internals.parser.filesRegEx.test(files);
158
+ })
159
+ .then(function (files) {
160
+ return files.sort();
161
+ })
162
+ .map(function (file) {
163
+ return new File(path.join(dir, file), internals);
164
+ });
165
+ };
166
+
167
+ File.loadFromDatabase = function (dir, prefix, driver, internals) {
168
+ log.verbose(`[${prefix}] loading from database`);
169
+ return driver[LOADER[prefix]]()
170
+ .catch(function (err) {
171
+ if (internals.dryRun) {
172
+ return [];
173
+ } else {
174
+ return Promise.reject(err);
175
+ }
176
+ })
177
+ .filter(function (result) {
178
+ return (
179
+ result.name.substr(0, result.name.lastIndexOf('/')) ===
180
+ internals.matching
181
+ );
182
+ })
183
+ .map(function (result) {
184
+ return new File(path.join(dir, result.name), internals);
185
+ });
186
+ };
187
+
188
+ Promise.promisifyAll(fs);
189
+
190
+ module.exports = File;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The migrator interface provides the ability to handle all abilities which
3
+ * are needed to successfully modify the DD of your tables.
4
+ * This includes all DDL methods provided by SQL naturally.
5
+ *
6
+ * Considering the zero downtime deployment we are handling migrations like
7
+ * the following:
8
+ *
9
+ * Zero Downtime notes
10
+ *
11
+ * *) The very first step is always a full dump, for the sake of security
12
+ *
13
+ * - all operations that are deleting a currently existing column, e.g. renaming a column:
14
+ * - 1. duplicate the column
15
+ * - if renamed:
16
+ * - 1. create a trigger to update both columns
17
+ * - 2. disable/delete trigger after successful deployment
18
+ * - if deleted:
19
+ * - 1. apply steps from the renaming process
20
+ * - 2. create a backup of this column (+PK)/table and delete it afterwards (after the successful deployment)
21
+ * - all operations that modify content
22
+ * - 1. Retrieve data to be applied
23
+ * - 2. Extract PKs of to be updated rows
24
+ * - 3. Create backup of to be updated rows
25
+ * - 4. Apply changes
26
+ */
27
+
28
+ function dummy () {
29
+ arguments[arguments.length - 1]('not implemented');
30
+ }
31
+
32
+ function MigratorInterface () {}
33
+
34
+ MigratorInterface.prototype = {
35
+ renameCollection: dummy,
36
+
37
+ dropCollection: dummy,
38
+
39
+ createCollection: dummy,
40
+
41
+ checkDBMS: dummy,
42
+
43
+ createDatabase: dummy,
44
+
45
+ switchDatabase: dummy,
46
+
47
+ dropDatabase: dummy,
48
+
49
+ bindForeignKey: dummy,
50
+
51
+ createTable: dummy,
52
+
53
+ dropTable: dummy,
54
+
55
+ renameTable: dummy,
56
+
57
+ addColumn: dummy,
58
+
59
+ removeColumn: dummy,
60
+
61
+ renameColumn: dummy,
62
+
63
+ changeColumn: dummy,
64
+
65
+ addIndex: dummy,
66
+
67
+ removeIndex: dummy,
68
+
69
+ addForeignKey: dummy,
70
+
71
+ removeForeignKey: dummy,
72
+
73
+ createMigrationsTable: dummy,
74
+
75
+ createSeedsTable: dummy,
76
+
77
+ normalizeColumnSpec: dummy,
78
+
79
+ runSql: dummy,
80
+
81
+ createColumnDef: dummy,
82
+
83
+ mapDataType: dummy,
84
+
85
+ createColumnConstraint: dummy,
86
+
87
+ recurseCallbackArray: dummy,
88
+
89
+ allLoadedMigrations: dummy,
90
+
91
+ allLoadedSeeds: dummy,
92
+
93
+ addSeedRecord: dummy,
94
+
95
+ startMigration: dummy,
96
+
97
+ endMigration: dummy,
98
+
99
+ addMigrationRecord: dummy,
100
+
101
+ deleteMigration: dummy,
102
+
103
+ deleteSeed: dummy,
104
+
105
+ quoteDDLArr: dummy,
106
+
107
+ quoteArr: dummy,
108
+
109
+ escapeString: dummy,
110
+
111
+ escape: dummy,
112
+
113
+ escapeDDL: dummy,
114
+
115
+ all: dummy,
116
+
117
+ close: dummy,
118
+
119
+ insert: dummy
120
+ };
121
+
122
+ module.exports = MigratorInterface;
123
+ module.exports.deprecated = {};
124
+
125
+ module.exports.extending = {};
126
+
127
+ module.exports.exclude = {};
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The seeder interface provides the ability to handle all operations, that
3
+ * are not DDL specific and thus not a migration.
4
+ *
5
+ * These operations are currently, but not limited to:
6
+ *
7
+ * inserting data
8
+ * removing data
9
+ * searching data
10
+ * truncating whole tables
11
+ *
12
+ * This functionality is provided in two ways to the user. First there are
13
+ * traditional seeder. You can call them whenever you want, and how often
14
+ * you want. This might be handy when initializing a new development
15
+ * environment, but not if you want to use it for the production environment.
16
+ *
17
+ * The second seeder type, the version controlled seeders, short VC Seeds,
18
+ * is targeting the production environment and development environment.
19
+ * There is technically no big difference between them, except the following
20
+ * details:
21
+ *
22
+ * A VC Seed can be called from a migration, via seed.execute and seed.link. A
23
+ * normal seeder can not. Also A VC Seeder has a down and up function, like
24
+ * the way the migrations work, the static has a truncate function instead,
25
+ * which gets called before the seed function. This is because a seeder is
26
+ * mostly used to initialize something, a VC Seeder instead is used to
27
+ * populate changes made to the dataset and to have a clear separation of
28
+ * DDL and Data Maniupulations.
29
+ * And last but not least, a VC Seed can not be executed if it was already
30
+ * executed. You will need to roll back it first, it just acts pretty similar
31
+ * to migrations. A normal seed can be executed just as often as you want
32
+ * without the need to rollback your data.
33
+ *
34
+ * To note: If you rollback a migration, linked to a seeder, db-migrate will
35
+ * also rollback the seed. This is also a reason why you can't rollback a
36
+ * specific migration, you would going to break that much, you probably loose
37
+ * a bunch of valueable time.
38
+ */
39
+
40
+ var Promise = require('bluebird');
41
+
42
+ function insert (table, options, callback) {
43
+ var foreignLinkage = this.foreignLinkage[table];
44
+
45
+ for (var o = 0; o < options.length; ++o) {
46
+ var option = Object.keys(options[o]);
47
+
48
+ for (var i = 0; i < option.length; ++i) {
49
+ if (typeof options[option[i]] === 'object') {
50
+ if (options[option[i]].type === 'lookup') {
51
+ if (!options[option[i]].table) {
52
+ if (foreignLinkage[option[i]]) {
53
+ options[option[i]].table = foreignLinkage[option[i]].table;
54
+ options[option[i]].field = foreignLinkage[option[i]].field;
55
+ } else {
56
+ return Promise.reject(
57
+ new Error('missing foreign key linkage!')
58
+ ).nodeify(callback);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ return lookup(options)
67
+ .then(function () {
68
+ return this.driver.insert(options);
69
+ })
70
+ .catch(function (e) {
71
+ throw e;
72
+ })
73
+ .nodeify(callback);
74
+ }
75
+
76
+ function lookup (options) {
77
+ var lookups = [];
78
+ var i = 0;
79
+
80
+ for (var o = 0; o < options.length; ++o) {
81
+ var option = Object.keys(options[o]);
82
+
83
+ for (; i < option.length; ++i) {
84
+ if (typeof options[option] === 'object') {
85
+ if (options[option].type === 'lookup') {
86
+ lookups.push(
87
+ this.driver.lookup(options[option]).catch(function (err) {
88
+ throw err;
89
+ })
90
+ );
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ return Promise.settle(lookups);
97
+ }
98
+
99
+ function dummy () {
100
+ arguments[arguments.length - 1]('not implemented');
101
+ }
102
+
103
+ var SeederInterface = {
104
+ lookup: dummy,
105
+
106
+ insert: dummy,
107
+
108
+ delete: dummy,
109
+
110
+ runSql: dummy,
111
+
112
+ buildWhereClause: dummy,
113
+
114
+ quoteDDLArr: dummy,
115
+
116
+ quoteArr: dummy,
117
+
118
+ escapeString: dummy,
119
+
120
+ escape: dummy,
121
+
122
+ escapeDDL: dummy,
123
+
124
+ checkDBMS: dummy,
125
+
126
+ update: dummy,
127
+
128
+ truncate: dummy,
129
+
130
+ switchDatabase: dummy,
131
+
132
+ remove: dummy,
133
+
134
+ close: dummy
135
+ };
136
+
137
+ module.exports = SeederInterface;
138
+ module.exports.deprecated = {};
139
+ module.exports.extending = {
140
+ _l: function (field) {
141
+ return { type: 'lookup', field: field };
142
+ },
143
+ __Test__insert: insert,
144
+ __Test__lookup: lookup
145
+ };