migrator.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. const fs = require("fs")
  2. , moment = require("moment")
  3. var Utils = require(__dirname + "/utils")
  4. , Migration = require(__dirname + "/migration")
  5. , DataTypes = require(__dirname + "/data-types")
  6. module.exports = (function() {
  7. var Migrator = function(sequelize, options) {
  8. this.sequelize = sequelize
  9. this.options = Utils._.extend({
  10. path: __dirname + '/../migrations',
  11. from: null,
  12. to: null,
  13. logging: console.log,
  14. filesFilter: /\.js$/
  15. }, options || {})
  16. if (this.options.logging === true) {
  17. console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log')
  18. this.options.logging = console.log
  19. }
  20. if (this.options.logging == console.log) {
  21. // using just console.log will break in node < 0.6
  22. this.options.logging = function(s) { console.log(s) }
  23. }
  24. }
  25. Object.defineProperty(Migrator.prototype, "queryInterface", {
  26. get: function() {
  27. return this.sequelize.getQueryInterface()
  28. }
  29. })
  30. Migrator.prototype.migrate = function(options) {
  31. var self = this
  32. options = Utils._.extend({
  33. method: 'up'
  34. }, options || {})
  35. return new Utils.CustomEventEmitter(function(emitter) {
  36. self.getUndoneMigrations(function(err, migrations) {
  37. if (err) {
  38. emitter.emit('error', err)
  39. } else {
  40. var chainer = new Utils.QueryChainer()
  41. , from = migrations[0]
  42. if (options.method === 'down') {
  43. migrations.reverse()
  44. }
  45. if (migrations.length === 0) {
  46. self.options.logging("There are no pending migrations.")
  47. } else {
  48. self.options.logging("Running migrations...")
  49. }
  50. migrations.forEach(function(migration) {
  51. var migrationTime
  52. chainer.add(migration, 'execute', [options], {
  53. before: function(migration) {
  54. if (self.options.logging !== false) {
  55. self.options.logging(migration.filename)
  56. }
  57. migrationTime = process.hrtime()
  58. },
  59. after: function(migration) {
  60. migrationTime = process.hrtime(migrationTime)
  61. migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000));
  62. if (self.options.logging !== false) {
  63. self.options.logging('Completed in ' + migrationTime + 'ms')
  64. }
  65. },
  66. success: function(migration, callback) {
  67. if (options.method === 'down') {
  68. deleteUndoneMigration.call(self, from, migration, callback)
  69. } else {
  70. saveSuccessfulMigration.call(self, from, migration, callback)
  71. }
  72. }
  73. })
  74. })
  75. chainer
  76. .runSerially({ skipOnError: true })
  77. .success(function() { emitter.emit('success', null) })
  78. .error(function(err) { emitter.emit('error', err) })
  79. }
  80. })
  81. }).run()
  82. }
  83. Migrator.prototype.getUndoneMigrations = function(callback) {
  84. var self = this
  85. var filterFrom = function(migrations, from, callback, options) {
  86. var result = migrations.filter(function(migration) { return migration.isAfter(from, options) })
  87. callback && callback(null, result)
  88. }
  89. var filterTo = function(migrations, to, callback, options) {
  90. var result = migrations.filter(function(migration) { return migration.isBefore(to, options) })
  91. callback && callback(null, result)
  92. }
  93. var migrationFiles = fs.readdirSync(this.options.path).filter(function(file) {
  94. return self.options.filesFilter.test(file)
  95. })
  96. var migrations = migrationFiles.map(function(file) {
  97. return new Migration(self, self.options.path + '/' + file)
  98. })
  99. migrations = migrations.sort(function(a,b){
  100. return parseInt(a.filename.split('-')[0]) - parseInt(b.filename.split('-')[0])
  101. })
  102. if (this.options.from) {
  103. filterFrom(migrations, this.options.from, function(err, migrations) {
  104. if (self.options.to) {
  105. filterTo(migrations, self.options.to, callback)
  106. } else {
  107. callback && callback(null, migrations)
  108. }
  109. })
  110. } else {
  111. getLastMigrationIdFromDatabase.call(this).success(function(lastMigrationId) {
  112. if (lastMigrationId) {
  113. filterFrom(migrations, lastMigrationId, function(err, migrations) {
  114. if (self.options.to) {
  115. filterTo(migrations, self.options.to, callback)
  116. } else {
  117. callback && callback(null, migrations)
  118. }
  119. }, { withoutEqual: true })
  120. } else {
  121. if (self.options.to) {
  122. filterTo(migrations, self.options.to, callback)
  123. } else {
  124. callback && callback(null, migrations)
  125. }
  126. }
  127. }).error(function(err) {
  128. callback && callback(err, null)
  129. })
  130. }
  131. }
  132. Migrator.prototype.findOrCreateSequelizeMetaDAO = function(syncOptions) {
  133. var self = this
  134. return new Utils.CustomEventEmitter(function(emitter) {
  135. var storedDAO = self.sequelize.daoFactoryManager.getDAO('SequelizeMeta')
  136. , SequelizeMeta = storedDAO
  137. if (!storedDAO) {
  138. SequelizeMeta = self.sequelize.define('SequelizeMeta', {
  139. from: DataTypes.STRING,
  140. to: DataTypes.STRING
  141. }, {
  142. timestamps: false
  143. })
  144. }
  145. // force sync when model has newly created or if syncOptions are passed
  146. if (!storedDAO || syncOptions) {
  147. SequelizeMeta
  148. .sync(syncOptions || {})
  149. .success(function() { emitter.emit('success', SequelizeMeta) })
  150. .error(function(err) { emitter.emit('error', err) })
  151. } else {
  152. emitter.emit('success', SequelizeMeta)
  153. }
  154. }).run()
  155. }
  156. /**
  157. * Explicitly executes one or multiple migrations.
  158. *
  159. * @param filename {String|Array} Absolute filename(s) of the migrations script
  160. * @param options {Object} Can contain three functions, before, after and success, which are executed before
  161. * or after each migration respectively, with one parameter, the migration.
  162. */
  163. Migrator.prototype.exec = function(filename, options) {
  164. var self = this;
  165. return new Utils.CustomEventEmitter(function(emitter) {
  166. var chainer = new Utils.QueryChainer()
  167. var addMigration = function(filename) {
  168. self.options.logging('Adding migration script at ' + filename)
  169. var migration = new Migration(self, filename)
  170. chainer.add(migration, 'execute', [{ method: 'up' }], {
  171. before: function(migration) {
  172. if (options && Utils._.isFunction(options.before)) {
  173. options.before.call(self, migration);
  174. }
  175. },
  176. after: function(migration) {
  177. if (options && Utils._.isFunction(options.after)) {
  178. options.after.call(self, migration);
  179. }
  180. },
  181. success: function(migration, callback) {
  182. if (options && Utils._.isFunction(options.success)) {
  183. options.success.call(self, migration);
  184. }
  185. callback();
  186. }
  187. })
  188. }
  189. if (Utils._.isArray(filename)) {
  190. Utils._.each(filename, function(f) {
  191. addMigration(f);
  192. })
  193. } else {
  194. addMigration(filename);
  195. }
  196. chainer
  197. .runSerially({ skipOnError: true })
  198. .success(function() { emitter.emit('success', null) })
  199. .error(function(err) { emitter.emit('error', err) })
  200. }).run()
  201. }
  202. // private
  203. var getLastMigrationFromDatabase = Migrator.prototype.getLastMigrationFromDatabase = function() {
  204. var self = this
  205. return new Utils.CustomEventEmitter(function(emitter) {
  206. self
  207. .findOrCreateSequelizeMetaDAO()
  208. .success(function(SequelizeMeta) {
  209. SequelizeMeta
  210. .find({ order: 'id DESC' })
  211. .success(function(meta) {
  212. emitter.emit('success', meta ? meta : null)
  213. })
  214. .error(function(err) {
  215. emitter.emit('error', err)
  216. })
  217. })
  218. .error(function(err) {
  219. emitter.emit('error', err)
  220. })
  221. }).run()
  222. }
  223. var getLastMigrationIdFromDatabase = Migrator.prototype.getLastMigrationIdFromDatabase = function() {
  224. var self = this
  225. return new Utils.CustomEventEmitter(function(emitter) {
  226. getLastMigrationFromDatabase
  227. .call(self)
  228. .success(function(meta) {
  229. emitter.emit('success', meta ? meta.to : null)
  230. })
  231. .error(function(err) {
  232. emitter.emit('error', err)
  233. })
  234. }).run()
  235. }
  236. var getFormattedDateString = Migrator.prototype.getFormattedDateString = function(s) {
  237. var result = null
  238. try {
  239. result = s.match(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/).slice(1, 6).join('-')
  240. } catch(e) {
  241. throw new Error(s + ' is no valid migration timestamp format! Use YYYYMMDDHHmmss!')
  242. }
  243. return result
  244. }
  245. var stringToDate = Migrator.prototype.stringToDate = function(s) {
  246. return moment(getFormattedDateString(s), "YYYYMMDDHHmmss")
  247. }
  248. var saveSuccessfulMigration = Migrator.prototype.saveSuccessfulMigration = function(from, to, callback) {
  249. var self = this
  250. self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
  251. SequelizeMeta
  252. .create({ from: from.migrationId, to: to.migrationId })
  253. .success(callback)
  254. })
  255. }
  256. var deleteUndoneMigration = Migrator.prototype.deleteUndoneMigration = function(from, to, callback) {
  257. var self = this
  258. self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
  259. SequelizeMeta
  260. .find({ where: { from: from.migrationId.toString(), to: to.migrationId.toString() } })
  261. .success(function(meta) {
  262. meta.destroy().success(callback)
  263. })
  264. })
  265. }
  266. return Migrator
  267. })()