Skip to content

Commit

Permalink
feat: add migration:refresh, migration:reset, migration:fresh a…
Browse files Browse the repository at this point in the history
…nd `db:wipe` commands (#764)

* feat: added migration:reset command

* feat: added shouldCloseConnectionAfterMigrations in MigrationsBase

* feat(commands): added migration:refresh

* test(commands): added migration:refresh tests

* fix: failing test on windows cause of path backslashes

* chore: remove log

* fix: use del-cli instead of del

See : adonisjs/mrm-preset#25

* feat: add `dropAllTables` to QueryClient and TransactionClient

* test: add failling test for exception raised when using `dropAllTables` with no tables in database

* fix: `dropAllTables` should not raises exception when database is empty

* test(commands): add test for db:wipe

* feat(commands): add `db:wipe` command

* feat(commands): add `migration:fresh` command

* test(commands): add test for `migration:fresh`

* chore: update description of new commands `force` flags

* chore: update dryRun flags description of new commands
  • Loading branch information
Julien-R44 authored Feb 5, 2022
1 parent 19990dc commit 441d9b5
Show file tree
Hide file tree
Showing 15 changed files with 760 additions and 4 deletions.
5 changes: 5 additions & 0 deletions adonis-typings/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ declare module '@ioc:Adonis/Lucid/Database' {
*/
getAllTables(schemas?: string[]): Promise<string[]>

/**
* Drop all tables inside database
*/
dropAllTables(schemas?: string[]): Promise<void>

/**
* Same as `query()`, but also selects the table for the query. The `from` method
* doesn't allow defining the return type and one must use `query` to define
Expand Down
67 changes: 67 additions & 0 deletions commands/DbWipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { BaseCommand, flags } from '@adonisjs/core/build/standalone'

export default class DbWipe extends BaseCommand {
public static commandName = 'db:wipe'
public static description = 'Drop all tables in database'

/**
* Choose a custom pre-defined connection. Otherwise, we use the
* default connection
*/
@flags.string({ description: 'Define a custom database connection', alias: 'c' })
public connection: string

/**
* Force command execution in production
*/
@flags.boolean({ description: 'Explicitly force command to run in production' })
public force: boolean

public static settings = {
loadApp: true,
}

/**
* Execute command
*/
public async run(): Promise<void> {
const db = this.application.container.use('Adonis/Lucid/Database')
const connection = db.connection(this.connection || db.primaryConnectionName)
const continueWipe =
!this.application.inProduction || this.force || (await this.takeProductionConstent())

/**
* Prompt cancelled or rejected and hence do not continue
*/
if (!continueWipe) {
return
}

/**
* Ensure the define connection name does exists in the
* config file
*/
if (!connection) {
this.logger.error(
`${this.connection} is not a valid connection name. Double check config/database file`
)
return
}

await db.connection().dropAllTables()
this.logger.success('All tables have been dropped successfully')
}

/**
* Prompts to take consent for running migrations in production
*/
protected async takeProductionConstent(): Promise<boolean> {
const question = 'You are in production environment. Continue ?'
try {
const continueMigrations = await this.prompt.confirm(question)
return continueMigrations
} catch (error) {
return false
}
}
}
15 changes: 13 additions & 2 deletions commands/Migration/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import { getDDLMethod } from '../../src/utils'
* Base class to execute migrations and print logs
*/
export default abstract class MigrationsBase extends BaseCommand {
/**
* Whether to close the connection after migrations are run
* Useful if the Migrator have to be used several times in a row.
*/
public shouldCloseConnectionAfterMigrations: boolean = true

/**
* Not a valid message
*/
Expand Down Expand Up @@ -88,7 +94,10 @@ export default abstract class MigrationsBase extends BaseCommand {
*/
if (migrator.dryRun) {
await migrator.run()
await migrator.close()

if (this.shouldCloseConnectionAfterMigrations) {
await migrator.close()
}

Object.keys(migrator.migratedFiles).forEach((file) => {
this.prettyPrintSql(migrator.migratedFiles[file], connectionName)
Expand Down Expand Up @@ -135,7 +144,9 @@ export default abstract class MigrationsBase extends BaseCommand {
* Run and close db connection
*/
await migrator.run()
await migrator.close()
if (this.shouldCloseConnectionAfterMigrations) {
await migrator.close()
}

/**
* Log all pending files. This will happen, when one of the migration
Expand Down
123 changes: 123 additions & 0 deletions commands/Migration/Fresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { flags } from '@adonisjs/core/build/standalone'
import DbSeed from '../DbSeed'
import DbWipe from '../DbWipe'
import MigrationsBase from './Base'
import Run from './Run'

/**
* This command reset the database by rolling back to batch 0 and then
* re-run all migrations.
*/
export default class Refresh extends MigrationsBase {
public static commandName = 'migration:fresh'
public static description = 'Drop all tables and re-run all migrations.'

/**
* Custom connection for running migrations.
*/
@flags.string({ description: 'Define a custom database connection', alias: 'c' })
public connection: string

/**
* Force command execution in production
*/
@flags.boolean({ description: 'Explicitly force command to run in production' })
public force: boolean

/**
* Perform dry run
*/
@flags.boolean({ description: 'Only print SQL queries instead of executing them' })
public dryRun: boolean

/**
* Run seeders
*/
@flags.boolean({ description: 'Indicates if the seed task should run.' })
public seed: boolean

/**
* This command loads the application, since we need the runtime
* to find the migration directories for a given connection
*/
public static settings = {
loadApp: true,
}

/**
* Handle command
*/
public async run(): Promise<void> {
const db = this.application.container.use('Adonis/Lucid/Database')
this.connection = this.connection || db.primaryConnectionName

const continueMigrations =
!this.application.inProduction || this.force || (await this.takeProductionConstent())

/**
* Prompt cancelled or rejected and hence do not continue
*/
if (!continueMigrations) {
return
}

const connection = db.getRawConnection(this.connection)

/**
* Ensure the define connection name does exists in the
* config file
*/
if (!connection) {
this.printNotAValidConnection(this.connection)
this.exitCode = 1
return
}

await this.runDbWipe()
await this.runMigrationRun()

if (this.seed) {
await this.runSeeders()
}

/**
* Close the connection after the migrations since we gave
* the order to not close after previous Migrator operations
*/
db.manager.closeAll(true)
}

/**
* Run the db:wipe command
*/
public async runDbWipe(): Promise<void> {
const resetCmd = new DbWipe(this.application, this.kernel)
resetCmd.connection = this.connection
resetCmd.force = true

await resetCmd.run()
}

/**
* Run the migration:run command
*/
public async runMigrationRun(): Promise<void> {
const migrateRunCmd = new Run(this.application, this.kernel)
migrateRunCmd.connection = this.connection
migrateRunCmd.force = true
migrateRunCmd.dryRun = this.dryRun
migrateRunCmd.shouldCloseConnectionAfterMigrations = false

await migrateRunCmd.run()
}

/**
* Run the seeders
*/
public async runSeeders(): Promise<void> {
const seedCmd = new DbSeed(this.application, this.kernel)
seedCmd.connection = this.connection

await seedCmd.run()
}
}
125 changes: 125 additions & 0 deletions commands/Migration/Refresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { flags } from '@adonisjs/core/build/standalone'
import DbSeed from '../DbSeed'
import MigrationsBase from './Base'
import Reset from './Reset'
import Run from './Run'

/**
* This command reset the database by rolling back to batch 0 and then
* re-run all migrations.
*/
export default class Refresh extends MigrationsBase {
public static commandName = 'migration:refresh'
public static description = 'Reset and re-run all migrations.'

/**
* Custom connection for running migrations.
*/
@flags.string({ description: 'Define a custom database connection', alias: 'c' })
public connection: string

/**
* Force command execution in production
*/
@flags.boolean({ description: 'Explicitly force command to run in production' })
public force: boolean

/**
* Perform dry run
*/
@flags.boolean({ description: 'Only print SQL queries instead of executing them' })
public dryRun: boolean

/**
* Run seeders
*/
@flags.boolean({ description: 'Indicates if the seed task should run.' })
public seed: boolean

/**
* This command loads the application, since we need the runtime
* to find the migration directories for a given connection
*/
public static settings = {
loadApp: true,
}

/**
* Handle command
*/
public async run(): Promise<void> {
const db = this.application.container.use('Adonis/Lucid/Database')
this.connection = this.connection || db.primaryConnectionName

const continueMigrations =
!this.application.inProduction || this.force || (await this.takeProductionConstent())

/**
* Prompt cancelled or rejected and hence do not continue
*/
if (!continueMigrations) {
return
}

const connection = db.getRawConnection(this.connection)

/**
* Ensure the define connection name does exists in the
* config file
*/
if (!connection) {
this.printNotAValidConnection(this.connection)
this.exitCode = 1
return
}

await this.runDbReset()
await this.runMigrationRun()

if (this.seed) {
await this.runSeeders()
}

/**
* Close the connection after the migrations since we gave
* the order to not close after previous Migrator operations
*/
db.manager.closeAll(true)
}

/**
* Run the migration:reset command
*/
public async runDbReset(): Promise<void> {
const resetCmd = new Reset(this.application, this.kernel)
resetCmd.connection = this.connection
resetCmd.force = true
resetCmd.dryRun = this.dryRun
resetCmd.shouldCloseConnectionAfterMigrations = false

await resetCmd.run()
}

/**
* Run the migration:run command
*/
public async runMigrationRun(): Promise<void> {
const migrateRunCmd = new Run(this.application, this.kernel)
migrateRunCmd.connection = this.connection
migrateRunCmd.force = true
migrateRunCmd.dryRun = this.dryRun
migrateRunCmd.shouldCloseConnectionAfterMigrations = false

await migrateRunCmd.run()
}

/**
* Run the seeders
*/
public async runSeeders(): Promise<void> {
const seedCmd = new DbSeed(this.application, this.kernel)
seedCmd.connection = this.connection

await seedCmd.run()
}
}
Loading

0 comments on commit 441d9b5

Please sign in to comment.