178 lines
5.0 KiB
TypeScript
178 lines
5.0 KiB
TypeScript
import {Inject, Injectable} from '../../di'
|
|
import {Migrator} from './Migrator'
|
|
import {DatabaseService} from '../DatabaseService'
|
|
import {FieldType} from '../types'
|
|
import {Migration} from './Migration'
|
|
import {Builder} from '../builder/Builder'
|
|
|
|
/**
|
|
* Migrator implementation that tracks applied migrations in a database table.
|
|
* @todo allow configuring more of this
|
|
*/
|
|
@Injectable()
|
|
export class DatabaseMigrator extends Migrator {
|
|
@Inject()
|
|
protected readonly db!: DatabaseService
|
|
|
|
/** True if we've initialized the migrator. */
|
|
protected initialized = false
|
|
|
|
public async initialize(): Promise<void> {
|
|
await super.initialize()
|
|
|
|
if ( this.initialized ) {
|
|
return
|
|
}
|
|
|
|
const schema = this.db.get().schema()
|
|
if ( !(await schema.hasTable('migrations')) ) {
|
|
const table = await schema.table('migrations')
|
|
|
|
table.primaryKey('id', FieldType.serial).required()
|
|
|
|
table.column('identifier')
|
|
.type(FieldType.varchar)
|
|
.required()
|
|
|
|
table.column('applygroup')
|
|
.type(FieldType.integer)
|
|
.required()
|
|
|
|
table.column('applydate')
|
|
.type(FieldType.timestamp)
|
|
.required()
|
|
|
|
await schema.commit(table)
|
|
}
|
|
|
|
this.initialized = true
|
|
}
|
|
|
|
async has(migration: Migration): Promise<boolean> {
|
|
return this.builder()
|
|
.connection('default')
|
|
.select('id')
|
|
.from('migrations')
|
|
.where('identifier', '=', migration.identifier)
|
|
.exists()
|
|
}
|
|
|
|
async markApplied(migrations: Migration | Migration[], applyDate: Date = new Date()): Promise<void> {
|
|
if ( !Array.isArray(migrations) ) {
|
|
migrations = [migrations]
|
|
}
|
|
|
|
const applyGroup = await this.getNextGroupIdentifier()
|
|
const rows = migrations.map(migration => {
|
|
return {
|
|
applygroup: applyGroup,
|
|
applydate: applyDate,
|
|
identifier: migration.identifier,
|
|
}
|
|
})
|
|
|
|
await this.builder()
|
|
.connection('default')
|
|
.table('migrations')
|
|
.insert(rows)
|
|
}
|
|
|
|
async unmarkApplied(migrations: Migration | Migration[]): Promise<void> {
|
|
if ( !Array.isArray(migrations) ) {
|
|
migrations = [migrations]
|
|
}
|
|
|
|
const identifiers = migrations.map(migration => migration.identifier)
|
|
|
|
await this.builder()
|
|
.connection('default')
|
|
.table('migrations')
|
|
.whereIn('identifier', identifiers)
|
|
.delete()
|
|
}
|
|
|
|
async getLastApplyGroup(): Promise<string[]> {
|
|
const applyGroup = await this.builder()
|
|
.connection('default')
|
|
.select('applygroup')
|
|
.from('migrations')
|
|
.get()
|
|
.max<number>('applygroup')
|
|
|
|
return this.builder()
|
|
.connection('default')
|
|
.select('identifier')
|
|
.from('migrations')
|
|
.where('applygroup', '=', applyGroup)
|
|
.get()
|
|
.asyncPipe()
|
|
.tap(coll => {
|
|
return coll.pluck<string>('identifier')
|
|
})
|
|
.tap(coll => {
|
|
return coll.all()
|
|
})
|
|
.resolve()
|
|
}
|
|
|
|
/**
|
|
* Helper method to look up the next `applygroup` that should be used.
|
|
* @protected
|
|
*/
|
|
protected async getNextGroupIdentifier(): Promise<number> {
|
|
const current = await this.builder()
|
|
.connection('default')
|
|
.select('applygroup')
|
|
.from('migrations')
|
|
.get()
|
|
.max<number>('applygroup')
|
|
|
|
return (current ?? 0) + 1
|
|
}
|
|
|
|
/**
|
|
* Given a list of migration identifiers, filter out those that have been applied.
|
|
* @override to make this more efficient
|
|
* @param identifiers
|
|
* @protected
|
|
*/
|
|
protected async filterAppliedMigrations(identifiers: string[]): Promise<string[]> {
|
|
const existing = (await this.builder()
|
|
.connection('default')
|
|
.select('identifier')
|
|
.from('migrations')
|
|
.whereIn('identifier', identifiers)
|
|
.get()
|
|
.collect())
|
|
.pluck<string>('identifier')
|
|
|
|
return identifiers.filter(id => !existing.includes(id))
|
|
}
|
|
|
|
/**
|
|
* Given a list of migration identifiers, filter out those that have not been applied.
|
|
* @override to make this more efficient
|
|
* @param identifiers
|
|
* @protected
|
|
*/
|
|
protected async filterPendingMigrations(identifiers: string[]): Promise<string[]> {
|
|
const existing = await this.builder()
|
|
.connection('default')
|
|
.select('identifier')
|
|
.from('migrations')
|
|
.whereIn('identifier', identifiers)
|
|
.get()
|
|
.pluck<string>('identifier')
|
|
|
|
return existing.all()
|
|
}
|
|
|
|
/**
|
|
* Get a query builder instance.
|
|
* @protected
|
|
*/
|
|
protected builder(): Builder {
|
|
return this.injector.make<Builder>(Builder)
|
|
}
|
|
}
|