node-pg-migrate
Advanced tools
Comparing version 0.0.12 to 0.0.13
/* | ||
This file just manages the database connection and provides a query method | ||
*/ | ||
This file just manages the database connection and provides a query method | ||
*/ | ||
var pg = require('pg'); | ||
//or native libpq bindings | ||
//var pg = require('pg').native | ||
// or native libpq bindings | ||
// var pg = require('pg').native | ||
@@ -12,28 +12,6 @@ var client; | ||
module.exports.init = function(connection_string){ | ||
client = new pg.Client(connection_string || process.env.DATABASE_URL); | ||
} | ||
function createConnection(callback) { | ||
if (client_active) return callback(); | ||
module.exports.query = function(query, callback){ | ||
createConnection(function(err){ | ||
if (err) return callback(err); | ||
// console.log('sql>> ' + query); | ||
client.query(query, function(err, result){ | ||
if (err) return callback(err); | ||
return callback(null, result); | ||
}); | ||
}); | ||
} | ||
module.exports.close = function(){ | ||
client.end(); | ||
} | ||
function createConnection(callback){ | ||
if (client_active) return callback(); | ||
client.connect(function(err){ | ||
client.connect(function(err) { | ||
if (err) { | ||
@@ -47,1 +25,27 @@ console.error('could not connect to postgres', err); | ||
} | ||
module.exports.init = function(connection_string) { | ||
if (!client) { | ||
client = new pg.Client(connection_string || process.env.DATABASE_URL); | ||
} | ||
}; | ||
module.exports.query = function(query, callback) { | ||
createConnection(function(connErr) { | ||
if (connErr) return callback(connErr); | ||
// console.log('sql>> ' + query); | ||
client.query(query, function(queryErr, result) { | ||
if (queryErr) return callback(queryErr); | ||
return callback(null, result); | ||
}); | ||
}); | ||
}; | ||
module.exports.close = function() { | ||
client_active = false; | ||
if (client) { | ||
client.end(); | ||
client = null; | ||
} | ||
}; |
/* | ||
The migration builder is used to actually create a migration from instructions | ||
The migration builder is used to actually create a migration from instructions | ||
A new instance of MigrationBuilder is instantiated and passed to the up or down block | ||
of each migration when it is being run. | ||
A new instance of MigrationBuilder is instantiated and passed to the up or down block | ||
of each migration when it is being run. | ||
It makes the methods available via the pgm variable and stores up the sql commands. | ||
This is what makes it possible to do this without making everything async | ||
and it makes inferrence of down migrations possible. | ||
It makes the methods available via the pgm variable and stores up the sql commands. | ||
This is what makes it possible to do this without making everything async | ||
and it makes inferrence of down migrations possible. | ||
*/ | ||
*/ | ||
var _ = require('lodash'); | ||
var utils = require('./utils'); | ||
@@ -21,6 +20,6 @@ | ||
tables: require('./operations/tables'), | ||
other: require('./operations/other') | ||
} | ||
other: require('./operations/other'), | ||
}; | ||
var MigrationBuilder = function(){ | ||
var MigrationBuilder = function() { | ||
var self = this; | ||
@@ -33,20 +32,16 @@ var steps = []; | ||
this.enableReverseMode = function(){ | ||
this.enableReverseMode = function() { | ||
REVERSE_MODE = true; | ||
} | ||
this.noTransaction = function(){ | ||
}; | ||
this.noTransaction = function() { | ||
self.use_transaction = false; | ||
} | ||
}; | ||
this.getSql = function(){ | ||
this.getSql = function() { | ||
return this.getSqlSteps().join('\n') + '\n'; | ||
}; | ||
this.getSqlSteps = function() { | ||
// in reverse mode, we flip the order of the statements | ||
if (REVERSE_MODE){ | ||
steps = steps.reverse(); | ||
} | ||
return steps.join("\n")+"\n"; | ||
} | ||
this.getSqlSteps = function(){ | ||
// in reverse mode, we flip the order of the statements | ||
return REVERSE_MODE ? steps.reverse() : steps; | ||
} | ||
return REVERSE_MODE ? steps.slice().reverse() : steps; | ||
}; | ||
@@ -56,13 +51,13 @@ // this function wraps each operation within a function that either | ||
// to the steps array | ||
function wrap(operation){ | ||
return function(){ | ||
function wrap(operation) { | ||
return function() { | ||
if (REVERSE_MODE) { | ||
if ( typeof operation.reverse !== 'function' ){ | ||
if (typeof operation.reverse !== 'function') { | ||
throw new Error('Impossible to automatically infer down migration'); | ||
} | ||
steps = steps.concat( operation.reverse.apply( self, arguments ) ); | ||
steps = steps.concat(operation.reverse.apply(self, arguments)); | ||
} else { | ||
steps = steps.concat( operation.apply( self, arguments ) ); | ||
steps = steps.concat(operation.apply(self, arguments)); | ||
} | ||
} | ||
}; | ||
} | ||
@@ -73,31 +68,31 @@ | ||
// there are some convenience aliases to make usage easier | ||
this.createExtension = wrap( ops.extensions.create ); | ||
this.dropExtension = wrap( ops.extensions.drop ); | ||
this.createExtension = wrap(ops.extensions.create); | ||
this.dropExtension = wrap(ops.extensions.drop); | ||
this.addExtension = this.createExtension; | ||
this.createTable = wrap( ops.tables.create ); | ||
this.dropTable = wrap( ops.tables.drop ); | ||
this.renameTable = wrap( ops.tables.renameTable ); | ||
this.createTable = wrap(ops.tables.create); | ||
this.dropTable = wrap(ops.tables.drop); | ||
this.renameTable = wrap(ops.tables.renameTable); | ||
this.addColumns = wrap( ops.tables.addColumns ); | ||
this.dropColumns = wrap( ops.tables.dropColumns ); | ||
this.renameColumn = wrap( ops.tables.renameColumn ); | ||
this.alterColumn = wrap( ops.tables.alterColumn ); | ||
this.addColumns = wrap(ops.tables.addColumns); | ||
this.dropColumns = wrap(ops.tables.dropColumns); | ||
this.renameColumn = wrap(ops.tables.renameColumn); | ||
this.alterColumn = wrap(ops.tables.alterColumn); | ||
this.addColumn = this.addColumns; | ||
this.dropColumn = this.dropColumns; | ||
this.addConstraint = wrap( ops.tables.addConstraint ); | ||
this.dropConstraint = wrap( ops.tables.dropConstraint ); | ||
this.addConstraint = wrap(ops.tables.addConstraint); | ||
this.dropConstraint = wrap(ops.tables.dropConstraint); | ||
this.createConstraint = this.addConstraint; | ||
this.createType = wrap( ops.tables.createType ); | ||
this.dropType = wrap( ops.tables.dropType ); | ||
this.alterType = wrap( ops.tables.alterType ); | ||
this.createType = wrap(ops.tables.createType); | ||
this.dropType = wrap(ops.tables.dropType); | ||
this.alterType = wrap(ops.tables.alterType); | ||
this.addType = this.createType; | ||
this.createIndex = wrap( ops.indexes.create ); | ||
this.dropIndex = wrap( ops.indexes.drop ); | ||
this.createIndex = wrap(ops.indexes.create); | ||
this.dropIndex = wrap(ops.indexes.drop); | ||
this.addIndex = this.createIndex; | ||
this.sql = wrap( ops.other.sql ); | ||
this.sql = wrap(ops.other.sql); | ||
@@ -109,4 +104,4 @@ | ||
this.func = utils.PgLiteral.create; | ||
} | ||
}; | ||
module.exports = MigrationBuilder; | ||
module.exports = MigrationBuilder; |
/* | ||
A new Migration is instantiated for each migration file. | ||
A new Migration is instantiated for each migration file. | ||
It is responsible for storing the name of the file and knowing how to execute | ||
the up and down migrations defined in the file. | ||
It is responsible for storing the name of the file and knowing how to execute | ||
the up and down migrations defined in the file. | ||
*/ | ||
*/ | ||
var fs = require('fs'); | ||
var fs = require('fs'); | ||
var mkdirp = require('mkdirp'); | ||
var path = require('path'); | ||
var util = require('util'); | ||
var async = require('async'); | ||
var _ = require('lodash'); | ||
var path = require('path'); | ||
var util = require('util'); | ||
var async = require('async'); | ||
var utils = require('./utils'); | ||
var utils = require('./utils'); | ||
var MigrationBuilder = require('./migration-builder'); | ||
var db = require('./db'); | ||
var Migration = function(path, actions, options){ | ||
var Migration = function(migrationPath, actions, options) { | ||
var self = this; | ||
this.path = path; | ||
this.name = path.split('/').pop().replace(/\.js$/, ''); | ||
actions = actions || {}; | ||
this.path = migrationPath; | ||
this.name = migrationPath.split('/').pop().replace(/\.js$/, ''); | ||
actions = actions || {}; // eslint-disable-line no-param-reassign | ||
this.up = actions.up; | ||
this.down = actions.down; | ||
self.applyUp = function(done){ | ||
self.applyUp = function(done) { | ||
var pgm = new MigrationBuilder(); | ||
function upComplete(){ | ||
console.log('### MIGRATION '+self.name+' (up) ###') | ||
function upComplete() { | ||
console.log('### MIGRATION ' + self.name + ' (up) ###'); | ||
var sql_steps = pgm.getSqlSteps(); | ||
sql_steps.push( utils.t("INSERT INTO "+options.migrations_table+" (name, run_on) VALUES ('{name}', NOW());", { name: self.name }) ); | ||
sql_steps.push(utils.t('INSERT INTO "{table}" (name, run_on) VALUES (\'{name}\', NOW());', { table: options.migrations_table, name: self.name })); | ||
if (pgm.use_transaction){ | ||
if (pgm.use_transaction) { | ||
// wrap in a transaction, combine into one sql statement | ||
@@ -43,9 +42,9 @@ sql_steps.unshift('BEGIN;'); | ||
} else { | ||
console.log('#> WARNING: This migration is not wrapped in a transaction! <') | ||
console.log('#> WARNING: This migration is not wrapped in a transaction! <'); | ||
} | ||
console.log( sql_steps.join("\n")+"\n\n" ); | ||
console.log(sql_steps.join('\n') + '\n\n'); | ||
if (!global.dryRun) { | ||
async.eachSeries(sql_steps, function(sql, next_step){ | ||
async.eachSeries(sql_steps, function(sql, next_step) { | ||
db.query(sql, next_step); | ||
@@ -58,15 +57,15 @@ }, done); | ||
if (self.up.length == 2) { | ||
if (self.up.length === 2) { | ||
self.up(pgm, upComplete); | ||
} else { | ||
self.up(pgm); | ||
upComplete() | ||
upComplete(); | ||
} | ||
} | ||
self.applyDown = function(done){ | ||
}; | ||
self.applyDown = function(done) { | ||
var pgm = new MigrationBuilder(); | ||
if (self.down === false){ | ||
return done('User has disabled down migration on file: '+self.name); | ||
} else if (self.down === undefined){ | ||
if (self.down === false) { | ||
return done('User has disabled down migration on file: ' + self.name); | ||
} else if (self.down === undefined) { | ||
// automatically infer the down migration by running the up migration in reverse mode... | ||
@@ -77,10 +76,9 @@ pgm.enableReverseMode(); | ||
function downComplete(){ | ||
function downComplete() { | ||
console.log('### MIGRATION ' + self.name + ' (down) ###'); | ||
console.log('### MIGRATION '+self.name+' (down) ###') | ||
var sql_steps = pgm.getSqlSteps(); | ||
sql_steps.push( utils.t("DELETE FROM "+options.migrations_table+" WHERE name='{name}';", { name: self.name }) ); | ||
sql_steps.push(utils.t('DELETE FROM "{table}" WHERE name=\'{name}\';', { table: options.migrations_table, name: self.name })); | ||
if (pgm.use_transaction){ | ||
if (pgm.use_transaction) { | ||
// wrap in a transaction, combine into one sql statement | ||
@@ -90,9 +88,9 @@ sql_steps.unshift('BEGIN;'); | ||
} else { | ||
console.log('#> WARNING: This migration is not wrapped in a transaction! <') | ||
console.log('#> WARNING: This migration is not wrapped in a transaction! <'); | ||
} | ||
console.log( sql_steps.join("\n")+"\n\n" ); | ||
console.log(sql_steps.join('\n') + '\n\n'); | ||
if (!global.dryRun) { | ||
async.eachSeries(sql_steps, function(sql, next_step){ | ||
async.eachSeries(sql_steps, function(sql, next_step) { | ||
db.query(sql, next_step); | ||
@@ -105,14 +103,13 @@ }, done); | ||
if (self.down.length == 2) { | ||
if (self.down.length === 2) { | ||
self.down(pgm, downComplete); | ||
} else { | ||
self.down(pgm); | ||
downComplete() | ||
downComplete(); | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
// class method that creates a new migration file by cloning the migration template | ||
Migration.create = function(name, directory){ | ||
Migration.create = function(name, directory) { | ||
// ensure the migrations directory exists | ||
@@ -125,7 +122,7 @@ mkdirp.sync(directory); | ||
// copy the default migration template to the new file location | ||
fs.createReadStream( path.resolve(__dirname, './migration-template.js')) | ||
.pipe(fs.createWriteStream( new_file )); | ||
fs.createReadStream(path.resolve(__dirname, './migration-template.js')) | ||
.pipe(fs.createWriteStream(new_file)); | ||
return new Migration( new_file ); | ||
} | ||
return new Migration(new_file); | ||
}; | ||
@@ -132,0 +129,0 @@ module.exports = Migration; |
@@ -5,7 +5,7 @@ var utils = require('../utils'); | ||
var ops = module.exports = { | ||
create: function( extensions ) { | ||
if ( !_.isArray(extensions) ) extensions = [extensions]; | ||
return _.map(extensions, function(extension){ | ||
return utils.t('CREATE EXTENSION \"{extension}\";', { | ||
extension: extension | ||
create: function(extensions) { | ||
if (!_.isArray(extensions)) extensions = [ extensions ]; // eslint-disable-line no-param-reassign | ||
return _.map(extensions, function(extension) { | ||
return utils.t('CREATE EXTENSION "{extension}";', { | ||
extension: extension, | ||
}); | ||
@@ -15,13 +15,13 @@ }); | ||
drop: function( extensions ) { | ||
if ( !_.isArray(extensions) ) extensions = [extensions]; | ||
return _.map(extensions, function(extension){ | ||
return utils.t('DROP EXTENSION \"{extension}\";', { | ||
extension: extension | ||
drop: function(extensions) { | ||
if (!_.isArray(extensions)) extensions = [ extensions ]; // eslint-disable-line no-param-reassign | ||
return _.map(extensions, function(extension) { | ||
return utils.t('DROP EXTENSION "{extension}";', { | ||
extension: extension, | ||
}); | ||
}); | ||
}, | ||
} | ||
}; | ||
// setup reverse functions | ||
ops.create.reverse = ops.drop; |
var utils = require('../utils'); | ||
var _ = require('lodash'); | ||
function generateIndexName(table_name, columns, options) { | ||
if (options.name) return options.name; | ||
var name = table_name; | ||
if (_.isArray(columns)) name += '_' + columns.join('_'); | ||
else name += '_' + columns; | ||
if (options.unique) name += '_unique'; | ||
name += '_index'; | ||
return name; | ||
// if (options.unique) | ||
} | ||
function generateColumnsString(columns) { | ||
if (_.isArray(columns)) return columns.map(function(name) { | ||
return '"' + name + '"'; | ||
}).join(', '); | ||
if (/.+\(.*\)/.test(columns)) return columns; // expression | ||
return '"' + columns + '"'; // single column | ||
} | ||
var ops = module.exports = { | ||
create: function( table_name, columns, options ) { | ||
create: function(table_name, columns, options) { | ||
/* | ||
columns - the column, columns, or expression to create the index on | ||
columns - the column, columns, or expression to create the index on | ||
Options | ||
name - explicitly specify the name for the index | ||
unique - is this a unique index | ||
where - where clause | ||
concurrently - | ||
options.method - [ btree | hash | gist | spgist | gin ] | ||
*/ | ||
options = options || {}; | ||
Options | ||
name - explicitly specify the name for the index | ||
unique - is this a unique index | ||
where - where clause | ||
concurrently - | ||
options.method - [ btree | hash | gist | spgist | gin ] | ||
*/ | ||
options = options || {}; // eslint-disable-line no-param-reassign | ||
var sql = utils.t('CREATE {unique} INDEX {concurrently}{index_name} ON \"{table_name}\"{method} ({columns}){where};', { | ||
table_name : table_name, | ||
index_name : generateIndexName( table_name, columns, options ), | ||
columns : generateColumnsString(columns), | ||
unique : options.unique ? ' UNIQUE ' : '', | ||
concurrently : options.concurrently ? ' CONCURRENTLY ' : '', | ||
method : options.method ? ' USING '+options.method : '', | ||
where : options.where ? ' WHERE '+options.where : '', | ||
} | ||
var sql = utils.t('CREATE {unique} INDEX {concurrently} "{index_name}" ON "{table_name}"{method} ({columns}){where};', { | ||
table_name: table_name, | ||
index_name: generateIndexName(table_name, columns, options), | ||
columns: generateColumnsString(columns), | ||
unique: options.unique ? ' UNIQUE ' : '', | ||
concurrently: options.concurrently ? ' CONCURRENTLY ' : '', | ||
method: options.method ? ' USING ' + options.method : '', | ||
where: options.where ? ' WHERE ' + options.where : '', | ||
} | ||
); | ||
@@ -32,29 +51,11 @@ return sql; | ||
drop: function ( table_name, columns, options ) { | ||
options = options || {}; | ||
drop: function(table_name, columns, options) { | ||
options = options || {}; // eslint-disable-line no-param-reassign | ||
var index_name = generateIndexName( table_name, columns, options ); | ||
return utils.t('DROP INDEX {index};', { index: index_name } ); | ||
} | ||
} | ||
var index_name = generateIndexName(table_name, columns, options); | ||
return utils.t('DROP INDEX "{index}";', { index: index_name }); | ||
}, | ||
}; | ||
// setup reverse functions | ||
ops.create.reverse = ops.drop; | ||
function generateIndexName( table_name, columns, options ){ | ||
if (options.name) return options.name; | ||
var name = table_name; | ||
if ( _.isArray(columns) ) name += '_' + columns.join('_'); | ||
else name += '_' + columns; | ||
if (options.unique) name += '_unique'; | ||
name += '_index'; | ||
return name; | ||
// if (options.unique) | ||
} | ||
function generateColumnsString(columns){ | ||
if ( _.isArray(columns) ) return columns = columns.map(function(name){return '"' + name + '"';}).join(', '); | ||
if (/.+\(.*\)/.test(columns)) return columns; //expression | ||
return columns = '"' + columns + '"'; //single column | ||
} |
var utils = require('../utils'); | ||
module.exports = { | ||
sql: function(sql, args){ | ||
sql: function(sql, args) { | ||
// applies some very basic templating using the utils.p | ||
var s = utils.t(sql, args); | ||
// add trailing ; if not present | ||
if (s.lastIndexOf(';') != (s.length - 1)){ | ||
if (s.lastIndexOf(';') !== (s.length - 1)) { | ||
s += ';'; | ||
} | ||
return s; | ||
} | ||
} | ||
}, | ||
}; |
var utils = require('../utils'); | ||
var _ = require('lodash'); | ||
var type_adapters = { | ||
int: 'integer', | ||
string: 'text', | ||
float: 'real', | ||
double: 'double precision', | ||
datetime: 'timestamp', | ||
bool: 'boolean', | ||
}; | ||
function applyTypeAdapters(type) { | ||
// some convenience adapters -- see above | ||
return type_adapters[type] ? type_adapters[type] : type; | ||
} | ||
function parseColumns(columns) { | ||
return _.map(columns, function(options, column_name) { | ||
options = options || {}; // eslint-disable-line no-param-reassign | ||
var type = applyTypeAdapters(options.type); | ||
var constraints = []; | ||
if (typeof options === 'string') { | ||
// convenience type for serial primary keys | ||
if (options === 'id') { | ||
type = 'serial'; | ||
options = { primaryKey: true }; // eslint-disable-line no-param-reassign | ||
} else { | ||
type = options; | ||
} | ||
} | ||
if (options.unique) { | ||
constraints.push('UNIQUE'); | ||
} | ||
if (options.primaryKey) { | ||
constraints.push('PRIMARY KEY'); | ||
} | ||
if (options.notNull) { | ||
constraints.push('NOT NULL'); | ||
} | ||
if (options.check) { | ||
constraints.push('CHECK (' + options.check + ')'); | ||
} | ||
if (options.references) { | ||
constraints.push('REFERENCES ' + options.references); | ||
if (options.onDelete) { | ||
constraints.push('ON DELETE ' + options.onDelete); | ||
} | ||
if (options.onUpdate) { | ||
constraints.push('ON UPDATE ' + options.onUpdate); | ||
} | ||
} | ||
return utils.t('"{column_name}" {type}{default}{constraints}', { | ||
column_name: column_name, | ||
type: type, | ||
default: options.default !== undefined ? ' DEFAULT ' + utils.escapeValue(options.default) : '', | ||
constraints: constraints.length ? ' ' + constraints.join(' ') : '', | ||
}); | ||
}).join(', \n'); | ||
} | ||
var ops = module.exports = { | ||
create: function( table_name, columns, options ) { | ||
create: function(table_name, columns, options) { | ||
/* | ||
columns - hash of columns | ||
columns - hash of columns | ||
Options | ||
table_name - the name of the table | ||
columns - see column options | ||
options.inherits - table to inherit from (optional) | ||
*/ | ||
options = options || {}; | ||
var sql = utils.t("CREATE TABLE \"{table_name}\" (\n{columns}\n){inherits};", { | ||
table_name : table_name, | ||
columns : parseColumns( columns ).replace(/^/gm, ' '), | ||
inherits : options.inherits ? ' INHERITS '+options.inherits : '', | ||
Options | ||
table_name - the name of the table | ||
columns - see column options | ||
options.inherits - table to inherit from (optional) | ||
*/ | ||
options = options || {}; // eslint-disable-line no-param-reassign | ||
var sql = utils.t('CREATE TABLE "{table_name}" (\n{columns}\n){inherits};', { | ||
table_name: table_name, | ||
columns: parseColumns(columns).replace(/^/gm, ' '), | ||
inherits: options.inherits ? ' INHERITS ' + options.inherits : '', | ||
}); | ||
@@ -23,78 +84,77 @@ return sql; | ||
drop: function ( table_name ) { | ||
return utils.t('DROP TABLE \"{table_name}\";', { table_name: table_name } ); | ||
drop: function(table_name) { | ||
return utils.t('DROP TABLE "{table_name}";', { table_name: table_name }); | ||
}, | ||
addColumns: function ( table_name, columns ) { | ||
return utils.t("ALTER TABLE \"{table_name}\" \n{actions};", { | ||
addColumns: function(table_name, columns) { | ||
return utils.t('ALTER TABLE "{table_name}" \n{actions};', { | ||
table_name: table_name, | ||
actions: parseColumns( columns ).replace(/^/gm, ' ADD '), | ||
actions: parseColumns(columns).replace(/^/gm, ' ADD '), | ||
}); | ||
}, | ||
dropColumns: function ( table_name, columns ) { | ||
if ( typeof columns === 'string' ) { | ||
columns = [columns]; | ||
} else if ( !_.isArray(columns) && typeof columns === 'object' ){ | ||
columns = _.keys(columns); | ||
dropColumns: function(table_name, columns) { | ||
if (typeof columns === 'string') { | ||
columns = [ columns ]; // eslint-disable-line no-param-reassign | ||
} else if (!_.isArray(columns) && typeof columns === 'object') { | ||
columns = _.keys(columns); // eslint-disable-line no-param-reassign | ||
} | ||
return utils.t("ALTER TABLE \"{table_name}\" \n{actions};", { | ||
return utils.t('ALTER TABLE "{table_name}" \n{actions};', { | ||
table_name: table_name, | ||
actions: columns.join(",\n").replace(/^/gm, ' DROP '), | ||
actions: columns.join(',\n').replace(/^/gm, ' DROP '), | ||
}); | ||
}, | ||
alterColumn: function ( table_name, column_name, options ) { | ||
alterColumn: function(table_name, column_name, options) { | ||
var actions = []; | ||
if ( options.default === null ){ | ||
if (options.default === null) { | ||
actions.push('DROP DEFAULT'); | ||
} else if ( options.default !== undefined ){ | ||
actions.push('SET DEFAULT '+utils.escapeValue(options.default)); | ||
} else if (options.default !== undefined) { | ||
actions.push('SET DEFAULT ' + utils.escapeValue(options.default)); | ||
} | ||
if ( options.type ){ | ||
actions.push('SET DATA TYPE '+applyTypeAdapters(options.type)); | ||
if (options.type) { | ||
actions.push('SET DATA TYPE ' + applyTypeAdapters(options.type)); | ||
} | ||
if ( options.notNull ){ | ||
if (options.notNull) { | ||
actions.push('SET NOT NULL'); | ||
} else if ( options.notNull === false || options.allowNull ){ | ||
} else if (options.notNull === false || options.allowNull) { | ||
actions.push('DROP NOT NULL'); | ||
} | ||
return utils.t("ALTER TABLE \"{table_name}\" \n{actions};", { | ||
return utils.t('ALTER TABLE "{table_name}" \n{actions};', { | ||
table_name: table_name, | ||
actions: actions.join(",\n").replace(/^/gm, ' ALTER '+column_name+' '), | ||
actions: actions.join(',\n').replace(/^/gm, ' ALTER ' + column_name + ' '), | ||
}); | ||
}, | ||
// RENAME | ||
renameTable: function( table_name, new_name ){ | ||
return utils.t("ALTER TABLE \"{table_name}\" RENAME TO {new_name};", { | ||
renameTable: function(table_name, new_name) { | ||
return utils.t('ALTER TABLE "{table_name}" RENAME TO "{new_name}";', { | ||
table_name: table_name, | ||
new_name: new_name | ||
new_name: new_name, | ||
}); | ||
}, | ||
undoRenameTable: function( table_name, new_name ){ | ||
module.exports.renameTable( new_name, table_name ); | ||
undoRenameTable: function(table_name, new_name) { | ||
module.exports.renameTable(new_name, table_name); | ||
}, | ||
renameColumn: function( table_name, column_name, new_name ){ | ||
return utils.t("ALTER TABLE \"{table_name}\" RENAME {column} TO {new_name};", { | ||
renameColumn: function(table_name, column_name, new_name) { | ||
return utils.t('ALTER TABLE "{table_name}" RENAME "{column}" TO "{new_name}";', { | ||
table_name: table_name, | ||
column: column_name, | ||
new_name: new_name | ||
new_name: new_name, | ||
}); | ||
}, | ||
undoRenameColumn: function( table_name, column_name, new_name ){ | ||
module.exports.renameColumn( table_name, new_name, column_name ); | ||
undoRenameColumn: function(table_name, column_name, new_name) { | ||
module.exports.renameColumn(table_name, new_name, column_name); | ||
}, | ||
// CONSTRAINTS -- only supports named check constraints | ||
addConstraint: function( table_name, constraint_name, expression ){ | ||
return utils.t("ALTER TABLE \"{table_name}\" ADD{constraint_name} {constraint};", { | ||
addConstraint: function(table_name, constraint_name, expression) { | ||
return utils.t('ALTER TABLE "{table_name}" ADD{constraint_name} {constraint};', { | ||
table_name: table_name, | ||
constraint_name: constraint_name ? ' CONSTRAINT '+constraint_name : '', | ||
constraint: expression | ||
constraint_name: constraint_name ? ' CONSTRAINT "' + constraint_name + '"' : '', | ||
constraint: expression, | ||
}); | ||
}, | ||
dropConstraint: function( table_name, constraint_name, expression ){ | ||
return utils.t("ALTER TABLE \"{table_name}\" DROP CONSTRAINT {constraint_name};", { | ||
dropConstraint: function(table_name, constraint_name) { | ||
return utils.t('ALTER TABLE "{table_name}" DROP CONSTRAINT "{constraint_name}";', { | ||
table_name: table_name, | ||
@@ -106,17 +166,15 @@ constraint_name: constraint_name, | ||
createType: function( type_name, options ){ | ||
return utils.t("CREATE TYPE {type_name} AS ENUM ('{opts}');", { | ||
createType: function(type_name, options) { | ||
return utils.t('CREATE TYPE "{type_name}" AS ENUM (\'{opts}\');', { | ||
type_name: type_name, | ||
opts: options.join("', '") | ||
opts: options.join('\', \''), | ||
}); | ||
}, | ||
dropType: function( type_name ){ | ||
return utils.t("DROP TYPE {type_name};", { type_name: type_name }); | ||
dropType: function(type_name) { | ||
return utils.t('DROP TYPE "{type_name}";', { type_name: type_name }); | ||
}, | ||
alterType: function( type_name, options ){ | ||
alterType: function() { | ||
}, | ||
}; | ||
} | ||
} | ||
// setup reverse functions | ||
@@ -129,64 +187,1 @@ ops.create.reverse = ops.drop; | ||
ops.renameTable.reverse = ops.undoRenameTable; | ||
var type_adapters = { | ||
'int': 'integer', | ||
'string': 'text', | ||
'float': 'real', | ||
'double': 'double precision', | ||
'datetime': 'timestamp', | ||
'bool': 'boolean', | ||
} | ||
function applyTypeAdapters ( type ){ | ||
// some convenience adapters -- see above | ||
return type_adapters[type] ? type_adapters[type] : type; | ||
} | ||
function parseColumns ( columns ){ | ||
return _.map(columns, function( options, column_name ){ | ||
options = options || {}; | ||
var type = applyTypeAdapters(options.type); | ||
var constraints = []; | ||
if (typeof options === 'string'){ | ||
// convenience type for serial primary keys | ||
if (options == 'id'){ | ||
type = 'serial'; | ||
options = { primaryKey: true }; | ||
} else { | ||
type = options; | ||
} | ||
} | ||
if (options.unique){ | ||
constraints.push('UNIQUE'); | ||
} | ||
if (options.primaryKey){ | ||
constraints.push('PRIMARY KEY'); | ||
} | ||
if (options.notNull){ | ||
constraints.push('NOT NULL'); | ||
} | ||
if (options.check){ | ||
constraints.push('CHECK ('+options.check+')'); | ||
} | ||
if (options.references){ | ||
constraints.push('REFERENCES '+options.references); | ||
if (options.onDelete){ | ||
constraints.push('ON DELETE '+options.onDelete); | ||
} | ||
if (options.onUpdate){ | ||
constraints.push('ON UPDATE '+options.onUpdate); | ||
} | ||
} | ||
return utils.t('\"{column_name}\" {type}{default}{constraints}', { | ||
column_name: column_name, | ||
type: type, | ||
default: options.default !== undefined ? ' DEFAULT '+utils.escapeValue(options.default) : '', | ||
constraints: constraints.length ? ' '+constraints.join(' ') : '' | ||
}); | ||
}).join(", \n"); | ||
} |
@@ -9,24 +9,59 @@ var async = require('async'); | ||
module.exports = function MigrationRunner(options){ | ||
function loadMigrationFiles(options) { | ||
var migrations = []; | ||
fs.readdirSync(options.dir + '/').forEach(function(file) { | ||
if (file.match(/.+\.js$/g) !== null && file !== 'index.js') { | ||
var file_path = options.dir + '/' + file; | ||
var actions = require(path.relative(__dirname, file_path)); | ||
var migration = new Migration(file_path, actions, options); | ||
migrations.push(migration); | ||
} | ||
}); | ||
return _.sortBy(migrations, 'name'); | ||
} | ||
function ensureMigrationTableExists(options, callback) { | ||
Step( | ||
function checkIfTableExists() { | ||
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' AND table_name = \'' + options.migrations_table + '\'', this); | ||
}, | ||
function createTableIfNecessary(err, result) { | ||
if (err) return callback(err); | ||
if (result && result.rows && result.rows.length === 1) return callback(); | ||
db.query('CREATE TABLE "' + options.migrations_table + '" ( id SERIAL, name varchar(255) NOT NULL, run_on timestamp NOT NULL)', this); | ||
}, | ||
function finish(err) { | ||
if (err) return callback(err); | ||
return callback(); | ||
} | ||
); | ||
} | ||
module.exports = function MigrationRunner(options) { | ||
var self = this; | ||
var migrations = []; | ||
loadMigrationFiles(); | ||
var migrations = loadMigrationFiles(options); | ||
db.init(options.database_url); | ||
self.run = function(callback) { | ||
function exitCallback(arg) { | ||
db.close(); | ||
callback(arg); | ||
} | ||
self.run = function(callback){ | ||
var current_index, migrate_to_index; | ||
Step( | ||
function(){ | ||
ensureMigrationTableExists(this); | ||
function() { | ||
db.init(options.database_url); | ||
this(); | ||
}, | ||
function fetchCompletedMigrations(err){ | ||
db.query("SELECT * FROM "+options.migrations_table+" ORDER BY run_on", this) | ||
function() { | ||
ensureMigrationTableExists(options, this); | ||
}, | ||
function fetchCompletedMigrations() { | ||
db.query('SELECT * FROM "' + options.migrations_table + '" ORDER BY run_on', this); | ||
}, | ||
function determineMigrationsToRun(err, result) { | ||
if(err) return callback(err); | ||
if(!result || !result.rows) return callback(new Error('Unable to fetch migrations from pgmigrations table')); | ||
if (err) return exitCallback(err); | ||
if (!result || !result.rows) return exitCallback(new Error('Unable to fetch migrations from ' + options.migrations_table + ' table')); | ||
//store done migration names | ||
// store done migration names | ||
var runNames = result.rows.map(function(row) { | ||
@@ -39,9 +74,8 @@ return row.name; | ||
//filter out undone and done migrations from list of files | ||
for(var i = 0; i < migrations.length; i++) { | ||
if(runNames.indexOf(migrations[i].name) < 0) { | ||
//if specific migration file is requested | ||
if(options.file && options.file == migrations[i].name) { | ||
runMigrations = [migrations[i]]; | ||
// filter out undone and done migrations from list of files | ||
for (var i = 0; i < migrations.length; i++) { | ||
if (runNames.indexOf(migrations[i].name) < 0) { | ||
// if specific migration file is requested | ||
if (options.file && options.file === migrations[i].name) { | ||
runMigrations = [ migrations[i] ]; | ||
break; | ||
@@ -51,14 +85,13 @@ } | ||
} else { | ||
doneMigrations.push(migrations[i]) | ||
doneMigrations.push(migrations[i]); | ||
} | ||
} | ||
//final selection will go here | ||
// final selection will go here | ||
var to_run; | ||
//down gets by default one migration at the time, if not set otherwise | ||
if(options.direction == 'down') { | ||
// down gets by default one migration at the time, if not set otherwise | ||
if (options.direction === 'down') { | ||
to_run = doneMigrations.slice(-1); | ||
//up gets all undone migrations by default | ||
// up gets all undone migrations by default | ||
} else { | ||
@@ -68,7 +101,7 @@ to_run = runMigrations; | ||
//if specific count of migrations is requested | ||
if(options.count) { | ||
// if specific count of migrations is requested | ||
if (options.count) { | ||
// infinity is set in the bin file | ||
if(options.count !== Infinity) { | ||
if(options.direction == 'up') { | ||
if (options.count !== Infinity) { | ||
if (options.direction === 'up') { | ||
to_run = runMigrations.slice(0, options.count); | ||
@@ -81,5 +114,5 @@ } else { | ||
if(!to_run.length) { | ||
if (!to_run.length) { | ||
console.log('No migrations to run!'); | ||
return callback(); | ||
return exitCallback(); | ||
} | ||
@@ -89,60 +122,26 @@ | ||
console.log('> Migrating files:'); | ||
for(var i in to_run) { | ||
console.log('> - ' + to_run[i].name); | ||
} | ||
to_run.forEach(function(m) { | ||
console.log('> - ' + m.name); | ||
}); | ||
this(null, to_run); | ||
}, | ||
function runMigrations(err, migrations_to_run){ | ||
if (err) return callback(err); | ||
async.eachSeries(migrations_to_run, function(m, next){ | ||
if (options.direction == 'up') m.applyUp(next); | ||
function runMigrations(err, migrations_to_run) { | ||
if (err) return exitCallback(err); | ||
async.eachSeries(migrations_to_run, function(m, next) { | ||
if (options.direction === 'up') m.applyUp(next); | ||
else m.applyDown(next); | ||
}, this); | ||
}, | ||
function finish(err){ | ||
function finish(err) { | ||
if (err) { | ||
console.log('> Rolling back attempted migration ...') | ||
return callback(err); | ||
console.log('> Rolling back attempted migration ...'); | ||
return exitCallback(err); | ||
} | ||
callback(); | ||
exitCallback(); | ||
} | ||
) | ||
} | ||
); | ||
}; | ||
}; | ||
function loadMigrationFiles(){ | ||
fs.readdirSync( options.dir + '/' ).forEach(function(file) { | ||
if (file.match(/.+\.js$/g) !== null && file !== 'index.js') { | ||
var name = file.replace('.js', ''); | ||
// console.log( '> loading migration: '+name) | ||
var file_path = options.dir +'/'+ file; | ||
var actions = require( path.relative(__dirname, file_path) ); | ||
var migration = new Migration( file_path, actions, options ); | ||
migrations.push(migration); | ||
} | ||
}); | ||
migrations = _.sortBy(migrations, 'name'); | ||
} | ||
function ensureMigrationTableExists(callback){ | ||
Step( | ||
function checkIfTableExists(){ | ||
db.query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '"+options.migrations_table+"'", this); | ||
}, | ||
function createTableIfNecessary(err, result){ | ||
if (err) return callback(err); | ||
if (result && result.rows && result.rows.length == 1) return callback(); | ||
db.query('CREATE TABLE '+options.migrations_table+' ( id SERIAL, name varchar(255) NOT NULL, run_on timestamp NOT NULL)', this); | ||
}, | ||
function finish(err){ | ||
if (err) return callback(err); | ||
return callback(); | ||
} | ||
) | ||
} | ||
} | ||
@@ -0,29 +1,37 @@ | ||
// This is used to create unescaped strings | ||
// exposed in the migrations via pgm.func | ||
function PgLiteral(str) { | ||
this.toString = function() { | ||
return str; | ||
}; | ||
} | ||
PgLiteral.create = function(str) { | ||
return new PgLiteral(str); | ||
}; | ||
module.exports = { | ||
// teeny templating function | ||
// http://mir.aculo.us/2011/03/09/little-helpers-a-tweet-sized-javascript-templating-engine/ | ||
t: function(s,d){ | ||
for(var p in d) | ||
s=s.replace(new RegExp('{'+p+'}','g'), d[p]); | ||
return s; | ||
t: function(s, d) { | ||
return Object | ||
.keys(d || {}) | ||
.reduce(function(str, p) { | ||
var newSubstr = d[p]; | ||
if (typeof d[p] === 'object') { | ||
var schema = d[p].schema; | ||
var name = d[p].name; | ||
newSubstr = (schema ? schema + '"."' : '') + name; | ||
} | ||
return str.replace(new RegExp('{' + p + '}', 'g'), newSubstr); | ||
}, s); | ||
}, | ||
escapeValue: function ( val ){ | ||
escapeValue: function(val) { | ||
// TODO: figure out a solution for unescaping functions -- ex: NOW() | ||
if ( val === null ) return 'NULL'; | ||
if ( typeof val === 'boolean' ) return val.toString(); | ||
if ( typeof val === 'string' ) return "'"+escape(val)+"'"; | ||
if ( typeof val === 'number' ) return val; | ||
if ( val instanceof PgLiteral ) return val.toString(); | ||
if (val === null) return 'NULL'; | ||
if (typeof val === 'boolean') return val.toString(); | ||
if (typeof val === 'string') return '\'' + escape(val) + '\''; | ||
if (typeof val === 'number') return val; | ||
if (val instanceof PgLiteral) return val.toString(); | ||
}, | ||
PgLiteral: PgLiteral | ||
} | ||
// This is used to create unescaped strings | ||
// exposed in the migrations via pgm.func | ||
function PgLiteral(str){ | ||
this.toString = function(){ | ||
return str; | ||
} | ||
} | ||
PgLiteral.create = function(str){ | ||
return new PgLiteral(str); | ||
} | ||
PgLiteral: PgLiteral, | ||
}; |
@@ -18,3 +18,3 @@ { | ||
], | ||
"version": "0.0.12", | ||
"version": "0.0.13", | ||
"engines": { | ||
@@ -37,6 +37,7 @@ "node": ">=0.6.0" | ||
"optimist": "~0.6.1", | ||
"pkginfo": "~0.3.0", | ||
"step": "0.0.5" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^3.11.1", | ||
"eslint-plugin-import": "^2.2.0", | ||
"mocha": "^2.2.5", | ||
@@ -51,4 +52,6 @@ "pg": "^6.1.2", | ||
"scripts": { | ||
"test": "./node_modules/.bin/mocha test" | ||
"test": "./node_modules/.bin/mocha test", | ||
"lint": "eslint -c .eslintrc . bin/pg-migrate", | ||
"lintfix": "npm run lint -- --fix" | ||
} | ||
} |
@@ -300,6 +300,17 @@ # pg-migrate | ||
### Using schemas | ||
Instead of passing string as name to `pgm` functions, you can pass an object with keys `schema` and `name`. E.g. | ||
`pgm.createTable( {schema: 'my_schema', name: 'my_table_name'}, {id: 'serial'});` | ||
will generate | ||
```sql | ||
CREATE TABLE "my_schema"."my_table_name" ( | ||
"id" serial | ||
); | ||
``` | ||
## Explanation & Goals | ||
@@ -306,0 +317,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
74144
7
26
635
322
6
1
- Removedpkginfo@~0.3.0
- Removedpkginfo@0.3.1(transitive)