query-generator.js 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. var Utils = require("../../utils")
  2. , SqlString = require("../../sql-string")
  3. , daoFactory = require("../../dao-factory")
  4. module.exports = (function() {
  5. var QueryGenerator = {
  6. addSchema: function(opts) {
  7. throwMethodUndefined('addSchema')
  8. },
  9. /*
  10. Returns a query for creating a table.
  11. Parameters:
  12. - tableName: Name of the new table.
  13. - attributes: An object with containing attribute-attributeType-pairs.
  14. Attributes should have the format:
  15. {attributeName: type, attr2: type2}
  16. --> e.g. {title: 'VARCHAR(255)'}
  17. - options: An object with options.
  18. Defaults: { engine: 'InnoDB', charset: null }
  19. */
  20. createTableQuery: function(tableName, attributes, options) {
  21. throwMethodUndefined('createTableQuery')
  22. },
  23. /*
  24. Returns a query for dropping a table.
  25. */
  26. dropTableQuery: function(tableName, options) {
  27. throwMethodUndefined('dropTableQuery')
  28. },
  29. /*
  30. Returns a rename table query.
  31. Parameters:
  32. - originalTableName: Name of the table before execution.
  33. - futureTableName: Name of the table after execution.
  34. */
  35. renameTableQuery: function(before, after) {
  36. var query = "ALTER TABLE <%= before %> RENAME TO <%= after %>;"
  37. return Utils._.template(query)({
  38. before: this.quoteTable(before),
  39. after: this.quoteTable(after)
  40. })
  41. },
  42. /*
  43. Returns a query, which gets all available table names in the database.
  44. */
  45. showTablesQuery: function() {
  46. throwMethodUndefined('showTablesQuery')
  47. },
  48. /*
  49. Returns a query, which adds an attribute to an existing table.
  50. Parameters:
  51. - tableName: Name of the existing table.
  52. - attributes: A hash with attribute-attributeOptions-pairs.
  53. - key: attributeName
  54. - value: A hash with attribute specific options:
  55. - type: DataType
  56. - defaultValue: A String with the default value
  57. - allowNull: Boolean
  58. */
  59. addColumnQuery: function(tableName, attributes) {
  60. throwMethodUndefined('addColumnQuery')
  61. },
  62. /*
  63. Returns a query, which removes an attribute from an existing table.
  64. Parameters:
  65. - tableName: Name of the existing table
  66. - attributeName: Name of the obsolete attribute.
  67. */
  68. removeColumnQuery: function(tableName, attributeName) {
  69. throwMethodUndefined('removeColumnQuery')
  70. },
  71. /*
  72. Returns a query, which modifies an existing attribute from a table.
  73. Parameters:
  74. - tableName: Name of the existing table.
  75. - attributes: A hash with attribute-attributeOptions-pairs.
  76. - key: attributeName
  77. - value: A hash with attribute specific options:
  78. - type: DataType
  79. - defaultValue: A String with the default value
  80. - allowNull: Boolean
  81. */
  82. changeColumnQuery: function(tableName, attributes) {
  83. throwMethodUndefined('changeColumnQuery')
  84. },
  85. /*
  86. Returns a query, which renames an existing attribute.
  87. Parameters:
  88. - tableName: Name of an existing table.
  89. - attrNameBefore: The name of the attribute, which shall be renamed.
  90. - attrNameAfter: The name of the attribute, after renaming.
  91. */
  92. renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter) {
  93. throwMethodUndefined('renameColumnQuery')
  94. },
  95. /*
  96. Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
  97. */
  98. insertQuery: function(table, valueHash, modelAttributes) {
  99. var query
  100. , valueQuery = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>)"
  101. , emptyQuery = "INSERT INTO <%= table %>"
  102. , fields = []
  103. , values = []
  104. , key
  105. , value
  106. if (this._dialect.supports['DEFAULT VALUES']) {
  107. emptyQuery += " DEFAULT VALUES"
  108. } else if (this._dialect.supports['VALUES ()']) {
  109. emptyQuery += " VALUES ()"
  110. }
  111. if (this._dialect.supports['RETURNING']) {
  112. valueQuery += " RETURNING *"
  113. emptyQuery += " RETURNING *"
  114. }
  115. valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull)
  116. for (key in valueHash) {
  117. if (valueHash.hasOwnProperty(key)) {
  118. value = valueHash[key]
  119. fields.push(this.quoteIdentifier(key))
  120. // SERIALS' can't be NULL in postgresql, use DEFAULT where supported
  121. if (modelAttributes && modelAttributes[key] && modelAttributes[key].autoIncrement === true && !value) {
  122. if (this._dialect.supports['DEFAULT']) {
  123. values.push('DEFAULT')
  124. } else {
  125. values.push(this.escape(null))
  126. }
  127. } else {
  128. values.push(this.escape(value, (modelAttributes && modelAttributes[key]) || undefined))
  129. }
  130. }
  131. }
  132. var replacements = {
  133. table: this.quoteTable(table),
  134. attributes: fields.join(","),
  135. values: values.join(",")
  136. }
  137. query = (replacements.attributes.length ? valueQuery : emptyQuery) + ";"
  138. return Utils._.template(query)(replacements)
  139. },
  140. /*
  141. Returns an insert into command for multiple values.
  142. Parameters: table name + list of hashes of attribute-value-pairs.
  143. */
  144. bulkInsertQuery: function(tableName, attrValueHashes) {
  145. throwMethodUndefined('bulkInsertQuery')
  146. },
  147. /*
  148. Returns an update query.
  149. Parameters:
  150. - tableName -> Name of the table
  151. - values -> A hash with attribute-value-pairs
  152. - where -> A hash with conditions (e.g. {name: 'foo'})
  153. OR an ID as integer
  154. OR a string with conditions (e.g. 'name="foo"').
  155. If you use a string, you have to escape it on your own.
  156. */
  157. updateQuery: function(tableName, attrValueHash, where, options, attributes) {
  158. options = options || {}
  159. attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options)
  160. var query
  161. , values = []
  162. query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
  163. if (this._dialect.supports['RETURNING'] && (options.returning || options.returning === undefined)) {
  164. query += " RETURNING *"
  165. }
  166. for (var key in attrValueHash) {
  167. var value = attrValueHash[key]
  168. values.push(this.quoteIdentifier(key) + "=" + this.escape(value, (!!attributes && !!attributes[key] ? attributes[key] : undefined)))
  169. }
  170. var replacements = {
  171. table: this.quoteTable(tableName),
  172. values: values.join(","),
  173. where: this.getWhereConditions(where)
  174. }
  175. return Utils._.template(query)(replacements)
  176. },
  177. /*
  178. Returns a deletion query.
  179. Parameters:
  180. - tableName -> Name of the table
  181. - where -> A hash with conditions (e.g. {name: 'foo'})
  182. OR an ID as integer
  183. OR a string with conditions (e.g. 'name="foo"').
  184. If you use a string, you have to escape it on your own.
  185. Options:
  186. - limit -> Maximaum count of lines to delete
  187. - truncate -> boolean - whether to use an 'optimized' mechanism (i.e. TRUNCATE) if available,
  188. note that this should not be the default behaviour because TRUNCATE does not
  189. always play nicely (e.g. InnoDB tables with FK constraints)
  190. (@see http://dev.mysql.com/doc/refman/5.6/en/truncate-table.html).
  191. Note that truncate must ignore limit and where
  192. */
  193. deleteQuery: function(tableName, where, options) {
  194. throwMethodUndefined('deleteQuery')
  195. },
  196. /*
  197. Returns a bulk deletion query.
  198. Parameters:
  199. - tableName -> Name of the table
  200. - where -> A hash with conditions (e.g. {name: 'foo'})
  201. OR an ID as integer
  202. OR a string with conditions (e.g. 'name="foo"').
  203. If you use a string, you have to escape it on your own.
  204. */
  205. bulkDeleteQuery: function(tableName, where, options) {
  206. throwMethodUndefined('bulkDeleteQuery')
  207. },
  208. /*
  209. Returns an update query.
  210. Parameters:
  211. - tableName -> Name of the table
  212. - values -> A hash with attribute-value-pairs
  213. - where -> A hash with conditions (e.g. {name: 'foo'})
  214. OR an ID as integer
  215. OR a string with conditions (e.g. 'name="foo"').
  216. If you use a string, you have to escape it on your own.
  217. */
  218. incrementQuery: function(tableName, attrValueHash, where, options) {
  219. attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
  220. var query
  221. , values = []
  222. query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
  223. if (this._dialect.supports['RETURNING']) {
  224. query += " RETURNING *"
  225. }
  226. for (var key in attrValueHash) {
  227. var value = attrValueHash[key]
  228. values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + this.escape(value))
  229. }
  230. options = options || {}
  231. for (var key in options) {
  232. var value = options[key];
  233. values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
  234. }
  235. var replacements = {
  236. table: this.quoteIdentifiers(tableName),
  237. values: values.join(","),
  238. where: this.getWhereConditions(where)
  239. }
  240. return Utils._.template(query)(replacements)
  241. },
  242. /*
  243. Returns an add index query.
  244. Parameters:
  245. - tableName -> Name of an existing table.
  246. - attributes:
  247. An array of attributes as string or as hash.
  248. If the attribute is a hash, it must have the following content:
  249. - attribute: The name of the attribute/column
  250. - length: An integer. Optional
  251. - order: 'ASC' or 'DESC'. Optional
  252. - options:
  253. - indicesType: UNIQUE|FULLTEXT|SPATIAL
  254. - indexName: The name of the index. Default is <tableName>_<attrName1>_<attrName2>
  255. - parser
  256. */
  257. addIndexQuery: function(tableName, attributes, options) {
  258. throwMethodUndefined('addIndexQuery')
  259. },
  260. /*
  261. Returns an show index query.
  262. Parameters:
  263. - tableName: Name of an existing table.
  264. - options:
  265. - database: Name of the database.
  266. */
  267. showIndexQuery: function(tableName, options) {
  268. throwMethodUndefined('showIndexQuery')
  269. },
  270. /*
  271. Returns a remove index query.
  272. Parameters:
  273. - tableName: Name of an existing table.
  274. - indexNameOrAttributes: The name of the index as string or an array of attribute names.
  275. */
  276. removeIndexQuery: function(tableName, indexNameOrAttributes) {
  277. throwMethodUndefined('removeIndexQuery')
  278. },
  279. /*
  280. This method transforms an array of attribute hashes into equivalent
  281. sql attribute definition.
  282. */
  283. attributesToSQL: function(attributes) {
  284. throwMethodUndefined('attributesToSQL')
  285. },
  286. /*
  287. Returns all auto increment fields of a factory.
  288. */
  289. findAutoIncrementField: function(factory) {
  290. throwMethodUndefined('findAutoIncrementField')
  291. },
  292. /*
  293. Quote an object based on its type. This is a more general version of quoteIdentifiers
  294. Strings: should proxy to quoteIdentifiers
  295. Arrays:
  296. * Expects array in the form: [<model> (optional), <model> (optional),... String, String (optional)]
  297. Each <model> can be a daoFactory or an object {model: DaoFactory, as: String}, matching include
  298. * Zero or more models can be included in the array and are used to trace a path through the tree of
  299. included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL
  300. and quotes it.
  301. * If a single string is appended to end of array, it is quoted.
  302. If two strings appended, the 1st string is quoted, the 2nd string unquoted.
  303. Objects:
  304. * If raw is set, that value should be returned verbatim, without quoting
  305. * If fn is set, the string should start with the value of fn, starting paren, followed by
  306. the values of cols (which is assumed to be an array), quoted and joined with ', ',
  307. unless they are themselves objects
  308. * If direction is set, should be prepended
  309. Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could
  310. potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
  311. */
  312. quote: function(obj, parent, force) {
  313. if (Utils._.isString(obj)) {
  314. return this.quoteIdentifiers(obj, force)
  315. } else if (Array.isArray(obj)) {
  316. // loop through array, adding table names of models to quoted
  317. // (checking associations to see if names should be singularised or not)
  318. var tableNames = []
  319. , parentAssociation
  320. , len = obj.length
  321. for (var i = 0; i < len - 1; i++) {
  322. var item = obj[i]
  323. if (Utils._.isString(item) || item instanceof Utils.fn || item instanceof Utils.col || item instanceof Utils.literal || item instanceof Utils.cast || 'raw' in item) {
  324. break
  325. }
  326. var model, as
  327. if (item instanceof daoFactory) {
  328. model = item
  329. } else {
  330. model = item.model
  331. as = item.as
  332. }
  333. // check if model provided is through table
  334. var association
  335. if (!as && parentAssociation && parentAssociation.through === model) {
  336. association = {as: Utils.singularize(model.tableName, model.options.language)}
  337. } else {
  338. // find applicable association for linking parent to this model
  339. association = parent.getAssociation(model, as)
  340. }
  341. if (association) {
  342. tableNames[i] = association.as
  343. parent = model
  344. parentAssociation = association
  345. } else {
  346. tableNames[i] = model.tableName
  347. throw new Error('\'' + tableNames.join('.') + '\' in order / group clause is not valid association')
  348. }
  349. }
  350. // add 1st string as quoted, 2nd as unquoted raw
  351. var sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : '') + this.quote(obj[i], parent, force)
  352. if (i < len - 1) {
  353. sql += ' ' + obj[i + 1]
  354. }
  355. return sql
  356. } else if (obj instanceof Utils.fn || obj instanceof Utils.col || obj instanceof Utils.literal || obj instanceof Utils.cast) {
  357. return obj.toString(this)
  358. } else if (Utils._.isObject(obj) && 'raw' in obj) {
  359. return obj.raw
  360. } else {
  361. throw new Error('Unknown structure passed to order / group: ' + JSON.stringify(obj))
  362. }
  363. },
  364. /*
  365. Create a trigger
  366. */
  367. createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams,
  368. optionsArray) {
  369. throwMethodUndefined('createTrigger')
  370. },
  371. /*
  372. Drop a trigger
  373. */
  374. dropTrigger: function(tableName, triggerName) {
  375. throwMethodUndefined('dropTrigger')
  376. },
  377. /*
  378. Rename a trigger
  379. */
  380. renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
  381. throwMethodUndefined('renameTrigger')
  382. },
  383. /*
  384. Create a function
  385. */
  386. createFunction: function(functionName, params, returnType, language, body, options) {
  387. throwMethodUndefined('createFunction')
  388. },
  389. /*
  390. Drop a function
  391. */
  392. dropFunction: function(functionName, params) {
  393. throwMethodUndefined('dropFunction')
  394. },
  395. /*
  396. Rename a function
  397. */
  398. renameFunction: function(oldFunctionName, params, newFunctionName) {
  399. throwMethodUndefined('renameFunction')
  400. },
  401. /*
  402. Escape an identifier (e.g. a table or attribute name)
  403. */
  404. quoteIdentifier: function(identifier, force) {
  405. throwMethodUndefined('quoteIdentifier')
  406. },
  407. /*
  408. Split an identifier into .-separated tokens and quote each part
  409. */
  410. quoteIdentifiers: function(identifiers, force) {
  411. throwMethodUndefined('quoteIdentifiers')
  412. },
  413. /*
  414. Escape a value (e.g. a string, number or date)
  415. */
  416. escape: function(value, field) {
  417. if (value instanceof Utils.fn || value instanceof Utils.col || value instanceof Utils.literal || value instanceof Utils.cast) {
  418. return value.toString(this)
  419. } else {
  420. return SqlString.escape(value, false, null, this.dialect, field)
  421. }
  422. },
  423. /**
  424. * Generates an SQL query that returns all foreign keys of a table.
  425. *
  426. * @param {String} tableName The name of the table.
  427. * @param {String} schemaName The name of the schema.
  428. * @return {String} The generated sql query.
  429. */
  430. getForeignKeysQuery: function(tableName, schemaName) {
  431. throwMethodUndefined('getForeignKeysQuery')
  432. },
  433. /**
  434. * Generates an SQL query that removes a foreign key from a table.
  435. *
  436. * @param {String} tableName The name of the table.
  437. * @param {String} foreignKey The name of the foreign key constraint.
  438. * @return {String} The generated sql query.
  439. */
  440. dropForeignKeyQuery: function(tableName, foreignKey) {
  441. throwMethodUndefined('dropForeignKeyQuery')
  442. },
  443. /*
  444. Returns a query for selecting elements in the table <tableName>.
  445. Options:
  446. - attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
  447. - where -> A hash with conditions (e.g. {name: 'foo'})
  448. OR an ID as integer
  449. OR a string with conditions (e.g. 'name="foo"').
  450. If you use a string, you have to escape it on your own.
  451. - order -> e.g. 'id DESC'
  452. - group
  453. - limit -> The maximum count you want to get.
  454. - offset -> An offset value to start from. Only useable with limit!
  455. */
  456. selectQuery: function(tableName, options, factory) {
  457. // Enter and change at your own peril -- Mick Hansen
  458. options = options || {}
  459. var table = null
  460. , self = this
  461. , query
  462. , limit = options.limit
  463. , mainQueryItems = []
  464. , mainAttributes = options.attributes
  465. , mainJoinQueries = []
  466. // We'll use a subquery if we have hasMany associations and a limit and a filtered/required association
  467. , subQuery = limit && (options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation)
  468. , subQueryItems = []
  469. , subQueryAttributes = null
  470. , subJoinQueries = []
  471. // Escape table
  472. options.table = table = !Array.isArray(tableName) ? this.quoteIdentifiers(tableName) : tableName.map(function(t) {
  473. return this.quoteIdentifiers(t)
  474. }.bind(this)).join(", ")
  475. if (subQuery && mainAttributes) {
  476. if (factory.hasPrimaryKeys) {
  477. factory.primaryKeyAttributes.forEach(function(keyAtt){
  478. if(mainAttributes.indexOf(keyAtt) == -1){
  479. mainAttributes.push(keyAtt)
  480. }
  481. })
  482. } else {
  483. mainAttributes.push("id")
  484. }
  485. }
  486. // Escape attributes
  487. mainAttributes = mainAttributes && mainAttributes.map(function(attr){
  488. var addTable = true
  489. if (attr instanceof Utils.literal) {
  490. return attr.toString(this)
  491. }
  492. if (attr instanceof Utils.fn || attr instanceof Utils.col) {
  493. return attr.toString(self)
  494. }
  495. if(Array.isArray(attr) && attr.length == 2) {
  496. if (attr[0] instanceof Utils.fn || attr[0] instanceof Utils.col) {
  497. attr[0] = attr[0].toString(self)
  498. addTable = false
  499. }
  500. attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
  501. } else {
  502. attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr
  503. }
  504. if (options.include && attr.indexOf('.') === -1 && addTable) {
  505. attr = this.quoteIdentifier(options.table) + '.' + attr
  506. }
  507. return attr
  508. }.bind(this))
  509. // If no attributes specified, use *
  510. mainAttributes = mainAttributes || (options.include ? [options.table+'.*'] : ['*'])
  511. // If subquery, we ad the mainAttributes to the subQuery and set the mainAttributes to select * from subquery
  512. if (subQuery) {
  513. // We need primary keys
  514. subQueryAttributes = mainAttributes
  515. mainAttributes = [options.table+'.*']
  516. }
  517. if (options.include) {
  518. var generateJoinQueries = function(include, parentTable) {
  519. var table = include.daoFactory.tableName
  520. , as = include.as
  521. , joinQueryItem = ""
  522. , joinQueries = {
  523. mainQuery: [],
  524. subQuery: []
  525. }
  526. , attributes
  527. , association = include.association
  528. , through = include.through
  529. , joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN '
  530. , includeWhere = {}
  531. , whereOptions = Utils._.clone(options)
  532. whereOptions.keysEscaped = true
  533. if (tableName !== parentTable) {
  534. as = parentTable+'.'+include.as
  535. }
  536. // includeIgnoreAttributes is used by aggregate functions
  537. if (options.includeIgnoreAttributes !== false) {
  538. attributes = include.attributes.map(function(attr) {
  539. return self.quoteIdentifier(as) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(as + "." + attr)
  540. })
  541. if (include.subQuery && subQuery) {
  542. subQueryAttributes = subQueryAttributes.concat(attributes)
  543. } else {
  544. mainAttributes = mainAttributes.concat(attributes)
  545. }
  546. }
  547. if (through) {
  548. var throughTable = through.daoFactory.tableName
  549. , throughAs = as + "." + through.as
  550. , throughAttributes = through.attributes.map(function(attr) {
  551. return self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(throughAs + "." + attr)
  552. })
  553. , primaryKeysSource = Object.keys(association.source.primaryKeys)
  554. , tableSource = parentTable
  555. , identSource = association.identifier
  556. , attrSource = ((!association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
  557. , where
  558. , primaryKeysTarget = Object.keys(association.target.primaryKeys)
  559. , tableTarget = as
  560. , identTarget = association.foreignIdentifier
  561. , attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
  562. , sourceJoinOn
  563. , targetJoinOn
  564. , targetWhere
  565. if (options.includeIgnoreAttributes !== false) {
  566. // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
  567. mainAttributes = mainAttributes.concat(throughAttributes)
  568. }
  569. // Filter statement for left side of through
  570. // Used by both join and subquery where
  571. sourceJoinOn = self.quoteIdentifier(tableSource) + "." + self.quoteIdentifier(attrSource) + " = "
  572. sourceJoinOn += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identSource)
  573. // Filter statement for right side of through
  574. // Used by both join and subquery where
  575. targetJoinOn = self.quoteIdentifier(tableTarget) + "." + self.quoteIdentifier(attrTarget) + " = "
  576. targetJoinOn += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identTarget)
  577. // Generate join SQL for left side of through
  578. joinQueryItem += joinType + self.quoteIdentifier(throughTable) + " AS " + self.quoteIdentifier(throughAs) + " ON "
  579. joinQueryItem += sourceJoinOn
  580. // Generate join SQL for right side of through
  581. joinQueryItem += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "
  582. joinQueryItem += targetJoinOn
  583. if (include.where) {
  584. targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.daoFactory, whereOptions)
  585. joinQueryItem += " AND "+ targetWhere
  586. if (subQuery) {
  587. if (!options.where) options.where = {}
  588. // Creating the as-is where for the subQuery, checks that the required association exists
  589. var _where = "(";
  590. _where += "SELECT "+self.quoteIdentifier(identSource)+" FROM " + self.quoteIdentifier(throughTable) + " AS " + self.quoteIdentifier(throughAs);
  591. _where += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "+targetJoinOn;
  592. _where += " WHERE " + sourceJoinOn + " AND " + targetWhere + " LIMIT 1"
  593. _where += ")";
  594. _where += " IS NOT NULL"
  595. options.where["__"+throughAs] = self.sequelize.asIs(_where)
  596. }
  597. }
  598. } else {
  599. var primaryKeysLeft = ((association.associationType === 'BelongsTo') ? Object.keys(association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
  600. , tableLeft = ((association.associationType === 'BelongsTo') ? as : parentTable)
  601. , attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
  602. , tableRight = ((association.associationType === 'BelongsTo') ? parentTable : as)
  603. , attrRight = association.identifier
  604. , where
  605. // Filter statement
  606. // Used by both join and subquery where
  607. if (subQuery && !include.subQuery && include.parent.subQuery) {
  608. where = self.quoteIdentifier(tableLeft + "." + attrLeft) + " = "
  609. } else {
  610. where = self.quoteIdentifier(tableLeft) + "." + self.quoteIdentifier(attrLeft) + " = "
  611. }
  612. where += self.quoteIdentifier(tableRight) + "." + self.quoteIdentifier(attrRight)
  613. // Generate join SQL
  614. joinQueryItem += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "
  615. joinQueryItem += where
  616. if (include.where) {
  617. joinQueryItem += " AND "+self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.daoFactory, whereOptions)
  618. // If its a multi association we need to add a where query to the main where (executed in the subquery)
  619. if (subQuery && association.isMultiAssociation) {
  620. if (!options.where) options.where = {}
  621. // Creating the as-is where for the subQuery, checks that the required association exists
  622. options.where["__"+as] = self.sequelize.asIs("(SELECT "+self.quoteIdentifier(attrRight)+" FROM " + self.quoteIdentifier(tableRight) + " WHERE " + where + " LIMIT 1) IS NOT NULL")
  623. }
  624. }
  625. }
  626. if (include.subQuery && subQuery) {
  627. joinQueries.subQuery.push(joinQueryItem);
  628. } else {
  629. joinQueries.mainQuery.push(joinQueryItem);
  630. }
  631. if (include.include) {
  632. include.include.forEach(function(childInclude) {
  633. if (childInclude._pseudo) return
  634. var childJoinQueries = generateJoinQueries(childInclude, as)
  635. if (childInclude.subQuery && subQuery) {
  636. joinQueries.subQuery = joinQueries.subQuery.concat(childJoinQueries.subQuery)
  637. } else {
  638. joinQueries.mainQuery = joinQueries.mainQuery.concat(childJoinQueries.mainQuery)
  639. }
  640. }.bind(this))
  641. }
  642. return joinQueries
  643. }
  644. // Loop through includes and generate subqueries
  645. options.include.forEach(function(include) {
  646. var joinQueries = generateJoinQueries(include, tableName)
  647. subJoinQueries = subJoinQueries.concat(joinQueries.subQuery)
  648. mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery)
  649. }.bind(this))
  650. }
  651. // If using subQuery select defined subQuery attributes and join subJoinQueries
  652. if (subQuery) {
  653. subQueryItems.push("SELECT " + subQueryAttributes.join(', ') + " FROM " + options.table)
  654. subQueryItems.push(subJoinQueries.join(''))
  655. // Else do it the reguar way
  656. } else {
  657. mainQueryItems.push("SELECT " + mainAttributes.join(', ') + " FROM " + options.table)
  658. mainQueryItems.push(mainJoinQueries.join(''))
  659. }
  660. // Add WHERE to sub or main query
  661. if (options.hasOwnProperty('where')) {
  662. options.where = this.getWhereConditions(options.where, tableName, factory, options)
  663. if (subQuery) {
  664. subQueryItems.push(" WHERE " + options.where)
  665. } else {
  666. mainQueryItems.push(" WHERE " + options.where)
  667. }
  668. }
  669. // Add GROUP BY to sub or main query
  670. if (options.group) {
  671. options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t, factory) }.bind(this)).join(', ') : options.group
  672. if (subQuery) {
  673. subQueryItems.push(" GROUP BY " + options.group)
  674. } else {
  675. mainQueryItems.push(" GROUP BY " + options.group)
  676. }
  677. }
  678. // Add HAVING to sub or main query
  679. if (options.hasOwnProperty('having')) {
  680. options.having = this.getWhereConditions(options.having, tableName, factory, options, false)
  681. if (subQuery) {
  682. subQueryItems.push(" HAVING " + options.having)
  683. } else {
  684. mainQueryItems.push(" HAVING " + options.having)
  685. }
  686. }
  687. // Add ORDER to sub or main query
  688. if (options.order) {
  689. var mainQueryOrder = [];
  690. var subQueryOrder = [];
  691. if (Array.isArray(options.order)) {
  692. options.order.forEach(function (t) {
  693. if (subQuery && !(t[0] instanceof daoFactory) && !(t[0].model instanceof daoFactory)) {
  694. subQueryOrder.push(this.quote(t, factory))
  695. }
  696. mainQueryOrder.push(this.quote(t, factory))
  697. }.bind(this))
  698. } else {
  699. mainQueryOrder.push(options.order)
  700. }
  701. if (mainQueryOrder.length) {
  702. mainQueryItems.push(" ORDER BY " + mainQueryOrder.join(', '))
  703. }
  704. if (subQueryOrder.length) {
  705. subQueryItems.push(" ORDER BY " + subQueryOrder.join(', '))
  706. }
  707. }
  708. var limitOrder = this.addLimitAndOffset(options, query)
  709. // Add LIMIT, OFFSET to sub or main query
  710. if (limitOrder) {
  711. if (subQuery) {
  712. subQueryItems.push(limitOrder)
  713. } else {
  714. mainQueryItems.push(limitOrder)
  715. }
  716. }
  717. // If using subQuery, select attributes from wrapped subQuery and join out join tables
  718. if (subQuery) {
  719. query = "SELECT " + mainAttributes.join(', ') + " FROM ("
  720. query += subQueryItems.join('')
  721. query += ") AS "+options.table
  722. query += mainJoinQueries.join('')
  723. query += mainQueryItems.join('')
  724. } else {
  725. query = mainQueryItems.join('')
  726. }
  727. query += ";";
  728. return query
  729. },
  730. /**
  731. * Returns a query that starts a transaction.
  732. *
  733. * @param {Boolean} value A boolean that states whether autocommit shall be done or not.
  734. * @return {String} The generated sql query.
  735. */
  736. setAutocommitQuery: function(value) {
  737. return "SET autocommit = " + (!!value ? 1 : 0) + ";"
  738. },
  739. setIsolationLevelQuery: function(value) {
  740. return "SET SESSION TRANSACTION ISOLATION LEVEL " + value + ";"
  741. },
  742. /**
  743. * Returns a query that starts a transaction.
  744. *
  745. * @param {Object} options An object with options.
  746. * @return {String} The generated sql query.
  747. */
  748. startTransactionQuery: function(options) {
  749. return "START TRANSACTION;"
  750. },
  751. /**
  752. * Returns a query that commits a transaction.
  753. *
  754. * @param {Object} options An object with options.
  755. * @return {String} The generated sql query.
  756. */
  757. commitTransactionQuery: function(options) {
  758. return "COMMIT;"
  759. },
  760. /**
  761. * Returns a query that rollbacks a transaction.
  762. *
  763. * @param {Object} options An object with options.
  764. * @return {String} The generated sql query.
  765. */
  766. rollbackTransactionQuery: function(options) {
  767. return "ROLLBACK;"
  768. },
  769. addLimitAndOffset: function(options, query) {
  770. query = query || ""
  771. if (options.offset && !options.limit) {
  772. query += " LIMIT " + options.offset + ", " + 10000000000000;
  773. } else if (options.limit) {
  774. if (options.offset) {
  775. query += " LIMIT " + options.offset + ", " + options.limit
  776. } else {
  777. query += " LIMIT " + options.limit
  778. }
  779. }
  780. return query;
  781. },
  782. /*
  783. Takes something and transforms it into values of a where condition.
  784. */
  785. getWhereConditions: function(smth, tableName, factory, options, prepend) {
  786. var result = null
  787. , where = {}
  788. , self = this
  789. if (typeof prepend === 'undefined') {
  790. prepend = true
  791. }
  792. if ((smth instanceof Utils.and) || (smth instanceof Utils.or)) {
  793. var connector = (smth instanceof Utils.and) ? ' AND ' : ' OR '
  794. result = smth.args.map(function(arg) {
  795. return self.getWhereConditions(arg, tableName, factory, options, prepend)
  796. }).join(connector)
  797. result = "(" + result + ")"
  798. } else if (Utils.isHash(smth)) {
  799. if (prepend) {
  800. smth = Utils.prependTableNameToHash(tableName, smth, self.quoteIdentifier.bind(self))
  801. }
  802. result = this.hashToWhereConditions(smth, factory, options)
  803. } else if (typeof smth === 'number') {
  804. var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
  805. if (primaryKeys.length > 0) {
  806. // Since we're just a number, assume only the first key
  807. primaryKeys = primaryKeys[0]
  808. } else {
  809. primaryKeys = 'id'
  810. }
  811. where[primaryKeys] = smth
  812. smth = Utils.prependTableNameToHash(tableName, where)
  813. result = this.hashToWhereConditions(smth)
  814. } else if (typeof smth === "string") {
  815. result = smth
  816. } else if (Buffer.isBuffer(smth)) {
  817. result = this.escape(smth)
  818. } else if (Array.isArray(smth)) {
  819. var treatAsAnd = smth.reduce(function(treatAsAnd, arg) {
  820. if (treatAsAnd) {
  821. return treatAsAnd
  822. } else {
  823. return !(arg instanceof Date) && ((arg instanceof Utils.and) || (arg instanceof Utils.or) || Utils.isHash(arg))
  824. }
  825. }, false)
  826. if (treatAsAnd) {
  827. var _smth = self.sequelize.and.apply(null, smth)
  828. result = self.getWhereConditions(_smth, tableName, factory, options, prepend)
  829. } else {
  830. result = Utils.format(smth, this.dialect)
  831. }
  832. }
  833. return result ? result : '1=1'
  834. },
  835. findAssociation: function(attribute, dao){
  836. var associationToReturn;
  837. Object.keys(dao.associations).forEach(function(key){
  838. if(!dao.associations[key]) return;
  839. var association = dao.associations[key]
  840. , associationName
  841. if (association.associationType === 'BelongsTo') {
  842. associationName = Utils.singularize(association.associationAccessor[0].toLowerCase() + association.associationAccessor.slice(1));
  843. } else {
  844. associationName = association.accessors.get.replace('get', '')
  845. associationName = associationName[0].toLowerCase() + associationName.slice(1);
  846. }
  847. if(associationName === attribute){
  848. associationToReturn = association;
  849. }
  850. });
  851. return associationToReturn;
  852. },
  853. getAssociationFilterDAO: function(filterStr, dao){
  854. var associationParts = filterStr.split('.')
  855. , self = this
  856. associationParts.pop()
  857. associationParts.forEach(function (attribute) {
  858. dao = self.findAssociation(attribute, dao).target;
  859. });
  860. return dao;
  861. },
  862. isAssociationFilter: function(filterStr, dao, options){
  863. if(!dao){
  864. return false;
  865. }
  866. var pattern = /^[a-z][a-zA-Z0-9]+(\.[a-z][a-zA-Z0-9]+)+$/;
  867. if (!pattern.test(filterStr)) return false;
  868. var associationParts = filterStr.split('.')
  869. , attributePart = associationParts.pop()
  870. , self = this
  871. return associationParts.every(function (attribute) {
  872. var association = self.findAssociation(attribute, dao);
  873. if (!association) return false;
  874. dao = association.target;
  875. return !!dao;
  876. }) && dao.rawAttributes.hasOwnProperty(attributePart);
  877. },
  878. getAssociationFilterColumn: function(filterStr, dao, options){
  879. var associationParts = filterStr.split('.')
  880. , attributePart = associationParts.pop()
  881. , self = this
  882. , association
  883. , keyParts = []
  884. associationParts.forEach(function (attribute) {
  885. association = self.findAssociation(attribute, dao)
  886. dao = association.target;
  887. if (options.include) {
  888. keyParts.push(association.as || association.options.as || dao.tableName)
  889. }
  890. })
  891. if (options.include) {
  892. return this.quoteIdentifier(keyParts.join('.')) + '.' + this.quoteIdentifiers(attributePart)
  893. }
  894. return this.quoteIdentifiers(dao.tableName + '.' + attributePart)
  895. },
  896. getConditionalJoins: function(options, originalDao){
  897. var joins = ''
  898. , self = this
  899. , joinedTables = {}
  900. if (Utils.isHash(options.where)) {
  901. Object.keys(options.where).forEach(function(filterStr){
  902. var associationParts = filterStr.split('.')
  903. , attributePart = associationParts.pop()
  904. , dao = originalDao
  905. if (self.isAssociationFilter(filterStr, dao, options)) {
  906. associationParts.forEach(function (attribute) {
  907. var association = self.findAssociation(attribute, dao);
  908. if(!joinedTables[association.target.tableName]){
  909. joinedTables[association.target.tableName] = true;
  910. if(association.associationType === 'BelongsTo'){
  911. joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
  912. joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.identifier)
  913. joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
  914. } else if (Object(association.through) === association.through) {
  915. joinedTables[association.through.tableName] = true;
  916. joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.through.tableName)
  917. joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.source.autoIncrementField)
  918. joins += ' = ' + self.quoteIdentifiers(association.through.tableName + '.' + association.identifier)
  919. joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
  920. joins += ' ON ' + self.quoteIdentifiers(association.through.tableName + '.' + association.foreignIdentifier)
  921. joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
  922. } else {
  923. joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
  924. joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.source.autoIncrementField)
  925. joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.identifier)
  926. }
  927. }
  928. dao = association.target;
  929. });
  930. }
  931. });
  932. }
  933. return joins;
  934. },
  935. arrayValue: function(value, key, _key, factory, logicResult){
  936. var _value = null;
  937. if (value.length === 0) { value = [null] }
  938. _value = "(" + value.map(function(v) { return this.escape(v) }.bind(this)).join(',') + ")"
  939. return [_key, _value].join(" " + logicResult + " ")
  940. },
  941. /*
  942. Takes a hash and transforms it into a mysql where condition: {key: value, key2: value2} ==> key=value AND key2=value2
  943. The values are transformed by the relevant datatype.
  944. */
  945. hashToWhereConditions: function(hash, dao, options) {
  946. var result = []
  947. options = options || {}
  948. // Closures are nice
  949. Utils._.each(hash, function (value, key) {
  950. var _key
  951. , _value = null
  952. if (value instanceof Utils.asIs) {
  953. result.push(value.toString(this))
  954. return
  955. }
  956. if (options.keysEscaped) {
  957. _key = key
  958. } else {
  959. if(this.isAssociationFilter(key, dao, options)){
  960. _key = key = this.getAssociationFilterColumn(key, dao, options);
  961. } else {
  962. _key = this.quoteIdentifiers(key)
  963. }
  964. }
  965. if (Array.isArray(value)) {
  966. result.push(this.arrayValue(value, key, _key, dao, "IN"))
  967. } else if ((value) && (typeof value == 'object') && !(value instanceof Date) && !Buffer.isBuffer(value)) {
  968. if (!!value.join) {
  969. //using as sentinel for join column => value
  970. _value = this.quoteIdentifiers(value.join)
  971. result.push([_key, _value].join("="))
  972. } else {
  973. for (var logic in value) {
  974. var logicResult = Utils.getWhereLogic(logic, hash[key][logic]);
  975. if (logicResult === "IN" || logicResult === "NOT IN") {
  976. var values = Array.isArray(value[logic]) ? value[logic] : [value[logic]]
  977. result.push(this.arrayValue(values, key, _key, dao, logicResult))
  978. }
  979. else if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
  980. _value = this.escape(value[logic][0])
  981. var _value2 = this.escape(value[logic][1])
  982. result.push(' (' + _key + ' ' + logicResult + ' ' + _value + ' AND ' + _value2 + ') ')
  983. } else {
  984. _value = this.escape(value[logic])
  985. result.push([_key, _value].join(' ' + logicResult + ' '))
  986. }
  987. }
  988. }
  989. } else {
  990. if (typeof value === 'boolean') {
  991. _value = this.booleanValue(value);
  992. } else {
  993. _value = this.escape(value)
  994. }
  995. result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))
  996. }
  997. }.bind(this))
  998. return result.join(" AND ")
  999. },
  1000. booleanValue: function(value){
  1001. return value;
  1002. }
  1003. }
  1004. var throwMethodUndefined = function(methodName) {
  1005. throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.')
  1006. }
  1007. return QueryGenerator
  1008. })()