Skip to content

Commit

Permalink
Config file and type shorthands (#91)
Browse files Browse the repository at this point in the history
* Config file and type shorthands

* Updated README
  • Loading branch information
dolezel authored Jun 20, 2017
1 parent d019f90 commit d384e65
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 52 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ You could also specify your database url by setting the environment variable `DA
DATABASE_URL=postgres://postgres@localhost/name node-pg-migrate
```

You can specify custom JSON file with config (format is same as for `db` entry of [config](https://www.npmjs.com/package/config) file)

If a .env file exists, it will be loaded using [dotenv](https://www.npmjs.com/package/dotenv) (if installed) when running the pg-migrate binary.

Depending on your project's setup, it may make sense to write some custom grunt tasks that set this env var and run your migration commands. More on that below.
Expand All @@ -65,6 +67,7 @@ Depending on your project's setup, it may make sense to write some custom grunt

You can adjust defaults by passing arguments to `pg-migrate`:

* `config-file` (`f`) - The file with migration JSON config (defaults to undefined)
* `schema` (`s`) - The schema on which migration will be run (defaults to `public`)
* `database-url-var` (`d`) - Name of env variable with database url string (defaults to `DATABASE_URL`)
* `migrations-dir` (`m`) - The directory containing your migration files (defaults to `migrations`)
Expand All @@ -76,11 +79,26 @@ You can adjust defaults by passing arguments to `pg-migrate`:

See all by running `pg-migrate --help`.

Most of configuration options can be also specified in `node-config` configuration file.
Most of configuration options can be also specified in [config](https://www.npmjs.com/package/config) file.

For SSL connection to DB you can set `PGSSLMODE` environment variable to value from [list](https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT-SSLMODE) other then `disable`.
e.g. `PGSSLMODE=require pg-migrate up` ([pg](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md#v260) will take it into account)

#### JSON Configuration

You can use [config](https://www.npmjs.com/package/config) or your own json file with configuration (`config-file` command line option).

Available options are:

* `migrations-dir`, `migrations-schema`, `migrations-table`, `check-order` - same as above

* either `url` or [`user`], [`password`], `host` (defaults to localhost), `port` (defaults to 5432), `name` - for connection details

* `type-shorthands` - for column type shorthands

You can specify custom types which will be expanded to column definition (e.g. for `module.exports = { "type-shorthands": { id: { type: 'uuid', primaryKey: true }, createdAt: { type: 'timestamp', notNull: true, default: new require('node-pg-migrate').PgLiteral('current_timestamp') } } }`
it will in `pgm.createTable('test', { id: 'id', createdAt: 'createdAt' });` produce SQL `CREATE TABLE "test" ("id" uuid PRIMARY KEY, "createdAt" timestamp DEFAULT current_timestamp NOT NULL);`).

### Locking

`pg-migrate` automatically checks if no other migration is running. To do so, it locks the migration table and enters comment there.
Expand Down
57 changes: 41 additions & 16 deletions bin/pg-migrate
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
'use strict';

const util = require('util');
const path = require('path');
const yargs = require('yargs');
const Migration = require('../dist/migration').default; // eslint-disable-line import/no-unresolved,import/extensions
const runner = require('../dist/runner'); // eslint-disable-line import/no-unresolved,import/extensions

const migrationRunner = runner.default;
const unlockRunner = runner.unlockRunner;

Expand All @@ -28,6 +30,7 @@ const migrationsTable = 'migrations-table';
const migrationsSchema = 'migrations-schema';
const checkOrder = 'check-order';
const configValue = 'config-value';
const configFile = 'config-file';

const argv = yargs
.usage('Usage: db-migrate [up|down|create|unlock] [migrationName] [options]')
Expand Down Expand Up @@ -87,12 +90,19 @@ const argv = yargs
type: 'boolean',
})

.option('config-value', {
.option(configValue, {
default: 'db',
describe: 'Name of config section with db options',
type: 'string',
})

.option('f', {
alias: configFile,
default: undefined,
describe: 'Name of config file with db options',
type: 'string',
})

.help()
.argv;

Expand All @@ -112,35 +122,49 @@ let SCHEMA = argv[schema];
let MIGRATIONS_SCHEMA = argv[migrationsSchema];
let MIGRATIONS_TABLE = argv[migrationsTable];
let CHECK_ORDER = argv[checkOrder];
let TYPE_SHORTHANDS = {};

function readJson(json) {
if (typeof json === 'object') {
SCHEMA = json[schema] || SCHEMA;
MIGRATIONS_DIR = json[migrationsDir] || MIGRATIONS_DIR;
MIGRATIONS_SCHEMA = json[migrationsSchema] || MIGRATIONS_SCHEMA;
MIGRATIONS_TABLE = json[migrationsTable] || MIGRATIONS_TABLE;
CHECK_ORDER = typeof json[checkOrder] !== 'undefined' ? json[checkOrder] : CHECK_ORDER;
TYPE_SHORTHANDS = json['type-shorthands'] || TYPE_SHORTHANDS;
if (json.url) {
DATABASE_URL = json.url;
} else if (json.host || json.port || json.name) {
const creds = `${json.user}${json.password ? `:${json.password}` : ''}`;
DATABASE_URL = `postgres://${creds ? `${creds}@` : ''}${json.host || 'localhost'}:${json.port || 5432}/${json.name}`;
}
} else {
DATABASE_URL = json || DATABASE_URL;
}
}

try {
// Load config (and suppress the no-config-warning)
const oldSuppressWarning = process.env.SUPPRESS_NO_CONFIG_WARNING;
process.env.SUPPRESS_NO_CONFIG_WARNING = 1;
const config = require('config'); // eslint-disable-line global-require,import/no-extraneous-dependencies
if (config[argv[configValue]]) {
const db = config[argv[configValue]];
if (typeof db === 'object') {
SCHEMA = db[schema] || SCHEMA;
MIGRATIONS_DIR = db[migrationsDir] || MIGRATIONS_DIR;
MIGRATIONS_SCHEMA = db[migrationsSchema] || MIGRATIONS_SCHEMA;
MIGRATIONS_TABLE = db[migrationsTable] || MIGRATIONS_TABLE;
CHECK_ORDER = typeof db[checkOrder] !== 'undefined' ? db[checkOrder] : CHECK_ORDER;
if (db.url) {
DATABASE_URL = db.url;
} else if (db.host || db.port || db.name) {
const creds = `${db.user}${db.password ? `:${db.password}` : ''}`;
DATABASE_URL = `postgres://${creds ? `${creds}@` : ''}${db.host || 'localhost'}:${db.port || 5432}/${db.name}`;
}
} else {
DATABASE_URL = db;
}
readJson(db);
}
process.env.SUPPRESS_NO_CONFIG_WARNING = oldSuppressWarning;
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
}

const configFileName = argv[configFile];
if (configFileName) {
const config = require(path.resolve(configFileName)); // eslint-disable-line global-require,import/no-dynamic-require
readJson(config);
}

const action = argv._.shift();

if (action === 'create') {
Expand Down Expand Up @@ -210,6 +234,7 @@ if (action === 'create') {
count: num_migrations,
file: migration_name,
checkOrder: CHECK_ORDER,
typeShorthands: TYPE_SHORTHANDS,
})
.then(() => {
console.log('Migrations complete!');
Expand Down
14 changes: 5 additions & 9 deletions lib/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import pg from 'pg';
// or native libpq bindings
// import pg from 'pg/native';

export default (connection_string) => {
export default (connection_string, log = console.error) => {
const client = new pg.Client(connection_string);
let client_active = false;
const beforeCloseListeners = [];
Expand All @@ -17,7 +17,7 @@ export default (connection_string) => {
? resolve()
: client.connect((err) => {
if (err) {
console.error('could not connect to postgres', err);
log('could not connect to postgres', err);
reject(err);
} else {
client_active = true;
Expand Down Expand Up @@ -46,14 +46,14 @@ export default (connection_string) => {
const stringEnd = string.substr(endLineWrapPos);
const startLineWrapPos = stringStart.lastIndexOf('\n') + 1;
const padding = ' '.repeat(position - startLineWrapPos - 1);
console.error(`Error executing:
log(`Error executing:
${stringStart}
${padding}^^^^${stringEnd}
${message}
`);
} else {
console.error(`Error executing:
log(`Error executing:
${string}
${err}
`);
Expand Down Expand Up @@ -82,11 +82,7 @@ ${err}
promise
.then(listener)
.catch(err =>
console.err(
err.stack
? err.stack
: err
)
log(err.stack || err)
),
Promise.resolve()
)
Expand Down
7 changes: 3 additions & 4 deletions lib/migration-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import * as tables from './operations/tables';
import * as other from './operations/other';

export default class MigrationBuilder {

constructor() {
constructor(options = {}) {
this._steps = [];
this._REVERSE_MODE = false;
// by default, all migrations are wrapped in a transaction
Expand All @@ -46,11 +45,11 @@ export default class MigrationBuilder {
this.dropExtension = wrap(extensions.drop);
this.addExtension = this.createExtension;

this.createTable = wrap(tables.create);
this.createTable = wrap(tables.create(options.typeShorthands));
this.dropTable = wrap(tables.drop);
this.renameTable = wrap(tables.renameTable);

this.addColumns = wrap(tables.addColumns);
this.addColumns = wrap(tables.addColumns(options.typeShorthands));
this.dropColumns = wrap(tables.dropColumns);
this.renameColumn = wrap(tables.renameColumn);
this.alterColumn = wrap(tables.alterColumn);
Expand Down
5 changes: 2 additions & 3 deletions lib/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import MigrationBuilder from './migration-builder';
import { getMigrationTableSchema } from './utils';

class Migration {

// class method that creates a new migration file by cloning the migration template
static create(name, directory) {
// ensure the migrations directory exists
Expand Down Expand Up @@ -89,13 +88,13 @@ class Migration {
}

applyUp() {
const pgm = new MigrationBuilder();
const pgm = new MigrationBuilder(this.options);

return this._apply(this.up, pgm);
}

applyDown() {
const pgm = new MigrationBuilder();
const pgm = new MigrationBuilder(this.options);

if (this.down === false) {
return Promise.reject(`User has disabled down migration on file: ${this.name}`);
Expand Down
20 changes: 12 additions & 8 deletions lib/operations/tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ const type_adapters = {
bool: 'boolean',
};

const default_type_shorthands = {
id: { type: 'serial', primaryKey: true }, // convenience type for serial primary keys
};

// some convenience adapters -- see above
const applyTypeAdapters = type => (type_adapters[type] ? type_adapters[type] : type);

const quote = array => array.map(item => template`"${item}"`);

function parseColumns(columns, table_name) {
function parseColumns(columns, table_name, extending_type_shorthands = {}) {
const type_shorthands = { ...default_type_shorthands, ...extending_type_shorthands };
let columnsWithOptions = _.mapValues(columns, (options = {}) => {
if (typeof options === 'string') {
options = options === 'id' // eslint-disable-line no-param-reassign
// convenience type for serial primary keys
? { type: 'serial', primaryKey: true }
options = type_shorthands[options] // eslint-disable-line no-param-reassign
? type_shorthands[options]
: { type: options };
}

Expand Down Expand Up @@ -78,7 +82,7 @@ function parseColumns(columns, table_name) {
.join(',\n');
}

export const create = (table_name, columns, options = {}) => {
export const create = type_shorthands => (table_name, columns, options = {}) => {
/*
columns - hash of columns
Expand All @@ -87,16 +91,16 @@ export const create = (table_name, columns, options = {}) => {
columns - see column options
options.inherits - table to inherit from (optional)
*/
const columnsString = parseColumns(columns, table_name).replace(/^/gm, ' ');
const columnsString = parseColumns(columns, table_name, type_shorthands).replace(/^/gm, ' ');
const inherits = options.inherits ? ` INHERITS ${options.inherits}` : '';
return template`CREATE TABLE "${table_name}" (\n${columnsString}\n)${inherits};`;
};

export const drop = table_name =>
template`DROP TABLE "${table_name}";`;

export const addColumns = (table_name, columns) =>
template`ALTER TABLE "${table_name}"\n${parseColumns(columns, table_name).replace(/^/gm, ' ADD ')};`;
export const addColumns = type_shorthands => (table_name, columns) =>
template`ALTER TABLE "${table_name}"\n${parseColumns(columns, table_name, type_shorthands).replace(/^/gm, ' ADD ')};`;

export const dropColumns = (table_name, columns) => {
if (typeof columns === 'string') {
Expand Down
6 changes: 4 additions & 2 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Db from './db';
import Migration from './migration';
import { getMigrationTableSchema, finallyPromise } from './utils';

export { PgLiteral } from './utils';

const readdir = (...args) =>
new Promise((resolve, reject) =>
fs.readdir(
Expand Down Expand Up @@ -55,7 +57,7 @@ const lock = (db, options) => {
return null;
});

return db.query(`BEGIN`)
return db.query('BEGIN')
.then(() => db.query(`LOCK "${schema}"."${options.migrations_table}" IN ACCESS EXCLUSIVE MODE`))
.then(getCurrentLockName)
.then((currentLockName) => {
Expand All @@ -64,7 +66,7 @@ const lock = (db, options) => {
}
})
.then(() => db.query(`COMMENT ON TABLE "${schema}"."${options.migrations_table}" IS '${lockName}'`))
.then(() => db.query(`COMMIT`));
.then(() => db.query('COMMIT'));
};

const unlock = (db, options) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const finallyPromise = func => [
func,
(err) => {
const errHandler = (innerErr) => {
console.err(
console.error(
innerErr.stack
? innerErr.stack
: innerErr
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"bin": {
"pg-migrate": "bin/pg-migrate"
},
"main": "dist/runner.js",
"keywords": [
"db",
"database",
Expand Down Expand Up @@ -41,12 +42,12 @@
"babel-plugin-rewire": "^1.1.0",
"babel-preset-env": "^1.4.0",
"babel-preset-stage-3": "^6.24.0",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai": "^4.0.0",
"chai-as-promised": "^7.0.0",
"config": ">=1.0.0",
"cross-env": "^5.0.0",
"dotenv": ">=1.0.0",
"eslint": "^3.17.0",
"eslint": "^3.19.0",
"eslint-config-airbnb-base": "11.2.0",
"eslint-plugin-import": "^2.2.0",
"mocha": "^3.4.1",
Expand All @@ -66,6 +67,6 @@
"test": "cross-env NODE_ENV=test mocha --opts ./mocha.opts test",
"lint": "eslint -c .eslintrc . bin/pg-migrate",
"lintfix": "npm run lint -- --fix",
"prepublish": "npm run compile"
"prepare": "npm run compile"
}
}
Loading

0 comments on commit d384e65

Please sign in to comment.