query-generator.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. var Utils = require("../../utils")
  2. , DataTypes = require("../../data-types")
  3. , util = require("util")
  4. module.exports = (function() {
  5. var QueryGenerator = {
  6. dialect: 'mysql',
  7. addSchema: function(opts) {
  8. var tableName
  9. var schema = (!!opts && !!opts.options && !!opts.options.schema ? opts.options.schema : undefined)
  10. var schemaDelimiter = (!!opts && !!opts.options && !!opts.options.schemaDelimiter ? opts.options.schemaDelimiter : undefined)
  11. if (!!opts && !!opts.tableName) {
  12. tableName = opts.tableName
  13. }
  14. else if (typeof opts === "string") {
  15. tableName = opts
  16. }
  17. if (!schema || schema.toString().trim() === "") {
  18. return tableName
  19. }
  20. return this.quoteIdentifier(schema + (!schemaDelimiter ? '.' : schemaDelimiter) + tableName, false)
  21. },
  22. createSchema: function() {
  23. var query = "SHOW TABLES"
  24. return Utils._.template(query)({})
  25. },
  26. dropSchema: function(tableName, options) {
  27. return QueryGenerator.dropTableQuery(tableName, options)
  28. },
  29. showSchemasQuery: function() {
  30. return "SHOW TABLES"
  31. },
  32. createTableQuery: function(tableName, attributes, options) {
  33. options = Utils._.extend({
  34. engine: 'InnoDB',
  35. charset: null
  36. }, options || {})
  37. var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comment %> ENGINE=<%= engine %> <%= charset %> <%= collation %>"
  38. , primaryKeys = []
  39. , foreignKeys = {}
  40. , attrStr = []
  41. for (var attr in attributes) {
  42. if (attributes.hasOwnProperty(attr)) {
  43. var dataType = this.mysqlDataTypeMapping(tableName, attr, attributes[attr])
  44. if (Utils._.includes(dataType, 'PRIMARY KEY')) {
  45. primaryKeys.push(attr)
  46. dataType = dataType.replace(/PRIMARY KEY/, '');
  47. }
  48. if (Utils._.includes(dataType, 'REFERENCES')) {
  49. // MySQL doesn't support inline REFERENCES declarations: move to the end
  50. var m = dataType.match(/^(.+) (REFERENCES.*)$/)
  51. dataType = m[1];
  52. foreignKeys[attr] = m[2]
  53. }
  54. attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
  55. }
  56. }
  57. var values = {
  58. table: this.quoteIdentifier(tableName),
  59. attributes: attrStr.join(", "),
  60. comment: options.comment && Utils._.isString(options.comment) ? " COMMENT " + this.escape(options.comment) : "",
  61. engine: options.engine,
  62. charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : ""),
  63. collation: (options.collate ? "COLLATE " + options.collate : "")
  64. }
  65. , pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")
  66. if (!!options.uniqueKeys) {
  67. Utils._.each(options.uniqueKeys, function(columns) {
  68. values.attributes += ", UNIQUE uniq_" + tableName + '_' + columns.fields.join('_') + " (" + columns.fields.join(', ') + ")"
  69. })
  70. }
  71. if (pkString.length > 0) {
  72. values.attributes += ", PRIMARY KEY (" + pkString + ")"
  73. }
  74. for (var fkey in foreignKeys) {
  75. if(foreignKeys.hasOwnProperty(fkey)) {
  76. values.attributes += ", FOREIGN KEY (" + this.quoteIdentifier(fkey) + ") " + foreignKeys[fkey]
  77. }
  78. }
  79. return Utils._.template(query)(values).trim() + ";"
  80. },
  81. dropTableQuery: function(tableName, options) {
  82. options = options || {}
  83. var query = "DROP TABLE IF EXISTS <%= table %>;"
  84. return Utils._.template(query)({
  85. table: this.quoteIdentifier(tableName)
  86. })
  87. },
  88. showTablesQuery: function() {
  89. return 'SHOW TABLES;'
  90. },
  91. uniqueConstraintMapping: {
  92. code: 'ER_DUP_ENTRY',
  93. map: function(str) {
  94. // we're manually remvoving uniq_ here for a future capability of defining column names explicitly
  95. var match = str.replace('uniq_', '').match(/Duplicate entry .* for key '(.*?)'$/)
  96. if (match === null || match.length < 2) {
  97. return false
  98. }
  99. return match[1].split('_')
  100. }
  101. },
  102. addColumnQuery: function(tableName, attributes) {
  103. var query = "ALTER TABLE `<%= tableName %>` ADD <%= attributes %>;"
  104. , attrString = []
  105. for (var attrName in attributes) {
  106. var definition = attributes[attrName]
  107. attrString.push(Utils._.template('`<%= attrName %>` <%= definition %>')({
  108. attrName: attrName,
  109. definition: this.mysqlDataTypeMapping(tableName, attrName, definition)
  110. }))
  111. }
  112. return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') })
  113. },
  114. removeColumnQuery: function(tableName, attributeName) {
  115. var query = "ALTER TABLE `<%= tableName %>` DROP `<%= attributeName %>`;"
  116. return Utils._.template(query)({ tableName: tableName, attributeName: attributeName })
  117. },
  118. changeColumnQuery: function(tableName, attributes) {
  119. var query = "ALTER TABLE `<%= tableName %>` CHANGE <%= attributes %>;"
  120. var attrString = []
  121. for (var attrName in attributes) {
  122. var definition = attributes[attrName]
  123. attrString.push(Utils._.template('`<%= attrName %>` `<%= attrName %>` <%= definition %>')({
  124. attrName: attrName,
  125. definition: definition
  126. }))
  127. }
  128. return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') })
  129. },
  130. renameColumnQuery: function(tableName, attrBefore, attributes) {
  131. var query = "ALTER TABLE `<%= tableName %>` CHANGE <%= attributes %>;"
  132. var attrString = []
  133. for (var attrName in attributes) {
  134. var definition = attributes[attrName]
  135. attrString.push(Utils._.template('`<%= before %>` `<%= after %>` <%= definition %>')({
  136. before: attrBefore,
  137. after: attrName,
  138. definition: definition
  139. }))
  140. }
  141. return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') })
  142. },
  143. bulkInsertQuery: function(tableName, attrValueHashes, options) {
  144. var query = "INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;"
  145. , tuples = []
  146. , allAttributes = []
  147. Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
  148. Utils._.forOwn(attrValueHash, function(value, key, hash) {
  149. if (allAttributes.indexOf(key) === -1) allAttributes.push(key)
  150. })
  151. })
  152. Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
  153. tuples.push("(" +
  154. allAttributes.map(function (key) {
  155. return this.escape(attrValueHash[key])
  156. }.bind(this)).join(",") +
  157. ")")
  158. }.bind(this))
  159. var replacements = {
  160. ignoreDuplicates: options && options.ignoreDuplicates ? ' IGNORE' : '',
  161. table: this.quoteIdentifier(tableName),
  162. attributes: allAttributes.map(function(attr){
  163. return this.quoteIdentifier(attr)
  164. }.bind(this)).join(","),
  165. tuples: tuples
  166. }
  167. return Utils._.template(query)(replacements)
  168. },
  169. deleteQuery: function(tableName, where, options) {
  170. options = options || {}
  171. var table = this.quoteIdentifier(tableName)
  172. if (options.truncate === true) {
  173. // Truncate does not allow LIMIT and WHERE
  174. return "TRUNCATE " + table
  175. }
  176. where = this.getWhereConditions(where)
  177. var limit = ""
  178. if(Utils._.isUndefined(options.limit)) {
  179. options.limit = 1;
  180. }
  181. if(!!options.limit) {
  182. limit = " LIMIT " + this.escape(options.limit)
  183. }
  184. return "DELETE FROM " + table + " WHERE " + where + limit
  185. },
  186. bulkDeleteQuery: function(tableName, where, options) {
  187. options = options || {}
  188. var table = this.quoteIdentifier(tableName)
  189. where = this.getWhereConditions(where)
  190. var query = "DELETE FROM " + table + " WHERE " + where
  191. return query
  192. },
  193. addIndexQuery: function(tableName, attributes, options) {
  194. var transformedAttributes = attributes.map(function(attribute) {
  195. if(typeof attribute === 'string') {
  196. return this.quoteIdentifier(attribute)
  197. } else {
  198. var result = ""
  199. if (!attribute.attribute) {
  200. throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute))
  201. }
  202. result += this.quoteIdentifier(attribute.attribute)
  203. if (attribute.length) {
  204. result += '(' + attribute.length + ')'
  205. }
  206. if (attribute.order) {
  207. result += ' ' + attribute.order
  208. }
  209. return result
  210. }
  211. }.bind(this))
  212. var onlyAttributeNames = attributes.map(function(attribute) {
  213. return (typeof attribute === 'string') ? attribute : attribute.attribute
  214. }.bind(this))
  215. options = Utils._.extend({
  216. indicesType: null,
  217. indexName: Utils._.underscored(tableName + '_' + onlyAttributeNames.join('_')),
  218. parser: null
  219. }, options || {})
  220. return Utils._.compact([
  221. "CREATE", options.indicesType, "INDEX", options.indexName,
  222. (options.indexType ? ('USING ' + options.indexType) : undefined),
  223. "ON", tableName, '(' + transformedAttributes.join(', ') + ')',
  224. (options.parser ? "WITH PARSER " + options.parser : undefined)
  225. ]).join(' ')
  226. },
  227. showIndexQuery: function(tableName, options) {
  228. var sql = "SHOW INDEX FROM `<%= tableName %>`<%= options %>"
  229. return Utils._.template(sql)({
  230. tableName: tableName,
  231. options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
  232. })
  233. },
  234. removeIndexQuery: function(tableName, indexNameOrAttributes) {
  235. var sql = "DROP INDEX <%= indexName %> ON <%= tableName %>"
  236. , indexName = indexNameOrAttributes
  237. if (typeof indexName !== 'string') {
  238. indexName = Utils._.underscored(tableName + '_' + indexNameOrAttributes.join('_'))
  239. }
  240. return Utils._.template(sql)({ tableName: tableName, indexName: indexName })
  241. },
  242. attributesToSQL: function(attributes) {
  243. var result = {}
  244. for (var name in attributes) {
  245. var dataType = attributes[name]
  246. if (Utils.isHash(dataType)) {
  247. var template
  248. if (dataType.type.toString() === DataTypes.ENUM.toString()) {
  249. if (Array.isArray(dataType.values) && (dataType.values.length > 0)) {
  250. template = "ENUM(" + Utils._.map(dataType.values, function(value) {
  251. return this.escape(value)
  252. }.bind(this)).join(", ") + ")"
  253. } else {
  254. throw new Error('Values for ENUM haven\'t been defined.')
  255. }
  256. } else {
  257. template = dataType.type.toString();
  258. }
  259. if (dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) {
  260. template += " NOT NULL"
  261. }
  262. if (dataType.autoIncrement) {
  263. template += " auto_increment"
  264. }
  265. // Blobs/texts cannot have a defaultValue
  266. if (dataType.type !== "TEXT" && dataType.type._binary !== true && Utils.defaultValueSchemable(dataType.defaultValue)) {
  267. template += " DEFAULT " + this.escape(dataType.defaultValue)
  268. }
  269. if (dataType.unique === true) {
  270. template += " UNIQUE"
  271. }
  272. if (dataType.primaryKey) {
  273. template += " PRIMARY KEY"
  274. }
  275. if(dataType.references) {
  276. template += " REFERENCES " + this.quoteIdentifier(dataType.references)
  277. if(dataType.referencesKey) {
  278. template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")"
  279. } else {
  280. template += " (" + this.quoteIdentifier('id') + ")"
  281. }
  282. if(dataType.onDelete) {
  283. template += " ON DELETE " + dataType.onDelete.toUpperCase()
  284. }
  285. if(dataType.onUpdate) {
  286. template += " ON UPDATE " + dataType.onUpdate.toUpperCase()
  287. }
  288. }
  289. if (dataType.comment && Utils._.isString(dataType.comment) && dataType.comment.length) {
  290. template += " COMMENT " + this.escape(dataType.comment)
  291. }
  292. result[name] = template
  293. } else {
  294. result[name] = dataType
  295. }
  296. }
  297. return result
  298. },
  299. findAutoIncrementField: function(factory) {
  300. var fields = []
  301. for (var name in factory.attributes) {
  302. if (factory.attributes.hasOwnProperty(name)) {
  303. var definition = factory.attributes[name]
  304. if (definition && (definition.indexOf('auto_increment') > -1)) {
  305. fields.push(name)
  306. }
  307. }
  308. }
  309. return fields
  310. },
  311. addLimitAndOffset: function(options, query){
  312. query = query || ""
  313. if (options.offset && !options.limit) {
  314. query += " LIMIT " + options.offset + ", " + 18440000000000000000;
  315. } else if (options.limit) {
  316. if (options.offset) {
  317. query += " LIMIT " + options.offset + ", " + options.limit
  318. } else {
  319. query += " LIMIT " + options.limit
  320. }
  321. }
  322. return query;
  323. },
  324. quoteIdentifier: function(identifier, force) {
  325. if (identifier === '*') return identifier
  326. return Utils.addTicks(identifier, "`")
  327. },
  328. quoteIdentifiers: function(identifiers, force) {
  329. return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
  330. },
  331. quoteTable: function(table) {
  332. return this.quoteIdentifier(table)
  333. },
  334. /**
  335. * Generates an SQL query that returns all foreign keys of a table.
  336. *
  337. * @param {String} tableName The name of the table.
  338. * @param {String} schemaName The name of the schema.
  339. * @return {String} The generated sql query.
  340. */
  341. getForeignKeysQuery: function(tableName, schemaName) {
  342. return "SELECT CONSTRAINT_NAME as constraint_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '" + tableName + "' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='" + schemaName + "' AND REFERENCED_TABLE_NAME IS NOT NULL;"
  343. },
  344. /**
  345. * Generates an SQL query that removes a foreign key from a table.
  346. *
  347. * @param {String} tableName The name of the table.
  348. * @param {String} foreignKey The name of the foreign key constraint.
  349. * @return {String} The generated sql query.
  350. */
  351. dropForeignKeyQuery: function(tableName, foreignKey) {
  352. return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';'
  353. },
  354. mysqlDataTypeMapping: function(tableName, attr, dataType) {
  355. if (Utils._.includes(dataType, 'UUID')) {
  356. dataType = dataType.replace(/UUID/, 'CHAR(36) BINARY')
  357. }
  358. return dataType
  359. }
  360. }
  361. return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
  362. })()