Skip to content

Commit

Permalink
feat(migrate.js, bin/rethinkdb-migrate): Add option to and commands, …
Browse files Browse the repository at this point in the history
  • Loading branch information
gristow committed Aug 1, 2018
1 parent 2d0072f commit b29c540
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Operation | Command | Description
---|---|---
create | `rethinkdb-migrate create <migration-name>` | Creates a migration with the given name
up | `rethinkdb-migrate up` | Runs all un-executed migrations up
down | `rethinkdb-migrate down` | Runs all executed migrations down
down | `rethinkdb-migrate down` | Runs all executed migrations down (unless `step` option specified)

### Create

Expand Down Expand Up @@ -118,7 +118,19 @@ $ rethinkdb-migrate down --db=mydb
```

This command will run all `down` steps from migrations that have been run
previously. See [Options](#options) section to configure this task.
previously.

> *Caution: this refreshes the database to before the first migration
> (potentially deleting data added since). Be very cautious about running
> this command in a production environment.*
To rollback just a subset of migrations, use the `step` option:

```shell
$ rethinkdb-migrate down --step=2 --db=mydb
```

See [Options](#options) section to further configure this task.

### Options

Expand All @@ -139,6 +151,7 @@ discovery | `false` | Whether or not the driver should try to keep a list of upd
pool | `false` | Whether or not to use a connection pool when using `rethinkdbdash` driver.
cursor | `true` | If true, cursors will not be automatically converted to arrays when using `rethinkdbdash`.
servers | undefined | Array of `{ host, port }` of servers to connect to. Only available when using `rethinkdbdash`
step | none | Number of migrations to execute or rollback. If omitted, all migrations will be executed or rolled back, potentially refreshing the db to its initial state and resulting in data loss.
migrationsDirectory | `migrations` | Directory where migration files will be saved
relativeTo | `process.cwd()` | Root path from which migration directory will be searched or created (if inexistent)'
migrationsTable | `_migrations` | Table where meta information about migrations will be saved. This should only be changed if you need a \_migrations table in your application
Expand Down
9 changes: 7 additions & 2 deletions bin/rethinkdb-migrate
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,18 @@ require('yargs')
handler: runMigrations.bind(null, 'up')
})
.example('$0 up', 'Runs all new migrations up')
.example('$0 up --step=<number>', 'Run just this number of new migrations up')
.command({
command: 'down',
desc: 'Runs all down migrations',
desc: 'Rollback all previously executed migrations',
builder: internals.migrateOptions,
handler: runMigrations.bind(null, 'down')
})
.number('step')
.nargs('step', 1)
.describe('step', 'Number of migrations to run, if omitted run or rollback all migrations')
.example('$0 down', 'Runs all migrations down')
.example('$0 down --step=<number>', 'Rollback just this number of previously executed migration files')
.alias('f', 'file')
.nargs('f', 1)
.describe('f', 'Uses the provided file as the options to be used when migration is run')
Expand Down Expand Up @@ -115,7 +120,7 @@ function logger (emitter) {
* an option present in the config file and also a environment variable, and so on
*/
function buildOptions (argv) {
const optionsMask = 'name,migrationsDirectory,relativeTo,migrationsTable,host,port,db,user,username,password,authKey,driver,discovery,pool,cursor,servers,ssl,i,ignoreTimestamp'
const optionsMask = 'name,migrationsDirectory,relativeTo,migrationsTable,host,port,db,user,username,password,authKey,driver,discovery,pool,cursor,servers,ssl,i,ignoreTimestamp,step'
const envVars = Mask(process.env, optionsMask)
const file = Mask(readOptionsFile(argv), optionsMask)
const args = Mask(argv, optionsMask)
Expand Down
70 changes: 47 additions & 23 deletions lib/migrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ function validateOptions (options) {
const schema = Joi.object().keys({
op: Joi.string().valid('up', 'down').required()
.description('Migration command'),
step: Joi.number().min(1)
.description('Number of migrations to perform (migrations are counted as each migration file)'),
driver: Joi.string().valid('rethinkdb', 'rethinkdbdash').default('rethinkdb')
.description('Rethinkdb javascript driver'),
migrationsTable: Joi.string().default('_migrations')
Expand Down Expand Up @@ -163,25 +165,53 @@ function executeMigration (options) {
}

function migrateUp (options) {
let steps
return getLatestMigrationExecuted(options)
.then(latest => getUnExecutedMigrations(latest, options))
.then(newerMigrations => runMigrations('up', newerMigrations, options))
.then(emit('info', 'Saving metada'))
.then(newerMigrations => {
const migrationSteps = limitToSteps(newerMigrations, options)
steps = migrationSteps.length
return migrationSteps
})
.then(migrationSteps => runMigrations('up', migrationSteps, options))
.then(emit('info', 'Saving metadata'))
.then(executedMigrations => saveExecutedMigrationsMetadata(executedMigrations, options))
.then(() => {
const migrationMessage = steps
? `Executed ${steps} migration${steps > 1 ? 's' : ''}.`
: `No migrations executed.`
emit('info', migrationMessage)()
})
.then(() => options)
}

function migrateDown (options) {
let steps
return getExecutedMigrations(options)
.then(migrations => runMigrations('down', migrations, options))
.then(migrations => clearMigrationsTable(migrations, options))
.then(emit('info', 'Cleared migrations table'))
.then(migrations => loadMigrationsCode(migrations, options))
.then(migrations => {
const migrationSteps = limitToSteps(migrations, options)
steps = migrationSteps.length
return migrationSteps
})
.then(migrationSteps => runMigrations('down', migrationSteps, options))
.then(rolledBackMigrations => clearMigrationsTable(rolledBackMigrations, options))
.then(() => {
const migrationMessage = steps
? `Cleared ${steps} migration${steps > 1 ? 's' : ''} from table.`
: 'Migrations table already clear.'
emit('info', migrationMessage)()
})
.then(() => options)
}

function limitToSteps (migrations, options) {
return options.step ? migrations.slice(0, options.step) : migrations
}

function getLatestMigrationExecuted (options) {
return ensureMigrationsTable(options)
.then(() => getAllMigrationsExecuted(options))
.then(() => getExecutedMigrations(options))
.then(migrations => {
if (!migrations.length) {
return {
Expand All @@ -206,20 +236,6 @@ function ensureMigrationsTable (options) {
})
}

function getAllMigrationsExecuted (options) {
const { r, conn, migrationsTable } = options

return ensureMigrationsTable(options)
.then(() => r.table(migrationsTable)
.orderBy({ index: r.desc('timestamp') })
.run(conn)
.then(toArray)
)
.then(migrations => migrations.map(migration => Object.assign({}, migration, {
timestamp: Moment.utc(migration.timestamp)
})))
}

function getMigrationsFromPath (options) {
const { migrationsDirectory, relativeTo } = options
const path = Path.resolve(relativeTo, migrationsDirectory)
Expand All @@ -239,9 +255,17 @@ function getMigrationsFromPath (options) {
}

function getExecutedMigrations (options) {
return getMigrationsFromPath(options)
.then(migrations => sortMigrations(migrations, true))
.then(migrations => loadMigrationsCode(migrations, options))
const { r, conn, migrationsTable } = options

return ensureMigrationsTable(options)
.then(() => r.table(migrationsTable)
.orderBy({ index: r.desc('timestamp') })
.run(conn)
.then(toArray)
)
.then(migrations => migrations.map(migration => Object.assign({}, migration, {
timestamp: Moment.utc(migration.timestamp)
})))
}

function getUnExecutedMigrations (latestExecutedMigration, options) {
Expand Down

0 comments on commit b29c540

Please sign in to comment.