query.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. var Utils = require('../../utils')
  2. , CustomEventEmitter = require("../../emitters/custom-event-emitter")
  3. , Dot = require('dottie')
  4. , _ = require('lodash')
  5. , QueryTypes = require('../../query-types')
  6. module.exports = (function() {
  7. var AbstractQuery = function(database, sequelize, callee, options) {}
  8. /**
  9. Inherit from CustomEventEmitter
  10. */
  11. Utils.inherit(AbstractQuery, CustomEventEmitter)
  12. /**
  13. * Execute the passed sql query.
  14. *
  15. * Examples:
  16. *
  17. * query.run('SELECT 1')
  18. *
  19. * @param {String} sql - The SQL query which should be executed.
  20. * @api public
  21. */
  22. AbstractQuery.prototype.run = function(sql) {
  23. throw new Error("The run method wasn't overwritten!")
  24. }
  25. /**
  26. * Check the logging option of the instance and print deprecation warnings.
  27. *
  28. * @return {void}
  29. */
  30. AbstractQuery.prototype.checkLoggingOption = function() {
  31. if (this.options.logging === true) {
  32. console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log')
  33. this.options.logging = console.log
  34. }
  35. if (this.options.logging === console.log) {
  36. // using just console.log will break in node < 0.6
  37. this.options.logging = function(s) { console.log(s) }
  38. }
  39. }
  40. /**
  41. * High level function that handles the results of a query execution.
  42. *
  43. *
  44. * Example:
  45. * query.formatResults([
  46. * {
  47. * id: 1, // this is from the main table
  48. * attr2: 'snafu', // this is from the main table
  49. * Tasks.id: 1, // this is from the associated table
  50. * Tasks.title: 'task' // this is from the associated table
  51. * }
  52. * ])
  53. *
  54. * @param {Array} data - The result of the query execution.
  55. */
  56. AbstractQuery.prototype.formatResults = function(data) {
  57. var result = this.callee
  58. if (isInsertQuery.call(this, data)) {
  59. handleInsertQuery.call(this, data)
  60. }
  61. if (isSelectQuery.call(this)) {
  62. result = handleSelectQuery.call(this, data)
  63. } else if (isShowTableQuery.call(this)) {
  64. result = handleShowTableQuery.call(this, data)
  65. } else if (isShowOrDescribeQuery.call(this)) {
  66. result = data
  67. if (this.sql.toLowerCase().indexOf('describe') === 0) {
  68. result = {}
  69. data.forEach(function(_result) {
  70. result[_result.Field] = {
  71. type: _result.Type.toUpperCase(),
  72. allowNull: (_result.Null === 'YES'),
  73. defaultValue: _result.Default
  74. }
  75. })
  76. } else if (this.sql.toLowerCase().indexOf('show index from') === 0) {
  77. result = Utils._.uniq(result.map(function(result) {
  78. return {
  79. name: result.Key_name,
  80. tableName: result.Table,
  81. unique: (result.Non_unique !== 1)
  82. }
  83. }), false, function(row) {
  84. return row.name
  85. })
  86. }
  87. } else if (isCallQuery.call(this)) {
  88. result = data[0]
  89. } else if (isBulkUpateQuery.call(this) || isBulkDeleteQuery.call(this)) {
  90. result = data.affectedRows
  91. }
  92. return result
  93. }
  94. /**
  95. * This function is a wrapper for private methods.
  96. *
  97. * @param {String} fctName The name of the private method.
  98. *
  99. */
  100. AbstractQuery.prototype.send = function(fctName/*, arg1, arg2, arg3, ...*/) {
  101. var args = Array.prototype.slice.call(arguments).slice(1)
  102. return eval(fctName).apply(this, args)
  103. }
  104. /**
  105. * Get the attributes of an insert query, which contains the just inserted id.
  106. *
  107. * @return {String} The field name.
  108. */
  109. AbstractQuery.prototype.getInsertIdField = function() {
  110. return 'insertId'
  111. }
  112. /////////////
  113. // private //
  114. /////////////
  115. /**
  116. * Iterate over all known tables and search their names inside the sql query.
  117. * This method will also check association aliases ('as' option).
  118. *
  119. * @param {String} attribute An attribute of a SQL query. (?)
  120. * @return {String} The found tableName / alias.
  121. */
  122. var findTableNameInAttribute = function(attribute) {
  123. if (!this.options.include) {
  124. return null
  125. }
  126. if (!this.options.includeNames) {
  127. this.options.includeNames = this.options.include.map(function(include) {
  128. return include.as
  129. })
  130. }
  131. var tableNames = this.options.includeNames.filter(function(include) {
  132. return attribute.indexOf(include + '.') === 0
  133. })
  134. if (tableNames.length === 1) {
  135. return tableNames[0]
  136. } else {
  137. return null
  138. }
  139. }
  140. var isInsertQuery = function(results, metaData) {
  141. var result = true
  142. // is insert query if sql contains insert into
  143. result = result && (this.sql.toLowerCase().indexOf('insert into') === 0)
  144. // is insert query if no results are passed or if the result has the inserted id
  145. result = result && (!results || results.hasOwnProperty(this.getInsertIdField()))
  146. // is insert query if no metadata are passed or if the metadata has the inserted id
  147. result = result && (!metaData || metaData.hasOwnProperty(this.getInsertIdField()))
  148. return result
  149. }
  150. var handleInsertQuery = function(results, metaData) {
  151. if (this.callee) {
  152. // add the inserted row id to the instance
  153. var autoIncrementField = this.callee.__factory.autoIncrementField
  154. , id = null
  155. id = id || (results && results[this.getInsertIdField()])
  156. id = id || (metaData && metaData[this.getInsertIdField()])
  157. this.callee[autoIncrementField] = id
  158. }
  159. }
  160. var isShowTableQuery = function() {
  161. return (this.sql.toLowerCase().indexOf('show tables') === 0)
  162. }
  163. var handleShowTableQuery = function(results) {
  164. return Utils._.flatten(results.map(function(resultSet) {
  165. return Utils._.values(resultSet)
  166. }))
  167. }
  168. var isSelectQuery = function() {
  169. return this.options.type === QueryTypes.SELECT
  170. }
  171. var isBulkUpateQuery = function() {
  172. return this.options.type === QueryTypes.BULKUPDATE
  173. }
  174. var isBulkDeleteQuery = function() {
  175. return this.options.type === QueryTypes.BULKDELETE
  176. }
  177. var isUpdateQuery = function() {
  178. return (this.sql.toLowerCase().indexOf('update') === 0)
  179. }
  180. var handleSelectQuery = function(results) {
  181. var result = null
  182. // Raw queries
  183. if (this.options.raw) {
  184. result = results.map(function(result) {
  185. var o = {}
  186. for (var key in result) {
  187. if (result.hasOwnProperty(key)) {
  188. o[key] = result[key]
  189. }
  190. }
  191. return o
  192. })
  193. result = result.map(Dot.transform)
  194. // Queries with include
  195. } else if (this.options.hasJoin === true) {
  196. results = groupJoinData(results, {
  197. daoFactory: this.callee,
  198. includeMap: this.options.includeMap,
  199. includeNames: this.options.includeNames
  200. }, {
  201. checkExisting: this.options.hasMultiAssociation
  202. })
  203. result = results.map(function(result) {
  204. return this.callee.build(result, {
  205. isNewRecord: false,
  206. isDirty: false,
  207. include:this.options.include,
  208. includeNames: this.options.includeNames,
  209. includeMap: this.options.includeMap,
  210. includeValidated: true,
  211. raw: true
  212. })
  213. }.bind(this))
  214. } else if (this.options.hasJoinTableModel === true) {
  215. result = results.map(function(result) {
  216. result = Dot.transform(result)
  217. var joinTableData = result[this.options.joinTableModel.name]
  218. , joinTableDAO = this.options.joinTableModel.build(joinTableData, { isNewRecord: false, isDirty: false })
  219. , mainDao
  220. delete result[this.options.joinTableModel.name]
  221. mainDao = this.callee.build(result, { isNewRecord: false, isDirty: false })
  222. mainDao[this.options.joinTableModel.name] = joinTableDAO
  223. return mainDao
  224. }.bind(this))
  225. // Regular queries
  226. } else {
  227. result = results.map(function(result) {
  228. return this.callee.build(result, { isNewRecord: false, isDirty: false, raw: true })
  229. }.bind(this))
  230. }
  231. // return the first real model instance if options.plain is set (e.g. Model.find)
  232. if (this.options.plain) {
  233. result = (result.length === 0) ? null : result[0]
  234. }
  235. return result
  236. }
  237. var isShowOrDescribeQuery = function() {
  238. var result = false
  239. result = result || (this.sql.toLowerCase().indexOf('show') === 0)
  240. result = result || (this.sql.toLowerCase().indexOf('describe') === 0)
  241. return result
  242. }
  243. var isCallQuery = function() {
  244. var result = false
  245. result = result || (this.sql.toLowerCase().indexOf('call') === 0)
  246. return result
  247. }
  248. /**
  249. The function takes the result of the query execution and groups
  250. the associated data by the callee.
  251. Example:
  252. groupJoinData([
  253. {
  254. some: 'data',
  255. id: 1,
  256. association: { foo: 'bar', id: 1 }
  257. }, {
  258. some: 'data',
  259. id: 1,
  260. association: { foo: 'bar', id: 2 }
  261. }, {
  262. some: 'data',
  263. id: 1,
  264. association: { foo: 'bar', id: 3 }
  265. }
  266. ])
  267. Result:
  268. Something like this:
  269. [
  270. {
  271. some: 'data',
  272. id: 1,
  273. association: [
  274. { foo: 'bar', id: 1 },
  275. { foo: 'bar', id: 2 },
  276. { foo: 'bar', id: 3 }
  277. ]
  278. }
  279. ]
  280. */
  281. // includeOptions are 'level'-specific where options is a general directive
  282. var groupJoinData = function(data, includeOptions, options) {
  283. var results = []
  284. , existingResult
  285. , calleeData
  286. , child
  287. , calleeDataIgnore = ['__children']
  288. , parseChildren = function(result) {
  289. _.each(result.__children, function (children, key) {
  290. result[key] = groupJoinData(children, (includeOptions.includeMap && includeOptions.includeMap[key]), options)
  291. })
  292. delete result.__children
  293. },
  294. primaryKeyAttribute
  295. // Identify singular primaryKey attribute for equality check (if possible)
  296. if (includeOptions.daoFactory.primaryKeyAttributes.length === 1) {
  297. primaryKeyAttribute = includeOptions.daoFactory.primaryKeyAttributes[0]
  298. } else if (includeOptions.daoFactory.rawAttributes.id) {
  299. primaryKeyAttribute = 'id'
  300. }
  301. // Ignore all include keys on main data
  302. if (includeOptions.includeNames) {
  303. calleeDataIgnore = calleeDataIgnore.concat(includeOptions.includeNames)
  304. }
  305. data.forEach(function (row) {
  306. row = Dot.transform(row)
  307. calleeData = _.omit(row, calleeDataIgnore)
  308. // If there are :M associations included we need to see if the main result of the row has already been identified
  309. existingResult = options.checkExisting && _.find(results, function (result) {
  310. // If we can, detect equality on the singular primary key
  311. if (primaryKeyAttribute) {
  312. return result[primaryKeyAttribute] === calleeData[primaryKeyAttribute]
  313. }
  314. // If we can't identify on a singular primary key, do a full row equality check
  315. return Utils._.isEqual(_.omit(result, calleeDataIgnore), calleeData)
  316. })
  317. if (!existingResult) {
  318. results.push(existingResult = calleeData)
  319. }
  320. for (var attrName in row) {
  321. if (row.hasOwnProperty(attrName)) {
  322. // Child if object, and is an child include
  323. child = Object(row[attrName]) === row[attrName] && includeOptions.includeMap && includeOptions.includeMap[attrName]
  324. if (child) {
  325. // Make sure nested object is available
  326. if (!existingResult.__children) {
  327. existingResult.__children = {}
  328. }
  329. if (!existingResult.__children[attrName]) {
  330. existingResult.__children[attrName] = []
  331. }
  332. existingResult.__children[attrName].push(row[attrName])
  333. }
  334. }
  335. }
  336. // parseChildren in same loop if no duplicate values are possible
  337. if (!options.checkExisting) {
  338. parseChildren(existingResult)
  339. }
  340. })
  341. // parseChildren after row parsing if duplicate values are possible
  342. if (options.checkExisting) {
  343. results.forEach(parseChildren)
  344. }
  345. return results
  346. }
  347. return AbstractQuery
  348. })()