utils.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. var util = require("util")
  2. , DataTypes = require("./data-types")
  3. , SqlString = require("./sql-string")
  4. , lodash = require("lodash")
  5. , _string = require('underscore.string')
  6. , ParameterValidator = require('./utils/parameter-validator')
  7. , uuid = require('node-uuid')
  8. var Utils = module.exports = {
  9. _: (function() {
  10. var _ = lodash
  11. , _s = _string
  12. _.mixin(_s.exports())
  13. _.mixin({
  14. includes: _s.include,
  15. camelizeIf: function(string, condition) {
  16. var result = string
  17. if (condition) {
  18. result = _.camelize(string)
  19. }
  20. return result
  21. },
  22. underscoredIf: function(string, condition) {
  23. var result = string
  24. if (condition) {
  25. result = _.underscored(string)
  26. }
  27. return result
  28. },
  29. /*
  30. * Returns an array with some falsy values removed. The values null, "", undefined and NaN are considered falsey.
  31. */
  32. compactLite: function(array) {
  33. var index = -1,
  34. length = array ? array.length : 0,
  35. result = [];
  36. while (++index < length) {
  37. var value = array[index];
  38. if (typeof value === "boolean" || value === 0 || value) {
  39. result.push(value);
  40. }
  41. }
  42. return result;
  43. }
  44. })
  45. return _
  46. })(),
  47. addEventEmitter: function(_class) {
  48. util.inherits(_class, require('events').EventEmitter)
  49. },
  50. format: function(arr, dialect) {
  51. var timeZone = null;
  52. // Make a clone of the array beacuse format modifies the passed args
  53. return SqlString.format(arr[0], arr.slice(1), timeZone, dialect)
  54. },
  55. formatNamedParameters: function(sql, parameters, dialect) {
  56. var timeZone = null;
  57. return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect)
  58. },
  59. injectScope: function(scope, merge) {
  60. var self = this
  61. scope = scope || {}
  62. self.scopeObj = self.scopeObj || {}
  63. if (Array.isArray(scope.where)) {
  64. self.scopeObj.where = self.scopeObj.where || []
  65. self.scopeObj.where.push(scope.where)
  66. return true
  67. }
  68. if (typeof scope.order === "string") {
  69. self.scopeObj.order = self.scopeObj.order || []
  70. self.scopeObj.order[self.scopeObj.order.length] = scope.order
  71. }
  72. // Limit and offset are *always* merged.
  73. if (!!scope.limit) {
  74. self.scopeObj.limit = scope.limit
  75. }
  76. if (!!scope.offset) {
  77. self.scopeObj.offset = scope.offset
  78. }
  79. // Where objects are a mixed variable. Possible values are arrays, strings, and objects
  80. if (!!scope.where) {
  81. // Begin building our scopeObj
  82. self.scopeObj.where = self.scopeObj.where || []
  83. // Reset if we're merging!
  84. if (merge === true && !!scope.where && !!self.scopeObj.where) {
  85. var scopeKeys = Object.keys(scope.where)
  86. self.scopeObj.where = self.scopeObj.where.map(function(scopeObj) {
  87. if (!Array.isArray(scopeObj) && typeof scopeObj === "object") {
  88. return lodash.omit.apply(undefined, [scopeObj].concat(scopeKeys))
  89. } else {
  90. return scopeObj
  91. }
  92. }).filter(function(scopeObj) {
  93. return !lodash.isEmpty(scopeObj)
  94. })
  95. self.scopeObj.where = self.scopeObj.where.concat(scope.where)
  96. }
  97. if (Array.isArray(scope.where)) {
  98. self.scopeObj.where.push(scope.where)
  99. }
  100. else if (typeof scope.where === "object") {
  101. Object.keys(scope.where).forEach(function(){
  102. self.scopeObj.where.push(scope.where)
  103. })
  104. } else { // Assume the value is a string
  105. self.scopeObj.where.push([scope.where])
  106. }
  107. }
  108. if (!!self.scopeObj.where) {
  109. self.scopeObj.where = lodash.uniq(self.scopeObj.where)
  110. }
  111. },
  112. // smartWhere can accept an array of {where} objects, or a single {where} object.
  113. // The smartWhere function breaks down the collection of where objects into a more
  114. // centralized object for each column so we can avoid duplicates
  115. // e.g. WHERE username='dan' AND username='dan' becomes WHERE username='dan'
  116. // All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column
  117. // This function will hopefully provide more functionality to sequelize in the future.
  118. // tl;dr It's a nice way to dissect a collection of where objects and compress them into one object
  119. smartWhere: function(whereArg, dialect) {
  120. var self = this
  121. , _where = {}
  122. , logic
  123. , type
  124. (Array.isArray(whereArg) ? whereArg : [whereArg]).forEach(function(where) {
  125. // If it's an array we're already good... / it's in a format that can't be broken down further
  126. // e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello']
  127. if (Array.isArray(where)) {
  128. _where._ = where._ || {queries: [], bindings: []}
  129. _where._.queries[_where._.queries.length] = where[0]
  130. if (where.length > 1) {
  131. var values = where.splice(1)
  132. if (dialect === "sqlite") {
  133. values.forEach(function(v, i) {
  134. if (typeof v === "boolean") {
  135. values[i] = (v === true ? 1 : 0)
  136. }
  137. })
  138. }
  139. _where._.bindings = _where._.bindings.concat(values)
  140. }
  141. }
  142. else if (typeof where === "object") {
  143. // First iteration is trying to compress IN and NOT IN as much as possible...
  144. // .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?)
  145. Object.keys(where).forEach(function(i) {
  146. if (Array.isArray(where[i])) {
  147. where[i] = {
  148. in: where[i]
  149. }
  150. }
  151. })
  152. // Build our smart object
  153. Object.keys(where).forEach(function(i) {
  154. type = typeof where[i]
  155. _where[i] = _where[i] || {}
  156. if (where[i] === null) {
  157. // skip nulls
  158. }
  159. else if (Array.isArray(where[i])) {
  160. _where[i].in = _where[i].in || []
  161. _where[i].in.concat(where[i])
  162. }
  163. else if (Utils.isHash(where[i])) {
  164. Object.keys(where[i]).forEach(function(ii) {
  165. logic = self.getWhereLogic(ii, where[i][ii]);
  166. switch(logic) {
  167. case 'IN':
  168. _where[i].in = _where[i].in || []
  169. _where[i].in = _where[i].in.concat(where[i][ii]);
  170. break
  171. case 'NOT':
  172. _where[i].not = _where[i].not || []
  173. _where[i].not = _where[i].not.concat(where[i][ii]);
  174. break
  175. case 'BETWEEN':
  176. _where[i].between = _where[i].between || []
  177. _where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]]
  178. break
  179. case 'NOT BETWEEN':
  180. _where[i].nbetween = _where[i].nbetween || []
  181. _where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]]
  182. break
  183. case 'JOIN':
  184. _where[i].joined = _where[i].joined || []
  185. _where[i].joined[_where[i].joined.length] = where[i][ii]
  186. break
  187. default:
  188. _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
  189. _where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?'
  190. _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii])
  191. }
  192. })
  193. }
  194. else if (type === "string" || type === "number" || type === "boolean" || Buffer.isBuffer(where[i])) {
  195. _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
  196. if (type === "boolean") {
  197. _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect) // sqlite is special
  198. } else {
  199. _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?'
  200. _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i])
  201. }
  202. }
  203. })
  204. }
  205. })
  206. return _where
  207. },
  208. // Converts {smart where} object(s) into an array that's friendly for Utils.format()
  209. // NOTE: Must be applied/called from the QueryInterface
  210. compileSmartWhere: function(obj, dialect) {
  211. var self = this
  212. , whereArgs = []
  213. , text = []
  214. , columnName
  215. if (typeof obj !== "object") {
  216. return obj
  217. }
  218. for (var column in obj) {
  219. if (column === "_") {
  220. text[text.length] = obj[column].queries.join(' AND ')
  221. if (obj[column].bindings.length > 0) {
  222. whereArgs = whereArgs.concat(obj[column].bindings)
  223. }
  224. } else {
  225. Object.keys(obj[column]).forEach(function(condition) {
  226. columnName = self.QueryInterface.quoteIdentifiers(column)
  227. switch(condition) {
  228. case 'in':
  229. text[text.length] = columnName + ' IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
  230. whereArgs = whereArgs.concat(obj[column][condition])
  231. break
  232. case 'not':
  233. text[text.length] = columnName + ' NOT IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
  234. whereArgs = whereArgs.concat(obj[column][condition])
  235. break
  236. case 'between':
  237. Object.keys(obj[column][condition]).forEach(function(row) {
  238. text[text.length] = columnName + ' BETWEEN ? AND ?'
  239. whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
  240. })
  241. break
  242. case 'nbetween':
  243. Object.keys(obj[column][condition]).forEach(function(row) {
  244. text[text.length] = columnName + ' BETWEEN ? AND ?'
  245. whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
  246. })
  247. break
  248. case 'joined':
  249. Object.keys(obj[column][condition]).forEach(function(row) {
  250. text[text.length] = columnName + ' = ' + self.QueryInterface.quoteIdentifiers(obj[column][condition][row])
  251. })
  252. break
  253. default: // lazy
  254. text = text.concat(obj[column].lazy.conditions.map(function(val){ return columnName + ' ' + val }))
  255. whereArgs = whereArgs.concat(obj[column].lazy.bindings)
  256. }
  257. })
  258. }
  259. }
  260. return Utils._.compactLite([text.join(' AND ')].concat(whereArgs))
  261. },
  262. getWhereLogic: function(logic, val) {
  263. switch (logic) {
  264. case 'join':
  265. return 'JOIN'
  266. case 'gte':
  267. return '>='
  268. case 'gt':
  269. return '>'
  270. case 'lte':
  271. return '<='
  272. case 'lt':
  273. return '<'
  274. case 'eq':
  275. return '='
  276. case 'ne':
  277. return val === null ? 'IS NOT' : '!='
  278. case 'between':
  279. case '..':
  280. return 'BETWEEN'
  281. case 'nbetween':
  282. case 'notbetween':
  283. case '!..':
  284. return 'NOT BETWEEN'
  285. case 'in':
  286. return 'IN'
  287. case 'not':
  288. return 'NOT IN'
  289. case 'like':
  290. return 'LIKE'
  291. case 'rlike':
  292. return 'RLIKE'
  293. case 'nlike':
  294. case 'notlike':
  295. return 'NOT LIKE'
  296. default:
  297. return ''
  298. }
  299. },
  300. isHash: function(obj) {
  301. return Utils._.isObject(obj) && !Array.isArray(obj) && !Buffer.isBuffer(obj);
  302. },
  303. hasChanged: function(attrValue, value) {
  304. //If attribute value is Date, check value as a date
  305. if (Utils._.isDate(attrValue) && !Utils._.isDate(value)) {
  306. value = new Date(value)
  307. }
  308. if (Utils._.isDate(attrValue)) {
  309. return attrValue.valueOf() !== value.valueOf()
  310. }
  311. //If both of them are empty, don't set as changed
  312. if ((attrValue === undefined || attrValue === null || attrValue === '') && (value === undefined || value === null || value === '')) {
  313. return false
  314. }
  315. return attrValue !== value
  316. },
  317. argsArePrimaryKeys: function(args, primaryKeys) {
  318. var result = (args.length == Object.keys(primaryKeys).length)
  319. if (result) {
  320. Utils._.each(args, function(arg) {
  321. if (result) {
  322. if (['number', 'string'].indexOf(typeof arg) !== -1) {
  323. result = true
  324. } else {
  325. result = (arg instanceof Date) || Buffer.isBuffer(arg);
  326. }
  327. }
  328. })
  329. }
  330. return result
  331. },
  332. combineTableNames: function(tableName1, tableName2) {
  333. return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1)
  334. },
  335. singularize: function(s, language) {
  336. return Utils.Lingo[language || 'en'].isSingular(s) ? s : Utils.Lingo[language || 'en'].singularize(s)
  337. },
  338. pluralize: function(s, language) {
  339. return Utils.Lingo[language || 'en'].isPlural(s) ? s : Utils.Lingo[language || 'en'].pluralize(s)
  340. },
  341. removeCommentsFromFunctionString: function(s) {
  342. s = s.replace(/\s*(\/\/.*)/g, '')
  343. s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '')
  344. return s
  345. },
  346. toDefaultValue: function(value) {
  347. if (lodash.isFunction(value)) {
  348. return value()
  349. } else if (value === DataTypes.UUIDV1) {
  350. return uuid.v1()
  351. } else if (value === DataTypes.UUIDV4) {
  352. return uuid.v4()
  353. } else if (value === DataTypes.NOW) {
  354. return Utils.now()
  355. } else {
  356. return value
  357. }
  358. },
  359. /**
  360. * Determine if the default value provided exists and can be described
  361. * in a db schema using the DEFAULT directive.
  362. *
  363. * @param {*} value Any default value.
  364. * @return {boolean} yes / no.
  365. */
  366. defaultValueSchemable: function(value) {
  367. if (typeof value === 'undefined') {return false}
  368. // TODO this will be schemable when all supported db
  369. // have been normalized for this case
  370. if (value === DataTypes.NOW) {return false}
  371. if (value === DataTypes.UUIDV1 || value === DataTypes.UUIDV4) {return false}
  372. if (lodash.isFunction(value)) {
  373. return false
  374. }
  375. return true
  376. },
  377. setAttributes: function(hash, identifier, instance, prefix) {
  378. prefix = prefix || ''
  379. if (this.isHash(identifier)) {
  380. this._.each(identifier, function(elem, key) {
  381. hash[prefix + key] = Utils._.isString(instance) ? instance : Utils._.isObject(instance) ? instance[elem.key || elem] : null
  382. })
  383. } else {
  384. hash[prefix + identifier] = Utils._.isString(instance) ? instance : Utils._.isObject(instance) ? instance.id : null
  385. }
  386. return hash
  387. },
  388. removeNullValuesFromHash: function(hash, omitNull, options) {
  389. var result = hash
  390. options = options || {}
  391. options.allowNull = options.allowNull || []
  392. if (omitNull) {
  393. var _hash = {}
  394. Utils._.each(hash, function(val, key) {
  395. if (options.allowNull.indexOf(key) > -1 || key.match(/Id$/) || ((val !== null) && (val !== undefined))) {
  396. _hash[key] = val
  397. }
  398. })
  399. result = _hash
  400. }
  401. return result
  402. },
  403. prependTableNameToHash: function(tableName, hash, quote) {
  404. if (tableName) {
  405. var _hash = {}
  406. for (var key in hash) {
  407. if (key instanceof Utils.literal) {
  408. _hash[key] = hash[key]
  409. } else if (key.indexOf('.') === -1) {
  410. if (tableName instanceof Utils.literal) {
  411. _hash[tableName + '.' + quote(key)] = hash[key]
  412. } else {
  413. _hash[tableName + '.' + key] = hash[key]
  414. }
  415. } else {
  416. _hash[key] = hash[key]
  417. }
  418. }
  419. return _hash
  420. } else {
  421. return hash
  422. }
  423. },
  424. firstValueOfHash: function(obj) {
  425. for (var key in obj) {
  426. if (obj.hasOwnProperty(key))
  427. return obj[key]
  428. }
  429. return null
  430. },
  431. inherit: function(subClass, superClass) {
  432. if (superClass.constructor == Function) {
  433. // Normal Inheritance
  434. subClass.prototype = new superClass();
  435. subClass.prototype.constructor = subClass;
  436. subClass.prototype.parent = superClass.prototype;
  437. } else {
  438. // Pure Virtual Inheritance
  439. subClass.prototype = superClass;
  440. subClass.prototype.constructor = subClass;
  441. subClass.prototype.parent = superClass;
  442. }
  443. return subClass;
  444. },
  445. stack: function() {
  446. var orig = Error.prepareStackTrace;
  447. Error.prepareStackTrace = function(_, stack){ return stack; };
  448. var err = new Error();
  449. Error.captureStackTrace(err, arguments.callee);
  450. var stack = err.stack;
  451. Error.prepareStackTrace = orig;
  452. return stack;
  453. },
  454. now: function(dialect) {
  455. var now = new Date()
  456. if(dialect != "postgres") now.setMilliseconds(0)
  457. return now
  458. },
  459. tick: function(func) {
  460. var tick = (global.hasOwnProperty('setImmediate') ? global.setImmediate : process.nextTick)
  461. tick(func)
  462. },
  463. // Note: Use the `quoteIdentifier()` and `escape()` methods on the
  464. // `QueryInterface` instead for more portable code.
  465. TICK_CHAR: '`',
  466. addTicks: function(s, tickChar) {
  467. tickChar = tickChar || Utils.TICK_CHAR
  468. return tickChar + Utils.removeTicks(s, tickChar) + tickChar
  469. },
  470. removeTicks: function(s, tickChar) {
  471. tickChar = tickChar || Utils.TICK_CHAR
  472. return s.replace(new RegExp(tickChar, 'g'), "")
  473. },
  474. /*
  475. * Utility functions for representing SQL functions, and columns that should be escaped.
  476. * Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead.
  477. */
  478. fn: function (fn, args) {
  479. this.fn = fn
  480. this.args = args
  481. },
  482. col: function (col) {
  483. if (arguments.length > 1) {
  484. col = Array.prototype.slice.call(arguments);
  485. }
  486. this.col = col
  487. },
  488. cast: function (val, type) {
  489. this.val = val
  490. this.type = (type || '').trim()
  491. },
  492. literal: function (val) {
  493. this.val = val
  494. },
  495. asIs: function(val) {
  496. this.val = val
  497. },
  498. and: function(args) {
  499. this.args = args
  500. },
  501. or: function(args) {
  502. this.args = args
  503. },
  504. generateUUID: function() {
  505. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  506. var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8)
  507. return v.toString(16)
  508. })
  509. },
  510. validateParameter: function(value, expectation, options) {
  511. return ParameterValidator.check(value, expectation, options)
  512. }
  513. }
  514. // I know this may seem silly, but this gives us the ability to recognize whether
  515. // or not we should be escaping or if we should trust the user. Basically, it
  516. // keeps things in perspective and organized.
  517. Utils.literal.prototype.toString = function() {
  518. return this.val
  519. }
  520. Utils.asIs.prototype = Utils.literal.prototype
  521. Utils.cast.prototype.toString = function(queryGenerator) {
  522. if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) {
  523. this.val = queryGenerator.escape(this.val)
  524. } else {
  525. this.val = this.val.toString(queryGenerator)
  526. }
  527. return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')'
  528. }
  529. Utils.fn.prototype.toString = function(queryGenerator, parentModel) {
  530. return this.fn + '(' + this.args.map(function (arg) {
  531. if (arg instanceof Utils.fn || arg instanceof Utils.col) {
  532. return arg.toString(queryGenerator, parentModel)
  533. } else {
  534. return queryGenerator.escape(arg)
  535. }
  536. }).join(', ') + ')'
  537. }
  538. Utils.col.prototype.toString = function (queryGenerator, parentModel) {
  539. if (Array.isArray(this.col)) {
  540. if (!parent) {
  541. throw new Error('Cannot call Sequelize.col() with array outside of order / group clause')
  542. }
  543. } else if (this.col.indexOf('*') === 0) {
  544. return '*'
  545. }
  546. return queryGenerator.quote(this.col, parentModel)
  547. }
  548. Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
  549. Utils.QueryChainer = require(__dirname + "/query-chainer")
  550. Utils.Lingo = require("lingo")