diff --git a/README.md b/README.md index 4436d18..62ccebb 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,13 @@ pgroll [global options] ### Global options -| Option | Description | -| --------------------------- | --------------------------------------------------------------- | -| `-d, --migrationDir ` | Directory holding the migration files (default `./migrations`). | -| `-u, --url ` | PostgreSQL connection URL (overrides `PG*` env vars). | -| `-V, --version` | Print the `pgroll` version. | -| `-h, --help` | Show help. | +| Option | Description | +| --------------------------- | --------------------------------------------------------------- | +| `-d, --migrationDir ` | Directory holding the migration files (default: `./migrations`). | +| `-u, --url ` | PostgreSQL connection URL (overrides `PG*` env vars). | +| `-s, --schema ` | Schema for pgroll's internal migrations table (default: public). | +| `-V, --version` | Print the `pgroll` version. | +| `-h, --help` | Show help. | ### Commands diff --git a/src/cli.ts b/src/cli.ts index 5bf6c4e..f6b0118 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -16,16 +16,17 @@ let migrator: IMigrator; program .version('0.0.9') .description('Database migration tool') - .option('-d, --migrationDir ', 'Specify migration directory(Default: ./migrations)') + .option('-d, --migrationDir ', 'Specify migration directory (Default: ./migrations)') .option('-u, --url ', 'PostgreSQL connection URL (overrides PG* env vars)') + .option('-s, --schema ', 'Specify schema (Default: public)') .hook('preAction', cmd => { - const opts = cmd.opts<{ migrationDir?: string; url?: string }>(); + const opts = cmd.opts<{ migrationDir?: string; url?: string; schema?: string }>(); const pgOptions = { onnotice: () => { // do nothing } }; - migrator = new Migrator(opts.url ? postgres(opts.url, pgOptions) : postgres(pgOptions), opts.migrationDir); + migrator = new Migrator(opts.url ? postgres(opts.url, pgOptions) : postgres(pgOptions), opts.migrationDir, opts.schema); }); program diff --git a/src/index.ts b/src/index.ts index e2658a5..c101688 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ interface Option { export interface IMigrator { migrationsDir: string; + schema: string; up: (opts?: Option) => Promise; down: (opts?: Option) => Promise; go: (version: number, opts?: Option) => Promise; @@ -19,14 +20,16 @@ export interface IMigrator { export class Migrator implements IMigrator { private readonly dbClient: Sql; readonly migrationsDir: string; + readonly schema: string; - constructor(dbClient: Sql, migrationsDir = '') { + constructor(dbClient: Sql, migrationsDir = '', schema = '') { this.dbClient = dbClient; this.migrationsDir = migrationsDir || `${process.cwd()}/migrations`; + this.schema = schema ?? "public"; } async ensureMigrationTable(tx: ReservedSql): Promise { - await tx`CREATE TABLE IF NOT EXISTS migrations( + await tx`CREATE TABLE IF NOT EXISTS ${this.schema}.migrations( name varchar(500) PRIMARY KEY, version smallint NOT NULL, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`; @@ -57,10 +60,8 @@ export class Migrator implements IMigrator { const fileVersion = Math.min(fileNames.length, version); for (let i = currentVersion; i < fileVersion; i++) { const file = fileNames[i] ?? ''; - await Promise.all([ - tx.file(path.join(this.migrationsDir, file)).execute(), - tx`INSERT INTO migrations(name, version) VALUES (${file}, ${i} + 1)` - ]); + await tx.file(path.join(this.migrationsDir, file)).execute(); + await tx`INSERT INTO ${this.schema}.migrations(name, version) VALUES (${file}, ${i} + 1)`; opts?.eventHandler(`Successfully migrated: ${file}`); } if (version > fileNames.length) { @@ -73,10 +74,8 @@ export class Migrator implements IMigrator { const end = start + (currentVersion - version); for (let i = start; i < end; i++) { const file = fileNames[i] ?? ''; - await Promise.all([ - tx.file(path.join(this.migrationsDir, file)).execute(), - tx`DELETE FROM migrations WHERE version = ${fileNames.length - i}` - ]); + await tx.file(path.join(this.migrationsDir, file)).execute(); + await tx`DELETE FROM ${this.schema}.migrations WHERE version = ${fileNames.length - i}`; opts?.eventHandler(`Successfully migrated: ${file}`); } } @@ -102,10 +101,8 @@ export class Migrator implements IMigrator { for (const fileName of fileNames) { const id = fileNames.indexOf(fileName); if (id >= currentVersion) { - await Promise.all([ - tx.file(path.join(this.migrationsDir, fileName)).execute(), - tx`INSERT INTO migrations(name, version) VALUES (${fileName}, ${id} + 1)` - ]); + await tx.file(path.join(this.migrationsDir, fileName)).execute(); + await tx`INSERT INTO ${this.schema}.migrations(name, version) VALUES (${fileName}, ${id} + 1)`; opts?.eventHandler(`Successfully migrated: ${fileName}`); } } @@ -114,10 +111,8 @@ export class Migrator implements IMigrator { const start = fileNames.length - currentVersion; for (let i = start; i < fileNames.length; i++) { const file = fileNames[i] ?? ''; - await Promise.all([ - tx.file(path.join(this.migrationsDir, file)).execute(), - tx`DELETE FROM migrations WHERE version = ${fileNames.length - i}` - ]); + await tx.file(path.join(this.migrationsDir, file)).execute(); + await tx`DELETE FROM ${this.schema}.migrations WHERE version = ${fileNames.length - i}`; opts?.eventHandler(`Successfully migrated: ${file}`); } } @@ -132,12 +127,12 @@ export class Migrator implements IMigrator { } async getCurrentVersion(): Promise { - const result = await this.dbClient`SELECT version FROM migrations ORDER BY version DESC LIMIT 1`; + const result = await this.dbClient`SELECT version FROM ${this.schema}.migrations ORDER BY version DESC LIMIT 1`; return result.length > 0 ? (result[0]?.['version'] as number) : 0; } async getCurrentVersionWithTx(tx: ReservedSql): Promise { - const result = await tx`SELECT version FROM migrations ORDER BY version DESC LIMIT 1`; + const result = await tx`SELECT version FROM ${this.schema}.migrations ORDER BY version DESC LIMIT 1`; return result.length > 0 ? (result[0]?.['version'] as number) : 0; }