Add rough handling for automatic foreign keys

This handles basic creating/dropping of FKs based on model relations
This commit is contained in:
Matthew Dickinson 2016-09-16 16:09:19 -04:00
parent 39d67fa1f9
commit 1324333ffd
1 changed files with 80 additions and 15 deletions

View File

@ -42,11 +42,13 @@ function mixinMigration(MySQL, mysql) {
var table = self.tableEscaped(model); var table = self.tableEscaped(model);
self.execute('SHOW FIELDS FROM ' + table, function(err, fields) { self.execute('SHOW FIELDS FROM ' + table, function(err, fields) {
self.execute('SHOW INDEXES FROM ' + table, function(err, indexes) { self.execute('SHOW INDEXES FROM ' + table, function(err, indexes) {
if (!err && fields && fields.length) { self.discoverForeignKeys(model, {}, function(discoverErr, foreignKeys) {
self.alterTable(model, fields, indexes, done); if (!err && fields && fields.length) {
} else { self.alterTable(model, fields, indexes, foreignKeys, done);
self.createTable(model, done); } else {
} self.createTable(model, done);
}
});
}); });
}); });
}, cb); }, cb);
@ -93,14 +95,16 @@ function mixinMigration(MySQL, mysql) {
var table = self.tableEscaped(model); var table = self.tableEscaped(model);
self.execute('SHOW FIELDS FROM ' + table, function(err, fields) { self.execute('SHOW FIELDS FROM ' + table, function(err, fields) {
self.execute('SHOW INDEXES FROM ' + table, function(err, indexes) { self.execute('SHOW INDEXES FROM ' + table, function(err, indexes) {
self.alterTable(model, fields, indexes, function(err, needAlter) { self.discoverForeignKeys(model, {}, function(discoverErr, foreignKeys) {
if (err) { self.alterTable(model, fields, indexes, foreignKeys, function(err, needAlter) {
return done(err); if (err) {
} else { return done(err);
ok = ok || needAlter; } else {
done(err); ok = ok || needAlter;
} done(err);
}, true); }
}, true);
});
}); });
}); });
}, function(err) { }, function(err) {
@ -111,7 +115,7 @@ function mixinMigration(MySQL, mysql) {
}); });
}; };
MySQL.prototype.alterTable = function(model, actualFields, actualIndexes, done, checkOnly) { MySQL.prototype.alterTable = function(model, actualFields, actualIndexes, actualFks, done, checkOnly) {
var self = this; var self = this;
var m = this.getModelDefinition(model); var m = this.getModelDefinition(model);
var propNames = Object.keys(m.properties).filter(function(name) { var propNames = Object.keys(m.properties).filter(function(name) {
@ -121,6 +125,10 @@ function mixinMigration(MySQL, mysql) {
var indexNames = Object.keys(indexes).filter(function(name) { var indexNames = Object.keys(indexes).filter(function(name) {
return !!m.settings.indexes[name]; return !!m.settings.indexes[name];
}); });
var newFks = m.settings.foreignKeys || {};
var newFkNames = Object.keys(newFks).filter(function(name) {
return !!m.settings.foreignKeys[name];
});
var sql = []; var sql = [];
var ai = {}; var ai = {};
@ -159,6 +167,35 @@ function mixinMigration(MySQL, mysql) {
} }
}); });
//drop foreign keys for removed fields
if (actualFks) {
var removedFks = [];
actualFks.forEach(function(fk) {
var needsToDrop = false;
var newFk = newFks[fk.fkName];
if (newFk) {
var fkCol = expectedColName(newFk.foreignKey);
var fkRefKey = expectedColNameForModel(newFk.entityKey, newFk.entity);
var fkRefTable = newFk.entity.name; //TODO check for mysql name
needsToDrop = fkCol != fk.fkColumnName ||
fkRefKey != fk.pkColumnName ||
fkRefTable != fk.pkTableName;
} else {
needsToDrop = true;
}
if (needsToDrop) {
sql.push('DROP FOREIGN KEY ' + fk.fkName);
removedFks.push(fk); //keep track that we removed these
}
});
//update out list of existing keys by removing dropped keys
actualFks = actualFks.filter(function(k) {
return removedFks.indexOf(k) == -1;
});
}
// drop columns // drop columns
if (actualFields) { if (actualFields) {
actualFields.forEach(function(f) { actualFields.forEach(function(f) {
@ -177,6 +214,8 @@ function mixinMigration(MySQL, mysql) {
aiNames.forEach(function(indexName) { aiNames.forEach(function(indexName) {
if (indexName === 'PRIMARY' || if (indexName === 'PRIMARY' ||
(m.properties[indexName] && self.id(model, indexName))) return; (m.properties[indexName] && self.id(model, indexName))) return;
if (newFkNames.indexOf(indexName) > -1) return; //this index is from an FK
if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] ||
m.properties[indexName] && !m.properties[indexName].index) { m.properties[indexName] && !m.properties[indexName].index) {
sql.push('DROP INDEX ' + self.client.escapeId(indexName)); sql.push('DROP INDEX ' + self.client.escapeId(indexName));
@ -293,6 +332,16 @@ function mixinMigration(MySQL, mysql) {
} }
}); });
//add new foreign keys
if (newFkNames.length) {
//TODO validate that these are in the same DB, etc.
var oldKeyNames = actualFks.map(function(oldKey) { return oldKey.fkName; });
newFkNames.filter(function(key) { return !~oldKeyNames.indexOf(key); })
.forEach(function(key) {
sql.push(self.buildForeignKeyDefinition(m, key));
});
}
if (sql.length) { if (sql.length) {
var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' +
sql.join(',\n'); sql.join(',\n');
@ -336,7 +385,11 @@ function mixinMigration(MySQL, mysql) {
} }
function expectedColName(propName) { function expectedColName(propName) {
var mysql = m.properties[propName].mysql; return expectedColNameForModel(propName, m);
}
function expectedColNameForModel(propName, modelToCheck) {
var mysql = modelToCheck.properties[propName].mysql;
if (typeof mysql === 'undefined') { if (typeof mysql === 'undefined') {
return propName; return propName;
} }
@ -348,6 +401,18 @@ function mixinMigration(MySQL, mysql) {
} }
}; };
MySQL.prototype.buildForeignKeyDefinition = function(model, keyName) {
var fk = model.settings.foreignKeys[keyName];
if (fk) {
//TODO verify that the other model in the same DB
return ' ADD CONSTRAINT ' + this.client.escapeId(fk.name) +
' FOREIGN KEY (' + fk.foreignKey + ')' +
' REFERENCES ' + this.tableEscaped(fk.entity.name) +
'(' + this.client.escapeId(fk.entityKey) + ')';
}
return '';
};
MySQL.prototype.buildColumnDefinitions = MySQL.prototype.buildColumnDefinitions =
MySQL.prototype.propertiesSQL = function(model) { MySQL.prototype.propertiesSQL = function(model) {
var self = this; var self = this;