var util = require("util") , DataTypes = require("./data-types") , SqlString = require("./sql-string") , lodash = require("lodash") , _string = require('underscore.string') , ParameterValidator = require('./utils/parameter-validator') , uuid = require('node-uuid') var Utils = module.exports = { _: (function() { var _ = lodash , _s = _string _.mixin(_s.exports()) _.mixin({ includes: _s.include, camelizeIf: function(string, condition) { var result = string if (condition) { result = _.camelize(string) } return result }, underscoredIf: function(string, condition) { var result = string if (condition) { result = _.underscored(string) } return result }, /* * Returns an array with some falsy values removed. The values null, "", undefined and NaN are considered falsey. */ compactLite: function(array) { var index = -1, length = array ? array.length : 0, result = []; while (++index < length) { var value = array[index]; if (typeof value === "boolean" || value === 0 || value) { result.push(value); } } return result; } }) return _ })(), addEventEmitter: function(_class) { util.inherits(_class, require('events').EventEmitter) }, 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) }, injectScope: function(scope, merge) { var self = this scope = scope || {} self.scopeObj = self.scopeObj || {} if (Array.isArray(scope.where)) { self.scopeObj.where = self.scopeObj.where || [] self.scopeObj.where.push(scope.where) return true } if (typeof scope.order === "string") { self.scopeObj.order = self.scopeObj.order || [] self.scopeObj.order[self.scopeObj.order.length] = scope.order } // Limit and offset are *always* merged. if (!!scope.limit) { self.scopeObj.limit = scope.limit } if (!!scope.offset) { self.scopeObj.offset = scope.offset } // Where objects are a mixed variable. Possible values are arrays, strings, and objects if (!!scope.where) { // Begin building our scopeObj self.scopeObj.where = self.scopeObj.where || [] // Reset if we're merging! if (merge === true && !!scope.where && !!self.scopeObj.where) { var scopeKeys = Object.keys(scope.where) self.scopeObj.where = self.scopeObj.where.map(function(scopeObj) { if (!Array.isArray(scopeObj) && typeof scopeObj === "object") { return lodash.omit.apply(undefined, [scopeObj].concat(scopeKeys)) } else { return scopeObj } }).filter(function(scopeObj) { return !lodash.isEmpty(scopeObj) }) self.scopeObj.where = self.scopeObj.where.concat(scope.where) } if (Array.isArray(scope.where)) { self.scopeObj.where.push(scope.where) } else if (typeof scope.where === "object") { Object.keys(scope.where).forEach(function(){ self.scopeObj.where.push(scope.where) }) } else { // Assume the value is a string self.scopeObj.where.push([scope.where]) } } if (!!self.scopeObj.where) { self.scopeObj.where = lodash.uniq(self.scopeObj.where) } }, // smartWhere can accept an array of {where} objects, or a single {where} object. // The smartWhere function breaks down the collection of where objects into a more // centralized object for each column so we can avoid duplicates // e.g. WHERE username='dan' AND username='dan' becomes WHERE username='dan' // All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column // This function will hopefully provide more functionality to sequelize in the future. // tl;dr It's a nice way to dissect a collection of where objects and compress them into one object smartWhere: function(whereArg, dialect) { var self = this , _where = {} , logic , type (Array.isArray(whereArg) ? whereArg : [whereArg]).forEach(function(where) { // If it's an array we're already good... / it's in a format that can't be broken down further // e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello'] if (Array.isArray(where)) { _where._ = where._ || {queries: [], bindings: []} _where._.queries[_where._.queries.length] = where[0] if (where.length > 1) { var values = where.splice(1) if (dialect === "sqlite") { values.forEach(function(v, i) { if (typeof v === "boolean") { values[i] = (v === true ? 1 : 0) } }) } _where._.bindings = _where._.bindings.concat(values) } } else if (typeof where === "object") { // First iteration is trying to compress IN and NOT IN as much as possible... // .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?) Object.keys(where).forEach(function(i) { if (Array.isArray(where[i])) { where[i] = { in: where[i] } } }) // Build our smart object Object.keys(where).forEach(function(i) { type = typeof where[i] _where[i] = _where[i] || {} if (where[i] === null) { // skip nulls } else if (Array.isArray(where[i])) { _where[i].in = _where[i].in || [] _where[i].in.concat(where[i]) } else if (Utils.isHash(where[i])) { Object.keys(where[i]).forEach(function(ii) { logic = self.getWhereLogic(ii, where[i][ii]); switch(logic) { case 'IN': _where[i].in = _where[i].in || [] _where[i].in = _where[i].in.concat(where[i][ii]); break case 'NOT': _where[i].not = _where[i].not || [] _where[i].not = _where[i].not.concat(where[i][ii]); break case 'BETWEEN': _where[i].between = _where[i].between || [] _where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]] break case 'NOT BETWEEN': _where[i].nbetween = _where[i].nbetween || [] _where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]] break case 'JOIN': _where[i].joined = _where[i].joined || [] _where[i].joined[_where[i].joined.length] = where[i][ii] break default: _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []} _where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?' _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii]) } }) } else if (type === "string" || type === "number" || type === "boolean" || Buffer.isBuffer(where[i])) { _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []} if (type === "boolean") { _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect) // sqlite is special } else { _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?' _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i]) } } }) } }) return _where }, // Converts {smart where} object(s) into an array that's friendly for Utils.format() // NOTE: Must be applied/called from the QueryInterface compileSmartWhere: function(obj, dialect) { var self = this , whereArgs = [] , text = [] , columnName if (typeof obj !== "object") { return obj } for (var column in obj) { if (column === "_") { text[text.length] = obj[column].queries.join(' AND ') if (obj[column].bindings.length > 0) { whereArgs = whereArgs.concat(obj[column].bindings) } } else { Object.keys(obj[column]).forEach(function(condition) { columnName = self.QueryInterface.quoteIdentifiers(column) switch(condition) { case 'in': text[text.length] = columnName + ' IN (' + obj[column][condition].map(function(){ return '?' }) + ')' whereArgs = whereArgs.concat(obj[column][condition]) break case 'not': text[text.length] = columnName + ' NOT IN (' + obj[column][condition].map(function(){ return '?' }) + ')' whereArgs = whereArgs.concat(obj[column][condition]) break case 'between': Object.keys(obj[column][condition]).forEach(function(row) { text[text.length] = columnName + ' BETWEEN ? AND ?' whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1]) }) break case 'nbetween': Object.keys(obj[column][condition]).forEach(function(row) { text[text.length] = columnName + ' BETWEEN ? AND ?' whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1]) }) break case 'joined': Object.keys(obj[column][condition]).forEach(function(row) { text[text.length] = columnName + ' = ' + self.QueryInterface.quoteIdentifiers(obj[column][condition][row]) }) break default: // lazy text = text.concat(obj[column].lazy.conditions.map(function(val){ return columnName + ' ' + val })) whereArgs = whereArgs.concat(obj[column].lazy.bindings) } }) } } return Utils._.compactLite([text.join(' AND ')].concat(whereArgs)) }, getWhereLogic: function(logic, val) { switch (logic) { case 'join': return 'JOIN' case 'gte': return '>=' case 'gt': return '>' case 'lte': return '<=' case 'lt': return '<' case 'eq': return '=' case 'ne': return val === null ? 'IS NOT' : '!=' case 'between': case '..': return 'BETWEEN' case 'nbetween': case 'notbetween': case '!..': return 'NOT BETWEEN' case 'in': return 'IN' case 'not': return 'NOT IN' case 'like': return 'LIKE' case 'rlike': return 'RLIKE' case 'nlike': case 'notlike': return 'NOT LIKE' default: return '' } }, isHash: function(obj) { return Utils._.isObject(obj) && !Array.isArray(obj) && !Buffer.isBuffer(obj); }, hasChanged: function(attrValue, value) { //If attribute value is Date, check value as a date if (Utils._.isDate(attrValue) && !Utils._.isDate(value)) { value = new Date(value) } if (Utils._.isDate(attrValue)) { return attrValue.valueOf() !== value.valueOf() } //If both of them are empty, don't set as changed if ((attrValue === undefined || attrValue === null || attrValue === '') && (value === undefined || value === null || value === '')) { return false } return attrValue !== value }, 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 }, combineTableNames: function(tableName1, tableName2) { return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1) }, singularize: function(s, language) { return Utils.Lingo[language || 'en'].isSingular(s) ? s : Utils.Lingo[language || 'en'].singularize(s) }, pluralize: function(s, language) { return Utils.Lingo[language || 'en'].isPlural(s) ? s : Utils.Lingo[language || 'en'].pluralize(s) }, removeCommentsFromFunctionString: function(s) { s = s.replace(/\s*(\/\/.*)/g, '') s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '') return s }, toDefaultValue: function(value) { if (lodash.isFunction(value)) { return value() } else if (value === DataTypes.UUIDV1) { return uuid.v1() } else if (value === DataTypes.UUIDV4) { return uuid.v4() } else if (value === DataTypes.NOW) { return Utils.now() } 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 === DataTypes.NOW) {return false} if (value === DataTypes.UUIDV1 || value === DataTypes.UUIDV4) {return false} if (lodash.isFunction(value)) { return false } return true }, setAttributes: function(hash, identifier, instance, prefix) { prefix = prefix || '' if (this.isHash(identifier)) { this._.each(identifier, function(elem, key) { hash[prefix + key] = Utils._.isString(instance) ? instance : Utils._.isObject(instance) ? instance[elem.key || elem] : null }) } else { hash[prefix + identifier] = Utils._.isString(instance) ? instance : Utils._.isObject(instance) ? instance.id : null } return hash }, removeNullValuesFromHash: function(hash, omitNull, options) { var result = hash options = options || {} options.allowNull = options.allowNull || [] if (omitNull) { var _hash = {} Utils._.each(hash, function(val, key) { if (options.allowNull.indexOf(key) > -1 || key.match(/Id$/) || ((val !== null) && (val !== undefined))) { _hash[key] = val } }) result = _hash } return result }, prependTableNameToHash: function(tableName, hash, quote) { if (tableName) { var _hash = {} for (var key in hash) { if (key instanceof Utils.literal) { _hash[key] = hash[key] } else if (key.indexOf('.') === -1) { if (tableName instanceof Utils.literal) { _hash[tableName + '.' + quote(key)] = hash[key] } else { _hash[tableName + '.' + key] = hash[key] } } else { _hash[key] = hash[key] } } return _hash } else { return hash } }, firstValueOfHash: function(obj) { for (var key in obj) { if (obj.hasOwnProperty(key)) return obj[key] } return null }, 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() { var orig = Error.prepareStackTrace; Error.prepareStackTrace = function(_, stack){ return stack; }; var err = new Error(); Error.captureStackTrace(err, arguments.callee); var stack = err.stack; Error.prepareStackTrace = orig; return stack; }, now: function(dialect) { var now = new Date() if(dialect != "postgres") 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 = Array.prototype.slice.call(arguments); } this.col = col }, cast: function (val, type) { this.val = val this.type = (type || '').trim() }, literal: function (val) { this.val = val }, asIs: function(val) { this.val = val }, and: function(args) { this.args = args }, or: function(args) { this.args = args }, generateUUID: function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8) return v.toString(16) }) }, validateParameter: function(value, expectation, options) { return ParameterValidator.check(value, expectation, options) } } // I know this may seem silly, but this gives us the ability to recognize whether // or not we should be escaping or if we should trust the user. Basically, it // keeps things in perspective and organized. Utils.literal.prototype.toString = function() { return this.val } Utils.asIs.prototype = Utils.literal.prototype Utils.cast.prototype.toString = function(queryGenerator) { if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) { this.val = queryGenerator.escape(this.val) } else { this.val = this.val.toString(queryGenerator) } return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')' } Utils.fn.prototype.toString = function(queryGenerator, parentModel) { return this.fn + '(' + this.args.map(function (arg) { if (arg instanceof Utils.fn || arg instanceof Utils.col) { return arg.toString(queryGenerator, parentModel) } else { return queryGenerator.escape(arg) } }).join(', ') + ')' } Utils.col.prototype.toString = function (queryGenerator, parentModel) { if (Array.isArray(this.col)) { if (!parent) { throw new Error('Cannot call Sequelize.col() with array outside of order / group clause') } } else if (this.col.indexOf('*') === 0) { return '*' } return queryGenerator.quote(this.col, parentModel) } Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter") Utils.QueryChainer = require(__dirname + "/query-chainer") Utils.Lingo = require("lingo")