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,13 +42,15 @@ function mixinMigration(MySQL, mysql) {
var table = self.tableEscaped(model);
self.execute('SHOW FIELDS FROM ' + table, function(err, fields) {
self.execute('SHOW INDEXES FROM ' + table, function(err, indexes) {
self.discoverForeignKeys(model, {}, function(discoverErr, foreignKeys) {
if (!err && fields && fields.length) {
self.alterTable(model, fields, indexes, done);
self.alterTable(model, fields, indexes, foreignKeys, done);
} else {
self.createTable(model, done);
}
});
});
});
}, cb);
};
@ -93,7 +95,8 @@ function mixinMigration(MySQL, mysql) {
var table = self.tableEscaped(model);
self.execute('SHOW FIELDS FROM ' + table, function(err, fields) {
self.execute('SHOW INDEXES FROM ' + table, function(err, indexes) {
self.alterTable(model, fields, indexes, function(err, needAlter) {
self.discoverForeignKeys(model, {}, function(discoverErr, foreignKeys) {
self.alterTable(model, fields, indexes, foreignKeys, function(err, needAlter) {
if (err) {
return done(err);
} else {
@ -103,6 +106,7 @@ function mixinMigration(MySQL, mysql) {
}, true);
});
});
});
}, function(err) {
if (err) {
return 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 m = this.getModelDefinition(model);
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) {
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 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
if (actualFields) {
actualFields.forEach(function(f) {
@ -177,6 +214,8 @@ function mixinMigration(MySQL, mysql) {
aiNames.forEach(function(indexName) {
if (indexName === 'PRIMARY' ||
(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] ||
m.properties[indexName] && !m.properties[indexName].index) {
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) {
var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' +
sql.join(',\n');
@ -336,7 +385,11 @@ function mixinMigration(MySQL, mysql) {
}
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') {
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.propertiesSQL = function(model) {
var self = this;