diff --git a/lib/sql.js b/lib/sql.js index c9345a7..b8866d9 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -54,6 +54,209 @@ SQLConnector.prototype.invokeSuper = function(methodName) { return superMethod.apply(this, args); }; +/** + * 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 + * @param {Function} [cb] The callback function + */ +SQLConnector.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, FKs) { + 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 + */ +SQLConnector.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) { + if (err) { + return cb && cb(err); + } + var actual = (changes.length === 0); + cb && cb(null, actual); + }); +}; + + +SQLConnector.prototype.getAddModifyColumns = function(model, fields) { + throw new Error(g.f('{{getAddModifyColumns()}} must be implemented by the connector')); +}; + +SQLConnector.prototype.getDropColumns = function(model, fields) { + var sql = []; + var self = this; + sql = sql.concat(self.getColumnsToDrop(model, fields)); + return sql; +}; + +SQLConnector.prototype.getColumnsToDrop = function(model, fields) { + throw new Error(g.f('{{getColumnsToDrop()}} must be implemented by the connector')); +}; + +SQLConnector.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; +}; + +SQLConnector.prototype.addPropertyToActual = function(model, propName) { + var self = this; + var sqlCommand = self.columnEscaped(model, propName) + + ' ' + self.columnDataType(model, propName) + + (self.isNullable(self.getPropertyDefinition(model, propName)) ? + '' : ' NOT NULL'); + return sqlCommand; +}; + +SQLConnector.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 || prop.limit; + if (colType && colLength) { + return colType + '(' + colLength + ')'; + } + return this.buildColumnType(prop); +}; + +SQLConnector.prototype.buildColumnType = function(property) { + throw new Error(g.f('{{buildColumnType()}} must be implemented by the connector')); +}; + +SQLConnector.prototype.propertyHasNotBeenDeleted = function(model, propName) { + return !!this.getModelDefinition(model).properties[propName]; +}; + +SQLConnector.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; + }); + self.execute(thisQuery, cb); + } +}; + +/** + * Alters a table + * @param {String} model The model name + * @param {Object} fields Fields of the table + * @param {Object} indexes Indexes of the table + * @param {Function} cb The callback function + */ +SQLConnector.prototype.alterTable = function(model, fields, indexes, cb) { + throw new Error(g.f('{{alterTable()}} must be implemented by the connector')); +}; + +/** + * Get the status of a table + * @param {String} model The model name + * @param {Function} cb The callback function + */ +SQLConnector.prototype.getTableStatus = function(model, cb) { + var fields, indexes; + var self = this; + + this.showFields(model, function(err, data) { + if (err) return cb(err); + fields = data; + + self.showIndexes(model, function(err, data) { + if (err) return cb(err); + indexes = data; + + if (fields && indexes) + return cb(null, fields, indexes); + }); + }); +}; + +/** + * Get fields from a table + * @param {String} model The model name + * @param {Function} cb The callback function + */ +SQLConnector.prototype.showFields = function(model, cb) { + throw new Error(g.f('{{showFields()}} must be implemented by the connector')); +}; + +/** + * Get indexes from a table + * @param {String} model The model name + * @param {Function} cb The callback function + */ +SQLConnector.prototype.showIndexes = function(model, cb) { + throw new Error(g.f('{{showIndexes()}} must be implemented by the connector')); +}; + /** * Get types associated with the connector * Returns {String[]} The types for the connector