Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Migrator): add step option #1013

Merged
merged 1 commit into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We encourage you to read the [contribution guide](https://github.com/adonisjs/.g
Easiest way to run tests is to launch the redis cluster using docker-compose and `docker-compose.yml` file.

```sh
docker-compose up
docker-compose up -d
npm run test
```

Expand Down
9 changes: 9 additions & 0 deletions commands/migration/rollback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ export default class Rollback extends MigrationsBase {
})
declare batch: number

/**
* Define custom step, instead of rolling back to the latest batch
*/
@flags.number({
description: 'The number of migrations to be reverted',
})
declare step: number

/**
* Display migrations result in one compact single-line output
*/
Expand All @@ -74,6 +82,7 @@ export default class Rollback extends MigrationsBase {
direction: 'down',
connectionName: this.connection,
batch: this.batch,
step: this.step,
dryRun: this.dryRun,
disableLocks: this.disableLocks,
})
Expand Down
12 changes: 9 additions & 3 deletions src/migration/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ export class MigrationRunner extends EventEmitter {
/**
* Migrate down (aka rollback)
*/
private async runDown(batch?: number) {
private async runDown(batch?: number, step?: number) {
if (this.isInProduction && this.migrationsConfig.disableRollbacksInProduction) {
throw new Error(
'Rollback in production environment is disabled. Check "config/database" file for options.'
Expand All @@ -481,6 +481,12 @@ export class MigrationRunner extends EventEmitter {
const existing = await this.getMigratedFilesTillBatch(batch)
const collected = await this.migrationSource.getMigrations()

if (step === undefined || step <= 0) {
step = 0
} else {
batch = (await this.getLatestBatch()) - 1
}

/**
* Finding schema files for migrations to rollback. We do not perform
* rollback when any of the files are missing
Expand All @@ -499,7 +505,7 @@ export class MigrationRunner extends EventEmitter {
}
})

const filesToMigrate = Object.keys(this.migratedFiles)
const filesToMigrate = Object.keys(this.migratedFiles).slice(-step)
for (let name of filesToMigrate) {
await this.executeMigration(this.migratedFiles[name].file)
}
Expand Down Expand Up @@ -583,7 +589,7 @@ export class MigrationRunner extends EventEmitter {
if (this.direction === 'up') {
await this.runUp()
} else if (this.options.direction === 'down') {
await this.runDown(this.options.batch)
await this.runDown(this.options.batch, this.options.step)
}
} catch (error) {
this.error = error
Expand Down
1 change: 1 addition & 0 deletions src/types/migrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type MigratorOptions =
| {
direction: 'down'
batch?: number
step?: number
connectionName?: string
dryRun?: boolean
disableLocks?: boolean
Expand Down
176 changes: 176 additions & 0 deletions test/migrations/migrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,182 @@ test.group('Migrator', (group) => {
])
})

test('rollback database using schema files to a given step', async ({ fs, assert, cleanup }) => {
const app = new AppFactory().create(fs.baseUrl, () => {})
await app.init()
const db = getDb()
cleanup(() => db.manager.closeAll())

await fs.create(
'database/migrations/0_users_v6.ts',
`
import { BaseSchema as Schema } from '../../../../src/schema/main.js'
export default class extends Schema {
public async up () {
this.schema.createTable('schema_users', (table) => {
table.increments()
})
}

public async down () {
this.schema.dropTable('schema_users')
}
}
`
)

await fs.create(
'database/migrations/1_accounts_v6.ts',
`
import { BaseSchema as Schema } from '../../../../src/schema/main.js'
export default class extends Schema {
public async up () {
this.schema.createTable('schema_accounts', (table) => {
table.increments()
})
}

public async down () {
this.schema.dropTable('schema_accounts')
}
}
`
)

const migrator = getMigrator(db, app, { direction: 'up', connectionName: 'primary' })
await migrator.run()

const migrator1 = getMigrator(db, app, {
direction: 'down',
step: 1,
connectionName: 'primary',
})
await migrator1.run()

const migrated = await db.connection().from('adonis_schema').select('*')
const hasUsersTable = await db.connection().schema.hasTable('schema_users')
const hasAccountsTable = await db.connection().schema.hasTable('schema_accounts')
const migratedFiles = Object.keys(migrator1.migratedFiles).map((file) => {
return {
status: migrator1.migratedFiles[file].status,
file: file,
queries: migrator1.migratedFiles[file].queries,
}
})

assert.lengthOf(migrated, 1)
assert.isFalse(hasUsersTable)
assert.isTrue(hasAccountsTable)
assert.deepEqual(migratedFiles, [
{
status: 'pending',
file: 'database/migrations/1_accounts_v6',
queries: [],
},
{
status: 'completed',
file: 'database/migrations/0_users_v6',
queries: [],
},
])
})

test('negative numbers specified by the step option must rollback all the migrated files to the current batch', async ({
fs,
assert,
cleanup,
}) => {
const app = new AppFactory().create(fs.baseUrl, () => {})
await app.init()
const db = getDb()
cleanup(() => db.manager.closeAll())

await fs.create(
'database/migrations/0_users_v6.ts',
`
import { BaseSchema as Schema } from '../../../../src/schema/main.js'
export default class extends Schema {
public async up () {
this.schema.createTable('schema_users', (table) => {
table.increments()
})
}

public async down () {
this.schema.dropTable('schema_users')
}
}
`
)

const migrator = getMigrator(db, app, { direction: 'up', connectionName: 'primary' })
await migrator.run()

await fs.create(
'database/migrations/1_accounts_v6.ts',
`
import { BaseSchema as Schema } from '../../../../src/schema/main.js'
export default class extends Schema {
public async up () {
this.schema.createTable('schema_accounts', (table) => {
table.increments()
})
}

public async down () {
this.schema.dropTable('schema_accounts')
}
}
`
)

await fs.create(
'database/migrations/2_roles_v6.ts',
`
import { BaseSchema as Schema } from '../../../../src/schema/main.js'
export default class extends Schema {
public async up () {
this.schema.createTable('schema_roles', (table) => {
table.increments()
})
}

public async down () {
this.schema.dropTable('schema_roles')
}
}
`
)

const migrator1 = getMigrator(db, app, { direction: 'up', connectionName: 'primary' })
await migrator.run()

const migrator2 = getMigrator(db, app, {
direction: 'down',
step: -1,
connectionName: 'primary',
})
await migrator2.run()

const migrated = await db.connection().from('adonis_schema').select('*')
const hasUsersTable = await db.connection().schema.hasTable('schema_users')
const hasAccountsTable = await db.connection().schema.hasTable('schema_accounts')
const hasRolesTable = await db.connection().schema.hasTable('schema_roles')
const migratedFiles = Object.keys(migrator1.migratedFiles).map((file) => {
return {
status: migrator2.migratedFiles[file].status,
file: file,
queries: migrator2.migratedFiles[file].queries,
}
})

assert.lengthOf(migrated, 0)
assert.isFalse(hasUsersTable)
assert.isFalse(hasAccountsTable)
assert.isFalse(hasRolesTable)
assert.deepEqual(migratedFiles, [])
})

test('rollback multiple times must be a noop', async ({ fs, assert, cleanup }) => {
const app = new AppFactory().create(fs.baseUrl, () => {})
await app.init()
Expand Down
Loading