Skip to content

Commit

Permalink
Add schema change to add timestamps to Sequelize meta table (#460)
Browse files Browse the repository at this point in the history
  • Loading branch information
Americas authored May 31, 2017
1 parent 2b2b380 commit a0f471c
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 87 deletions.
108 changes: 60 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,68 @@ The Sequelize Command Line Interface (CLI)

Install this globally and you'll have access to the `sequelize` command anywhere on your system.

```
npm install -g sequelize-cli
```bash
$ npm install -g sequelize-cli
```

or install it locally to your `node_modules` folder

```bash
npm install --save sequelize-cli
$ npm install --save sequelize-cli
```
## Global Install Usage

```
```bash
$ sequelize [--HARMONY-FLAGS]
```


```
Sequelize [Node: 2.5.0, CLI: 1.8.3, ORM: 2.1.3]
Sequelize [Node: 7.8.0, CLI: 2.7.0, ORM: 3.27.0]
Usage
sequelize [task]
sequelize [task][options...]
Available tasks
db:migrate Run pending migrations.
db:migrate:old_schema Update legacy migration table
db:migrate:undo Revert the last migration run.
db:migrate:undo:all Revert all migrations ran.
db:seed Run seeders.
db:seed:undo Deletes data from the database.
db:seed:undo:all Deletes data from the database.
help Display this help text. Aliases: h
init Initializes the project.
init:config Initializes the configuration.
init:migrations Initializes the migrations.
init:models Initializes the models.
init:seeders Initializes the seeders.
migration:create Generates a new migration file. Aliases: migration:generate
model:create Generates a model and its migration. Aliases: model:generate
seed:create Generates a new seed file. Aliases: seed:generate
version Prints the version number. Aliases: v
db:migrate Run pending migrations.
db:migrate:old_schema Update legacy migration table
db:migrate:schema:timestamps:add Update migration table to have timestamps
db:migrate:status List the status of all migrations
db:migrate:undo Reverts a migration.
db:migrate:undo:all Revert all migrations ran.
db:seed Run specified seeder.
db:seed:all Run every seeder.
db:seed:undo Deletes data from the database.
db:seed:undo:all Deletes data from the database.
help Display this help text. Aliases: h
init Initializes the project. [init:config, init:migrations, init:seeders, init:models]
init:config Initializes the configuration.
init:migrations Initializes the migrations.
init:models Initializes the models.
init:seeders Initializes the seeders.
migration:create Generates a new migration file. Aliases: migration:generate
model:create Generates a model and its migration. Aliases: model:generate
seed:create Generates a new seed file. Aliases: seed:generate
version Prints the version number. Aliases: v
Available manuals
help:db:migrate The documentation for "sequelize db:migrate".
help:db:migrate:old_schema The documentation for "sequelize db:migrate:old_schema".
help:db:migrate:undo The documentation for "sequelize db:migrate:undo".
help:db:migrate:undo:all The documentation for "sequelize db:migrate:undo:all".
help:db:seed The documentation for "sequelize db:seed".
help:db:seed:undo The documentation for "sequelize db:seed:undo".
help:db:seed:undo:all The documentation for "sequelize db:seed:undo:all".
help:init The documentation for "sequelize init".
help:init:config The documentation for "sequelize init:config".
help:init:migrations The documentation for "sequelize init:migrations".
help:init:models The documentation for "sequelize init:models".
help:init:seeders The documentation for "sequelize init:seeders".
help:migration:create The documentation for "sequelize migration:create".
help:model:create The documentation for "sequelize model:create".
help:seed:create The documentation for "sequelize seed:create".
help:version The documentation for "sequelize version".
help:db:migrate The documentation for "sequelize db:migrate".
help:db:migrate:old_schema The documentation for "sequelize db:migrate:old_schema".
help:db:migrate:schema:timestamps:add The documentation for "sequelize db:migrate:schema:timestamps:add".
help:db:migrate:status The documentation for "sequelize db:migrate:status".
help:db:migrate:undo The documentation for "sequelize db:migrate:undo".
help:db:migrate:undo:all The documentation for "sequelize db:migrate:undo:all".
help:db:seed The documentation for "sequelize db:seed".
help:db:seed:all The documentation for "sequelize db:seed:all".
help:db:seed:undo The documentation for "sequelize db:seed:undo".
help:db:seed:undo:all The documentation for "sequelize db:seed:undo:all".
help:init The documentation for "sequelize init".
help:init:config The documentation for "sequelize init:config".
help:init:migrations The documentation for "sequelize init:migrations".
help:init:models The documentation for "sequelize init:models".
help:init:seeders The documentation for "sequelize init:seeders".
help:migration:create The documentation for "sequelize migration:create".
help:model:create The documentation for "sequelize model:create".
help:seed:create The documentation for "sequelize seed:create".
help:version The documentation for "sequelize version".
```

## Local Install Usage
Expand All @@ -82,7 +86,7 @@ or a Node.JS script that exports a hash.

### Example for a Node.JS script

```javascript
```js
var path = require('path')

module.exports = {
Expand Down Expand Up @@ -158,7 +162,7 @@ environment variable called `DB_CONNECTION_STRING` which stores the value
`mysql://root:password@mysql_host.com/database_name`. In order to make the CLI
use it, you have to use declare it in your config file:

```
```json
{
"production": {
"use_env_variable": "DB_CONNECTION_STRING"
Expand All @@ -168,7 +172,7 @@ use it, you have to use declare it in your config file:

With v2.0.0 of the CLI you can also just directly access the environment variables inside the `config/config.js`:

```
```js
module.exports = {
"production": {
"hostname": process.env.DB_HOSTNAME
Expand All @@ -195,7 +199,7 @@ or the CLI will write to the file `sequelize-meta.json`. If you want to keep the
database, using `sequelize`, but want to use a different table, you can change the table name using
`migrationStorageTableName`.

```js
```json
{
"development": {
"username": "root",
Expand Down Expand Up @@ -228,7 +232,7 @@ you can specify the path of the file using `seederStoragePath` or the CLI will w
`sequelize-data.json`. If you want to keep the information in the database, using `sequelize`, you can
specify the table name using `seederStorageTableName`, or it will default to `SequelizeData`.

```js
```json
{
"development": {
"username": "root",
Expand All @@ -252,7 +256,7 @@ specify the table name using `seederStorageTableName`, or it will default to `Se
In order to pass options to the underlying database connectors, you can add the property `dialectOptions`
to your configuration like this:

```
```js
var fs = require('fs');

module.exports = {
Expand Down Expand Up @@ -280,11 +284,19 @@ when you run a migration while having the old schema. You can opt-in for auto mi
}
```

#### Timestamps

Since v2.8.0 the CLI supports a adding timestamps to the schema for saving the executed migrations. You can opt-in for timestamps by running the following command:

```bash
$ sequelize db:migrate:schema:timestamps:add
```

### The migration schema

The CLI uses [umzug](https://github.com/sequelize/umzug) and its migration schema. This means a migration has to look like this:

```javascript
```js
"use strict";

module.exports = {
Expand Down
162 changes: 123 additions & 39 deletions lib/tasks/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,53 +275,86 @@ module.exports = {
},

task: function () {
tryToMigrateFromOldSchema()
return getMigrator('migration')
.then(function (migrator) {
return tryToMigrateFromOldSchema(migrator)
.then(function (items) {
if (items) {
console.log('Successfully migrated ' + items.length + ' migrations.');
}

process.exit(0);
}, function (err) {
console.log(err.name);
console.error(err);
process.exit(1);
});
});
}
},

'db:migrate:schema:timestamps:add': {
descriptions: {
'short': 'Update migration table to have timestamps',
'long': [
'This command updates the structure of the SequelizeMeta table to include',
'"createdAt" and "updatedAt" columns. This is an optional definition and',
'not required to keep on working with sequelize-cli.',
'',
'Please note that the script will create a backup of your old table schema',
'table by renaming the original table to "<tableName>Backup".'
]
},

task: function () {
return getMigrator('migration')
.then(function (migrator) {
return addTimestampsToSchema(migrator)
.then(function (items) {
if (items) {
console.log('Successfully added timestamps to MetaTable.');
} else {
console.log('MetaTable already has timestamps.');
}

process.exit(0);
}, function (err) {
console.error(err);
process.exit(1);
});
});
}
}
};

function ensureCurrentMetaSchema (migrator) {
var sequelize = migrator.options.storageOptions.sequelize;
var queryInterface = migrator.options.storageOptions.sequelize.getQueryInterface();
var tableName = migrator.options.storageOptions.tableName;
var columnName = migrator.options.storageOptions.columnName;
var config = helpers.config.readConfig();

return sequelize.getQueryInterface()
.showAllTables()
.then(function (tables) {
if (tables.indexOf('SequelizeMeta') === -1) {
return;
}
return ensureMetaTable(queryInterface, tableName)
.then(function (table) {
var columns = Object.keys(table);

return sequelize.queryInterface
.describeTable('SequelizeMeta')
.then(function (table) {
var columns = Object.keys(table);
if ((columns.length === 1) && (columns[0] === columnName)) {
return;
} else if (columns.length === 3 && columns.indexOf('createdAt') >= 0) {
return;
} else {
if (!config.autoMigrateOldSchema) {
console.error(
'Database schema was not migrated. Please run ' +
'"sequelize db:migrate:old_schema" first.'
);
process.exit(1);
}

if ((columns.length === 1) && (columns[0] === columnName)) {
return;
} else {
if (!config.autoMigrateOldSchema) {
console.error(
'Database schema was not migrated. Please run ' +
'"sequelize db:migrate:old_schema" first.'
);
process.exit(1);
}

return tryToMigrateFromOldSchema();
}
});
});
return tryToMigrateFromOldSchema(migrator);
}
},
function () {
return;
});
}

function logMigrator (s) {
Expand All @@ -330,6 +363,18 @@ function logMigrator (s) {
}
}

function ensureMetaTable (queryInterface, tableName) {
return queryInterface.showAllTables()
.then(function (tableNames) {
if (tableNames.indexOf(tableName) === -1) {
throw new Error('No MetaTable table found.');
}
})
.then(function () {
return queryInterface.describeTable(tableName);
});
}

function getSequelizeInstance () {
var config = null;
var options = {};
Expand Down Expand Up @@ -433,19 +478,11 @@ function getMigrator (type) {
*
* @return {Promise}
*/
function tryToMigrateFromOldSchema () {
var sequelize = getSequelizeInstance();
function tryToMigrateFromOldSchema (migrator) {
var sequelize = migrator.options.storageOptions.sequelize;
var queryInterface = sequelize.getQueryInterface();

return queryInterface.showAllTables()
.then(function (tableNames) {
if (tableNames.indexOf('SequelizeMeta') === -1) {
throw new Error('No SequelizeMeta table found.');
}
})
.then(function () {
return queryInterface.describeTable('SequelizeMeta');
})
return ensureMetaTable(queryInterface, 'SequelizeMeta')
.then(function (table) {
if (JSON.stringify(Object.keys(table).sort()) === JSON.stringify(['id', 'from', 'to'])) {
return;
Expand Down Expand Up @@ -497,6 +534,53 @@ function tryToMigrateFromOldSchema () {
});
}

/**
* Add timestamps
*
* @return {Promise}
*/
function addTimestampsToSchema (migrator) {
var sequelize = migrator.options.storageOptions.sequelize;
var queryInterface = sequelize.getQueryInterface();
var tableName = migrator.options.storageOptions.tableName;

return ensureMetaTable(queryInterface, tableName)
.then(function (table) {
if (table.createdAt) {
return;
}

return ensureCurrentMetaSchema(migrator)
.then(function () {
return queryInterface.renameTable(tableName, tableName + 'Backup');
})
.then(function () {
var sql = queryInterface.QueryGenerator.selectQuery(tableName + 'Backup');

return helpers.generic.execQuery(sequelize, sql, { type: 'SELECT', raw: true });
})
.then(function (result) {
var SequelizeMeta = sequelize.define(tableName, {
name: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: false
}
}, {
tableName: tableName,
timestamps: true
});

return SequelizeMeta.sync()
.then(function () {
return SequelizeMeta.bulkCreate(result);
});
});
});
}

/**
* ensureSeeds - checks that the `--seed` option exists
*/
Expand Down
Loading

0 comments on commit a0f471c

Please sign in to comment.