不要怂,就是干,撸起袖子干!

Commit af723ee4 by Jonathan M. Altman

Add basic function and trigger migrations

Added the ability to define migrations that create, drop, and rename
both functions and triggers (since triggers need functions).  This
allows migrations to handle more of the requirements of setting up
the underlying SQL database behavior.

This version currently only provides support on postgresql.
1 parent 65fe70fd
......@@ -707,6 +707,82 @@ module.exports = (function() {
return false // not supported by dialect
},
createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
var sql = [
'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
, '<%= eventType %> <%= eventSpec %>'
, 'ON <%= tableName %>'
, '<%= optionsSpec %>'
, 'EXECUTE PROCEDURE <%= functionName %>(<%= paramList %>)'
].join('\n\t');
return Utils._.template(sql)({
constraintVal: this.triggerEventTypeIsConstraint(eventType)
, triggerName: triggerName
, eventType: this.decodeTriggerEventType(eventType)
, eventSpec: this.expandTriggerEventSpec(fireOnSpec)
, tableName: tableName
, optionsSpec: this.expandOptions(optionsArray)
, functionName: functionName
, paramList: this.expandFunctionParamList(functionParams)
})
},
dropTrigger: function(tableName, triggerName) {
var sql = 'DROP TRIGGER IF EXISTS <%= triggerName %> ON <%= tableName %> RESTRICT'
return Utils._.template(sql)({
triggerName: triggerName
, tableName: tableName
});
},
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
var sql = 'ALTER TRIGGER <%= oldTriggerName %> ON <%= tableName %> RENAME TO <%= newTriggerName%>'
return Utils._.template(sql)({
tableName: tableName
, oldTriggerName: oldTriggerName
, newTriggerName: newTriggerName
});
},
createFunction: function(functionName, params, returnType, language, body, options) {
var sql = [ "CREATE FUNCTION <%= functionName %>(<%= paramList %>)"
, "RETURNS <%= returnType %> AS $$"
, "BEGIN"
, "\t<%= body %>"
, "END;"
, "$$ language '<%= language %>'<%= options %>;"
].join('\n');
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params),
returnType: returnType,
body: body.replace('\n', '\n\t'),
language: language,
options: this.expandOptions(options)
})
},
dropFunction: function(functionName, params) {
// RESTRICT is (currently, as of 9.2) default but we'll be explicit
var sql = 'DROP FUNCTION IF EXISTS <%= functionName %>(<%= paramList %>) RESTRICT';
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params)
});
},
renameFunction: function(oldFunctionName, params, newFunctionName) {
// RESTRICT is (currently, as of 9.2) default but we'll be explicit
var sql = 'ALTER FUNCTION <%= oldFunctionName %>(<%= paramList %>) RENAME TO <%= newFunctionName %>';
return Utils._.template(sql)({
oldFunctionName: oldFunctionName,
paramList: this.expandFunctionParamList(params),
newFunctionName: newFunctionName
});
},
databaseConnectionUri: function(config) {
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'
......@@ -724,6 +800,78 @@ module.exports = (function() {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"))
},
expandFunctionParamList: function expandFunctionParamList(params) {
if (Utils._.isUndefined(params) || !Utils._.isArray(params)) {
throw new Error("expandFunctionParamList: function parameters array required, including an empty one for no arguments");
}
var paramList = Utils._.each(params, function expandParam(curParam){
paramDef = [];
if (Utils._.has(curParam, 'type')) {
if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam['direction']); }
if (Utils._.has(curParam, 'name')) { paramDef.push(curParam['name']); }
paramDef.push(curParam['type']);
} else {
throw new Error('createFunction called with a parameter with no type');
}
return paramDef.join(' ');
});
return paramList.join(', ');
},
expandOptions: function expandOptions(options) {
return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
'' :
'\n\t' + options.join('\n\t');
},
decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) {
var EVENT_DECODER = {
'after': 'AFTER'
, 'before': 'BEFORE'
, 'instead_of': 'INSTEAD OF'
, 'after_constraint': 'AFTER'
}
if (!Utils._.has(EVENT_DECODER, eventSpecifier)) {
throw new Error('Invalid trigger event specified: ' + eventSpecifier);
}
return EVENT_DECODER[eventSpecifier];
},
triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) {
return eventSpecifier === 'after_constrain' ? 'CONSTRAINT ' : '';
},
expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) {
if (Utils._.isEmpty(fireOnSpec)) {
throw new Error('no table change events specified to trigger on');
}
return Utils._.map(fireOnSpec, function parseTriggerEventSpec(fireValue, fireKey){
var EVENT_MAP = {
'insert': 'INSERT'
, 'update': 'UPDATE'
, 'delete': 'DELETE'
, 'truncate': 'TRUNCATE'
};
if (!Utils._.has(EVENT_MAP, fireKey)) {
throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey);
}
var eventSpec = EVENT_MAP[fireKey];
if (eventSpec === 'UPDATE') {
if (Utils._.isArray(fireValue) && fireValue.length > 0) {
eventSpec += ' OF ' + fireValue.join(', ');
}
}
return eventSpec;
}).join(' OR ');
},
pgEnum: function (tableName, attr, dataType) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
......
......@@ -272,6 +272,49 @@ module.exports = (function() {
},
/*
Create a trigger
*/
createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams,
optionsArray) {
throwMethodUndefined('createTrigger')
},
/*
Drop a trigger
*/
dropTrigger: function(tableName, triggerName) {
throwMethodUndefined('dropTrigger')
},
/*
Rename a trigger
*/
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
throwMethodUndefined('renameTrigger')
},
/*
Create a function
*/
createFunction: function(functionName, params, returnType, language, body, options) {
throwMethodUndefined('createFunction')
},
/*
Drop a function
*/
dropFunction: function(functionName, params) {
throwMethodUndefined('dropFunction')
},
/*
Rename a function
*/
renameFunction: function(oldFunctionName, params, newFunctionName) {
throwMethodUndefined('renameFunction')
},
/*
Escape an identifier (e.g. a table or attribute name)
*/
quoteIdentifier: function(identifier, force) {
......
......@@ -475,6 +475,80 @@ module.exports = (function() {
}
}
QueryInterface.prototype.createTrigger = function(tableName, triggerName, timingType, fireOnArray,
functionName, functionParams, optionsArray) {
var sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName
, functionParams, optionsArray);
if (sql){
return queryAndEmit.call(this, sql, 'createTrigger')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('createTrigger', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.dropTrigger = function(tableName, triggerName) {
var sql = this.QueryGenerator.dropTrigger(tableName, triggerName)
if (sql){
return queryAndEmit.call(this, sql, 'dropTrigger')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('dropTrigger', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.renameTrigger = function(tableName, oldTriggerName, newTriggerName) {
var sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName)
if (sql){
return queryAndEmit.call(this, sql, 'renameTrigger')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('dropTrigger', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.createFunction = function(functionName, params, returnType, language, body, options) {
var sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, options);
if (sql){
return queryAndEmit.call(this, sql, 'createFunction')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('createFunction', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.dropFunction = function(functionName, params) {
var sql = this.QueryGenerator.dropFunction(functionName, params);
if (sql){
return queryAndEmit.call(this, sql, 'dropFunction')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('dropFunction', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.renameFunction = function(oldFunctionName, params, newFunctionName) {
var sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName);
if (sql){
return queryAndEmit.call(this, sql, 'renameFunction')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('renameFunction', null)
emitter.emit('success')
}).run()
}
}
// Helper methods useful for querying
/**
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!