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

Config file and type shorthands #91

Merged
merged 2 commits into from
Jun 20, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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) configuration file.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config configuration file?
---> I would omit the middle "configuration" word here.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is user and password in square brackets here?

should it read:
either url, or [user, password, host (...), port, name] - for connection details
?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means that user and password are optional (you can have user without password and if you do not specify user, logged in user is used)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha, OK :-)


* `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') } } }`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bracket before "e.g." is not closed here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can someone provide a real example of how to use this feature? I have been trying in different ways without any success.

Thank you

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);`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I would say "it will in ..." instead of just "will in".


### 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