sequelize
Version:
Multi dialect ORM for Node.JS/io.js
479 lines (405 loc) • 14.5 kB
JavaScript
;
var DataTypes = require('./data-types')
, SqlString = require('./sql-string')
, _ = require('lodash').runInContext() // Prevent anyone messing with template settings by creating a fresh copy
, parameterValidator = require('./utils/parameter-validator')
, inflection = require('inflection')
, uuid = require('uuid')
, deprecate = require('depd')('Utils')
, primitives = ['string', 'number', 'boolean'];
var Utils = module.exports = {
inflection: inflection,
_: _,
camelizeIf: function(string, condition) {
var result = string;
if (condition) {
result = Utils.camelize(string);
}
return result;
},
underscoredIf: function(string, condition) {
var result = string;
if (condition) {
result = inflection.underscore(string);
}
return result;
},
isPrimitive: function (val) {
return primitives.indexOf(typeof val) !== -1;
},
// Same concept as _.merge, but don't overwrite properties that have already been assigned
mergeDefaults: function (a, b) {
return _.mergeWith(a, b, function (objectValue, sourceValue) {
// If it's an object, let _ handle it this time, we will be called again for each property
if (!this._.isPlainObject(objectValue) && objectValue !== undefined) {
return objectValue;
}
}.bind(this));
},
// An alternative to _.merge, which doesn't clone its arguments
// Cloning is a bad idea because options arguments may contain references to sequelize
// models - which again reference database libs which don't like to be cloned (in particular pg-native)
merge: function () {
var result = {};
Array.prototype.slice.apply(arguments).forEach(function (obj) {
_.forOwn(obj, function (value, key) {
if (typeof value !== 'undefined') {
if (!result[key]) {
result[key] = value;
} else if (_.isPlainObject(value) && _.isPlainObject(result[key])) {
result[key] = Utils.merge(result[key], value);
} else if (Array.isArray(value) && Array.isArray(result[key])) {
result[key] = value.concat(result[key]);
} else {
result[key] = value;
}
}
});
});
return result;
},
lowercaseFirst: function (s) {
return s[0].toLowerCase() + s.slice(1);
},
uppercaseFirst: function (s) {
return s[0].toUpperCase() + s.slice(1);
},
spliceStr: function (str, index, count, add) {
return str.slice(0, index) + add + str.slice(index + count);
},
camelize: function(str){
return str.trim().replace(/[-_\s]+(.)?/g, function(match, c){ return c.toUpperCase(); });
},
format: function(arr, dialect) {
var timeZone = null;
// Make a clone of the array beacuse format modifies the passed args
return SqlString.format(arr[0], arr.slice(1), timeZone, dialect);
},
formatNamedParameters: function(sql, parameters, dialect) {
var timeZone = null;
return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect);
},
cloneDeep: function(obj) {
obj = obj || {};
return _.cloneDeepWith(obj, function (elem) {
// Do not try to customize cloning of arrays or POJOs
if (Array.isArray(elem) || _.isPlainObject(elem)) {
return undefined;
}
// Don't clone stuff that's an object, but not a plain one - fx example sequelize models and instances
if (typeof elem === 'object') {
return elem;
}
// Preserve special data-types like `fn` across clones. _.get() is used for checking up the prototype chain
if (elem && typeof elem.clone === 'function') {
return elem.clone();
}
});
},
/* Expand and normalize finder options */
mapFinderOptions: function(options, Model) {
if (Model._hasVirtualAttributes && Array.isArray(options.attributes)) {
options.attributes.forEach(function (attribute) {
if (Model._isVirtualAttribute(attribute) && Model.rawAttributes[attribute].type.fields) {
options.attributes = options.attributes.concat(Model.rawAttributes[attribute].type.fields);
}
}.bind(Model));
options.attributes = _.without.apply(_, [options.attributes].concat(Model._virtualAttributes));
options.attributes = _.uniq(options.attributes);
}
Utils.mapOptionFieldNames(options, Model);
return options;
},
/* Used to map field names in attributes and where conditions */
mapOptionFieldNames: function(options, Model) {
if (Array.isArray(options.attributes)) {
options.attributes = options.attributes.map(function(attr) {
// Object lookups will force any variable to strings, we don't want that for special objects etc
if (typeof attr !== 'string') return attr;
// Map attributes to aliased syntax attributes
if (Model.rawAttributes[attr] && attr !== Model.rawAttributes[attr].field) {
return [Model.rawAttributes[attr].field, attr];
}
return attr;
});
}
if (options.where && _.isPlainObject(options.where)) {
options.where = Utils.mapWhereFieldNames(options.where, Model);
}
if (Array.isArray(options.order)) {
options.order.forEach(function(oGroup) {
var OrderModel, attr, attrOffset;
if (Array.isArray(oGroup)) {
OrderModel = Model;
// Check if we have ['attr', 'DESC'] or [Model, 'attr', 'DESC']
if (typeof oGroup[oGroup.length - 2] === 'string') {
attrOffset = 2;
// Assume ['attr'], [Model, 'attr'] or [seq.fn('somefn', 1), 'DESC']
} else {
attrOffset = 1;
}
attr = oGroup[oGroup.length - attrOffset];
if (oGroup.length > attrOffset) {
OrderModel = oGroup[oGroup.length - (attrOffset + 1)];
if (OrderModel.model) {
OrderModel = OrderModel.model;
}
}
if (OrderModel.rawAttributes && OrderModel.rawAttributes[attr] && attr !== OrderModel.rawAttributes[attr].field) {
oGroup[oGroup.length - attrOffset] = OrderModel.rawAttributes[attr].field;
}
}
});
}
return options;
},
mapWhereFieldNames: function (attributes, Model) {
var attribute
, rawAttribute;
if (attributes) {
for (attribute in attributes) {
rawAttribute = Model.rawAttributes[attribute];
if (rawAttribute && rawAttribute.field !== rawAttribute.fieldName) {
attributes[rawAttribute.field] = attributes[attribute];
delete attributes[attribute];
}
if (_.isPlainObject(attributes[attribute])) {
attributes[attribute] = Utils.mapOptionFieldNames({
where: attributes[attribute]
}, Model).where;
}
if (Array.isArray(attributes[attribute])) {
attributes[attribute] = attributes[attribute].map(function (where) {
if (_.isPlainObject(where)) {
return Utils.mapWhereFieldNames(where, Model);
}
return where;
});
}
}
}
return attributes;
},
/* Used to map field names in values */
mapValueFieldNames: function (dataValues, fields, Model) {
var values = {};
fields.forEach(function(attr) {
if (dataValues[attr] !== undefined && !Model._isVirtualAttribute(attr)) {
// Field name mapping
if (Model.rawAttributes[attr] && Model.rawAttributes[attr].field && Model.rawAttributes[attr].field !== attr) {
values[Model.rawAttributes[attr].field] = dataValues[attr];
} else {
values[attr] = dataValues[attr];
}
}
});
return values;
},
isColString: function(value) {
return typeof value === 'string' && value.substr(0, 1) === '$' && value.substr(value.length - 1, 1) === '$';
},
argsArePrimaryKeys: function(args, primaryKeys) {
var result = (args.length === Object.keys(primaryKeys).length);
if (result) {
Utils._.each(args, function(arg) {
if (result) {
if (['number', 'string'].indexOf(typeof arg) !== -1) {
result = true;
} else {
result = (arg instanceof Date) || Buffer.isBuffer(arg);
}
}
});
}
return result;
},
canTreatArrayAsAnd: function(arr) {
return arr.reduce(function(treatAsAnd, arg) {
if (treatAsAnd) {
return treatAsAnd;
} else {
return Utils._.isPlainObject(arg);
}
}, false);
},
combineTableNames: function(tableName1, tableName2) {
return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1);
},
singularize: function(s) {
return inflection.singularize(s);
},
pluralize: function(s) {
return inflection.pluralize(s);
},
removeCommentsFromFunctionString: function(s) {
s = s.replace(/\s*(\/\/.*)/g, '');
s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '');
return s;
},
toDefaultValue: function(value) {
if (typeof value === 'function') {
var tmp = value();
if (tmp instanceof DataTypes.ABSTRACT) {
return tmp.toSql();
} else {
return tmp;
}
} else if (value instanceof DataTypes.UUIDV1) {
return uuid.v1();
} else if (value instanceof DataTypes.UUIDV4) {
return uuid.v4();
} else if (value instanceof DataTypes.NOW) {
return Utils.now();
} else if(_.isPlainObject(value) || _.isArray(value)) {
return _.clone(value);
} else {
return value;
}
},
/**
* Determine if the default value provided exists and can be described
* in a db schema using the DEFAULT directive.
*
* @param {*} value Any default value.
* @return {boolean} yes / no.
*/
defaultValueSchemable: function(value) {
if (typeof value === 'undefined') { return false; }
// TODO this will be schemable when all supported db
// have been normalized for this case
if (value instanceof DataTypes.NOW) { return false; }
if (value instanceof DataTypes.UUIDV1 || value instanceof DataTypes.UUIDV4) { return false; }
if (_.isFunction(value)) {
return false;
}
return true;
},
removeNullValuesFromHash: function(hash, omitNull, options) {
var result = hash;
options = options || {};
options.allowNull = options.allowNull || [];
if (omitNull) {
var _hash = {};
Utils._.forIn(hash, function(val, key) {
if (options.allowNull.indexOf(key) > -1 || key.match(/Id$/) || ((val !== null) && (val !== undefined))) {
_hash[key] = val;
}
});
result = _hash;
}
return result;
},
inherit: function(SubClass, SuperClass) {
if (SuperClass.constructor === Function) {
// Normal Inheritance
SubClass.prototype = new SuperClass();
SubClass.prototype.constructor = SubClass;
SubClass.prototype.parent = SuperClass.prototype;
} else {
// Pure Virtual Inheritance
SubClass.prototype = SuperClass;
SubClass.prototype.constructor = SubClass;
SubClass.prototype.parent = SuperClass;
}
return SubClass;
},
stack: function _stackGrabber() {
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function(_, stack) { return stack; };
var err = new Error();
Error.captureStackTrace(err, _stackGrabber);
var errStack = err.stack;
Error.prepareStackTrace = orig;
return errStack;
},
sliceArgs: function (args, begin) {
begin = begin || 0;
var tmp = new Array(args.length - begin);
for (var i = begin; i < args.length; ++i) {
tmp[i - begin] = args[i];
}
return tmp;
},
now: function(dialect) {
var now = new Date();
if (['postgres', 'sqlite'].indexOf(dialect) === -1) {
now.setMilliseconds(0);
}
return now;
},
tick: function(func) {
var tick = (global.hasOwnProperty('setImmediate') ? global.setImmediate : process.nextTick);
tick(func);
},
// Note: Use the `quoteIdentifier()` and `escape()` methods on the
// `QueryInterface` instead for more portable code.
TICK_CHAR: '`',
addTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR;
return tickChar + Utils.removeTicks(s, tickChar) + tickChar;
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR;
return s.replace(new RegExp(tickChar, 'g'), '');
},
/*
* Utility functions for representing SQL functions, and columns that should be escaped.
* Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead.
*/
fn: function(fn, args) {
this.fn = fn;
this.args = args;
},
col: function(col) {
if (arguments.length > 1) {
col = this.sliceArgs(arguments);
}
this.col = col;
},
cast: function(val, type) {
this.val = val;
this.type = (type || '').trim();
},
literal: function(val) {
this.val = val;
},
json: function(conditionsOrPath, value) {
if (Utils._.isObject(conditionsOrPath)) {
this.conditions = conditionsOrPath;
} else {
this.path = conditionsOrPath;
if (value) {
this.value = value;
}
}
},
where: function(attribute, comparator, logic) {
if (logic === undefined) {
logic = comparator;
comparator = '=';
}
this.attribute = attribute;
this.comparator = comparator;
this.logic = logic;
},
validateParameter: parameterValidator,
formatReferences: function (obj) {
if (!_.isPlainObject(obj.references)) {
deprecate('Non-object references property found. Support for that will be removed in version 4. Expected { references: { model: "value", key: "key" } } instead of { references: "value", referencesKey: "key" }.');
obj.references = { model: obj.references, key: obj.referencesKey, deferrable: obj.referencesDeferrable };
obj.referencesKey = undefined;
obj.referencesDeferrable = undefined;
}
return obj;
}
};
Utils.where.prototype._isSequelizeMethod =
Utils.literal.prototype._isSequelizeMethod =
Utils.cast.prototype._isSequelizeMethod =
Utils.fn.prototype._isSequelizeMethod =
Utils.col.prototype._isSequelizeMethod =
Utils.json.prototype._isSequelizeMethod = true;
Utils.fn.prototype.clone = function() {
return new Utils.fn(this.fn, this.args);
};
Utils.Promise = require('./promise');