Skip to content

Refactor postgresql migration #219

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

Merged
merged 1 commit into from
Mar 2, 2017
Merged
Changes from all commits
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
230 changes: 35 additions & 195 deletions lib/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,136 +11,46 @@ var async = require('async');
module.exports = mixinMigration;

function mixinMigration(PostgreSQL) {
/*!
* Discover the properties from a table
* @param {String} model The model name
* @param {Function} cb The callback function
*/
PostgreSQL.prototype.getTableStatus = function(model, cb) {
var fields;
var indexes;

function done(err) {
if (fields && indexes) {
cb(err, fields, indexes);
}
}

function decoratedTableDataCallback(err, data) {
PostgreSQL.prototype.showFields = function(model, cb) {
var sql = 'SELECT column_name AS "column", data_type AS "type", ' +
'is_nullable AS "nullable", character_maximum_length as "length"' // , data_default AS "Default"'
+ ' FROM "information_schema"."columns" WHERE table_name=\'' +
this.table(model) + '\'';
this.execute(sql, function(err, fields) {
if (err) {
console.error(err);
}
if (!err) {
data.forEach(function(field) {
return cb(err);
} else {
fields.forEach(function(field) {
field.type = mapPostgreSQLDatatypes(field.type, field.length);
});
cb(err, fields);
}
fields = data;
done(err);
}

function decoratedIndexDataCallback(err, data) {
var indexHash = {};

if (err) {
console.log(err);
}

indexes = data;
done(err);
}

this.execute('SELECT column_name AS "column", data_type AS "type", ' +
'is_nullable AS "nullable", character_maximum_length as "length"' // , data_default AS "Default"'
+ ' FROM "information_schema"."columns" WHERE table_name=\'' +
this.table(model) + '\'', decoratedTableDataCallback);

this.execute(
'SELECT t.relname AS "table", i.relname AS "name", ' +
'am.amname AS "type", ix.indisprimary AS "primary", ' +
'ix.indisunique AS "unique", ' +
'ARRAY(SELECT pg_get_indexdef(ix.indexrelid, k + 1, true) ' +
' FROM generate_subscripts(ix.indkey, 1) AS k ' +
' ORDER BY k ) AS "keys", ' +
'ARRAY(SELECT ' +
' CASE ix.indoption[k] & 1 WHEN 1 THEN \'DESC\' ELSE \'ASC\' END ' +
' FROM generate_subscripts(ix.indoption, 1) AS k ' +
' ORDER BY k ' +
') AS "order" ' +
'FROM pg_class t, pg_class i, pg_index ix, pg_am am ' +
'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND ' +
'i.relam = am.oid AND ' +
't.relkind=\'r\' AND t.relname=\'' +
this.table(model) + '\'', decoratedIndexDataCallback);
};

/**
* Perform autoupdate for the given models
* @param {String[]} [models] A model name or an array of model names. If not present, apply to all models
* @callback {Function} [callback] The callback function
* @param {String|Error} err The error string or object
*/
PostgreSQL.prototype.autoupdate = function(models, cb) {
var self = this;
if ((!cb) && ('function' === typeof models)) {
cb = models;
models = undefined;
}
// First argument is a model name
if ('string' === typeof models) {
models = [models];
}

models = models || Object.keys(this._models);

async.each(models, function(model, done) {
if (!(model in self._models)) {
return process.nextTick(function() {
done(new Error(g.f('Model not found: %s' + model)));
});
}
self.getTableStatus(model, function(err, fields, indexes) {
if (!err && fields.length) {
self.alterTable(model, fields, indexes, done);
} else {
self.createTable(model, done);
}
});
}, cb);
});
};

/*!
* Check if the models exist
* @param {String[]} [models] A model name or an array of model names. If not present, apply to all models
* @param {Function} [cb] The callback function
*/
PostgreSQL.prototype.isActual = function(models, cb) {
var self = this;

if ((!cb) && ('function' === typeof models)) {
cb = models;
models = undefined;
}
// First argument is a model name
if ('string' === typeof models) {
models = [models];
}

models = models || Object.keys(this._models);

var changes = [];
async.each(models, function(model, done) {
self.getTableStatus(model, function(err, fields) {
changes = changes.concat(self.getAddModifyColumns(model, fields));
changes = changes.concat(self.getDropColumns(model, fields));
done(err);
});
}, function done(err) {
PostgreSQL.prototype.showIndexes = function(model, cb) {
var sql = 'SELECT t.relname AS "table", i.relname AS "name", ' +
'am.amname AS "type", ix.indisprimary AS "primary", ' +
'ix.indisunique AS "unique", ' +
'ARRAY(SELECT pg_get_indexdef(ix.indexrelid, k + 1, true) ' +
' FROM generate_subscripts(ix.indkey, 1) AS k ' +
' ORDER BY k ) AS "keys", ' +
'ARRAY(SELECT ' +
' CASE ix.indoption[k] & 1 WHEN 1 THEN \'DESC\' ELSE \'ASC\' END ' +
' FROM generate_subscripts(ix.indoption, 1) AS k ' +
' ORDER BY k ' +
') AS "order" ' +
'FROM pg_class t, pg_class i, pg_index ix, pg_am am ' +
'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND ' +
'i.relam = am.oid AND ' +
't.relkind=\'r\' AND t.relname=\'' +
this.table(model) + '\'';
this.execute(sql, function(err, indexes) {
if (err) {
return cb && cb(err);
return cb(err);
} else {
cb(err, indexes);
}
var actual = (changes.length === 0);
cb && cb(null, actual);
});
};

Expand Down Expand Up @@ -205,13 +115,6 @@ function mixinMigration(PostgreSQL) {
return sql;
};

PostgreSQL.prototype.getDropColumns = function(model, actualFields) {
var sql = [];
var self = this;
sql = sql.concat(self.getColumnsToDrop(model, actualFields));
return sql;
};

PostgreSQL.prototype.getColumnsToAdd = function(model, actualFields) {
var self = this;
var m = self._models[model];
Expand All @@ -231,28 +134,6 @@ function mixinMigration(PostgreSQL) {
return sql;
};

PostgreSQL.prototype.addPropertyToActual = function(model, propName) {
var self = this;
var prop = this.getModelDefinition(model).properties[propName];
var sqlCommand = self.columnEscaped(model, propName)
+ ' ' + self.columnDataType(model, propName) +
(self.isNullable(prop) ? '' : ' NOT NULL') +
self.columnDbDefault(model, propName);
return sqlCommand;
};

PostgreSQL.prototype.searchForPropertyInActual = function(model, propName, actualFields) {
var self = this;
var found = false;
actualFields.forEach(function(f) {
if (f.column === self.column(model, propName)) {
found = f;
return;
}
});
return found;
};

PostgreSQL.prototype.getPropertiesToModify = function(model, actualFields) {
var self = this;
var sql = [];
Expand Down Expand Up @@ -345,23 +226,6 @@ function mixinMigration(PostgreSQL) {
}
};

PostgreSQL.prototype.applySqlChanges = function(model, pendingChanges, cb) {
var self = this;
if (pendingChanges.length) {
var thisQuery = 'ALTER TABLE ' + self.tableEscaped(model);
var ranOnce = false;
pendingChanges.forEach(function(change) {
if (ranOnce) {
thisQuery = thisQuery + ' ';
}
thisQuery = thisQuery + ' ' + change;
ranOnce = true;
});
// thisQuery = thisQuery + ';';
self.execute(thisQuery, cb);
}
};

/*!
* Build a list of columns for the given model
* @param {String} model The model name
Expand Down Expand Up @@ -517,29 +381,9 @@ function mixinMigration(PostgreSQL) {
return colDefault ? (' DEFAULT ' + colDefault) : '';
};

/*!
* Find the column type for a given model property
*
* @param {String} model The model name
* @param {String} property The property name
* @returns {String} The column type
*/
PostgreSQL.prototype.columnDataType = function(model, property) {
var columnMetadata = this.columnMetadata(model, property);
var colType = columnMetadata && columnMetadata.dataType;
if (colType) {
colType = colType.toUpperCase();
}
var prop = this.getModelDefinition(model).properties[property];
if (!prop) {
return null;
}
var colLength = columnMetadata && columnMetadata.dataLength || prop.length;
if (colType) {
return colType + (colLength ? '(' + colLength + ')' : '');
}

switch (prop.type.name) {
PostgreSQL.prototype.buildColumnType = function buildColumnType(propertyDefinition) {
var p = propertyDefinition;
switch (p.type.name) {
default:
case 'String':
case 'JSON':
Expand Down Expand Up @@ -641,10 +485,6 @@ function mixinMigration(PostgreSQL) {
}
}

PostgreSQL.prototype.propertyHasNotBeenDeleted = function(model, propName) {
return !!this.getModelDefinition(model).properties[propName];
};

PostgreSQL.prototype.addIndexes = function(model, actualIndexes, cb) {
var self = this;
var m = self._models[model];
Expand Down