query-generator.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. var Utils = require("../../utils")
  2. , util = require("util")
  3. , DataTypes = require("../../data-types")
  4. , SqlString = require("../../sql-string")
  5. , tables = {}
  6. , primaryKeys = {}
  7. module.exports = (function() {
  8. var QueryGenerator = {
  9. options: {},
  10. dialect: 'postgres',
  11. addSchema: function(opts) {
  12. var tableName = undefined
  13. var schema = (!!opts.options && !!opts.options.schema ? opts.options.schema : undefined)
  14. var schemaDelimiter = (!!opts.options && !!opts.options.schemaDelimiter ? opts.options.schemaDelimiter : undefined)
  15. if (!!opts && !!opts.tableName) {
  16. tableName = opts.tableName
  17. }
  18. else if (typeof opts === "string") {
  19. tableName = opts
  20. }
  21. if (!schema || schema.toString().trim() === "") {
  22. return tableName
  23. }
  24. return this.quoteIdentifiers((!!schema ? (schema + '.' + tableName) : tableName));
  25. },
  26. createSchema: function(schema) {
  27. var query = "CREATE SCHEMA <%= schema%>;"
  28. return Utils._.template(query)({schema: schema})
  29. },
  30. dropSchema: function(schema) {
  31. var query = "DROP SCHEMA <%= schema%> CASCADE;"
  32. return Utils._.template(query)({schema: schema})
  33. },
  34. showSchemasQuery: function() {
  35. return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';"
  36. },
  37. createTableQuery: function(tableName, attributes, options) {
  38. var self = this
  39. options = Utils._.extend({
  40. }, options || {})
  41. primaryKeys[tableName] = []
  42. tables[tableName] = {}
  43. var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comments %>"
  44. , comments = ""
  45. , attrStr = []
  46. , i
  47. if (options.comment && Utils._.isString(options.comment)) {
  48. comments += "; COMMENT ON TABLE <%= table %> IS " + this.escape(options.comment)
  49. }
  50. for (var attr in attributes) {
  51. if ((i = attributes[attr].indexOf('COMMENT')) !== -1) {
  52. // Move comment to a seperate query
  53. comments += "; " + attributes[attr].substring(i)
  54. attributes[attr] = attributes[attr].substring(0, i)
  55. }
  56. var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr])
  57. attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
  58. }
  59. var values = {
  60. table: this.quoteIdentifiers(tableName),
  61. attributes: attrStr.join(", "),
  62. comments: Utils._.template(comments, { table: this.quoteIdentifiers(tableName)})
  63. }
  64. if (!!options.uniqueKeys) {
  65. Utils._.each(options.uniqueKeys, function(columns) {
  66. values.attributes += ", UNIQUE (" + columns.fields.map(function(f) { return self.quoteIdentifiers(f) }).join(', ') + ")"
  67. })
  68. }
  69. var pks = primaryKeys[tableName].map(function(pk){
  70. return this.quoteIdentifier(pk)
  71. }.bind(this)).join(",")
  72. if (pks.length > 0) {
  73. values.attributes += ", PRIMARY KEY (" + pks + ")"
  74. }
  75. return Utils._.template(query)(values).trim() + ";"
  76. },
  77. dropTableQuery: function(tableName, options) {
  78. options = options || {}
  79. var query = "DROP TABLE IF EXISTS <%= schema %><%= table %><%= cascade %>;"
  80. return Utils._.template(query)({
  81. schema: options.schema ? this.quoteIdentifiers(options.schema) + '.' : '',
  82. table: this.quoteIdentifiers(tableName),
  83. cascade: options.cascade? " CASCADE" : ""
  84. })
  85. },
  86. showTablesQuery: function() {
  87. return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"
  88. },
  89. describeTableQuery: function(tableName, schema) {
  90. if (!schema) {
  91. schema = 'public';
  92. }
  93. var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", CASE WHEN c.udt_name = \'hstore\' THEN c.udt_name ELSE c.data_type END as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" FROM information_schema.columns c WHERE table_name = <%= table %> AND table_schema = <%= schema %>'
  94. return Utils._.template(query)({
  95. table: this.escape(tableName),
  96. schema: this.escape(schema)
  97. })
  98. },
  99. uniqueConstraintMapping: {
  100. code: '23505',
  101. map: function(str) {
  102. var match = str.match(/duplicate key value violates unique constraint "(.*?)_key"/)
  103. if (match === null || match.length < 2) {
  104. return false
  105. }
  106. return match[1].split('_').splice(1)
  107. }
  108. },
  109. addColumnQuery: function(tableName, attributes) {
  110. var query = "ALTER TABLE <%= tableName %> ADD COLUMN <%= attributes %>;"
  111. , attrString = []
  112. for (var attrName in attributes) {
  113. var definition = attributes[attrName]
  114. attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
  115. attrName: this.quoteIdentifier(attrName),
  116. definition: this.pgDataTypeMapping(tableName, attrName, definition)
  117. }))
  118. if (definition.match(/^ENUM\(/)) {
  119. query = this.pgEnum(tableName, attrName, definition) + query
  120. }
  121. }
  122. return Utils._.template(query)({
  123. tableName: this.quoteIdentifiers(tableName),
  124. attributes: attrString.join(', ') })
  125. },
  126. arrayValue: function(value, key, _key, factory, logicResult){
  127. var col = null
  128. , coltype = null
  129. , _realKey = key.split('.').pop()
  130. , _value
  131. if (value.length === 0) { value = [null] }
  132. // Special conditions for searching within an array column type
  133. if (!!factory && !!factory.rawAttributes[_realKey]) {
  134. col = factory.rawAttributes[_realKey]
  135. coltype = col.type
  136. if(coltype && !(typeof coltype == 'string')) {
  137. coltype = coltype.toString();
  138. }
  139. }
  140. if ( col && ((!!coltype && coltype.match(/\[\]$/) !== null) || (col.toString().match(/\[\]$/) !== null))) {
  141. _value = 'ARRAY[' + value.map(this.escape.bind(this)).join(',') + ']::' + (!!col.type ? col.type : col.toString())
  142. return [_key, _value].join(" && ")
  143. } else {
  144. _value = "(" + value.map(this.escape.bind(this)).join(',') + ")"
  145. return [_key, _value].join(" " + logicResult + " ")
  146. }
  147. },
  148. removeColumnQuery: function(tableName, attributeName) {
  149. var query = "ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;"
  150. return Utils._.template(query)({
  151. tableName: this.quoteIdentifiers(tableName),
  152. attributeName: this.quoteIdentifier(attributeName)
  153. })
  154. },
  155. changeColumnQuery: function(tableName, attributes) {
  156. var query = "ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;"
  157. , sql = []
  158. for (var attributeName in attributes) {
  159. var definition = attributes[attributeName]
  160. var attrSql = ''
  161. if (definition.indexOf('NOT NULL') > 0) {
  162. attrSql += Utils._.template(query)({
  163. tableName: this.quoteIdentifiers(tableName),
  164. query: this.quoteIdentifier(attributeName) + ' SET NOT NULL'
  165. })
  166. definition = definition.replace('NOT NULL', '').trim()
  167. } else {
  168. attrSql += Utils._.template(query)({
  169. tableName: this.quoteIdentifiers(tableName),
  170. query: this.quoteIdentifier(attributeName) + ' DROP NOT NULL'
  171. })
  172. }
  173. if (definition.indexOf('DEFAULT') > 0) {
  174. attrSql += Utils._.template(query)({
  175. tableName: this.quoteIdentifiers(tableName),
  176. query: this.quoteIdentifier(attributeName) + ' SET DEFAULT ' + definition.match(/DEFAULT ([^;]+)/)[1]
  177. })
  178. definition = definition.replace(/(DEFAULT[^;]+)/, '').trim()
  179. } else {
  180. attrSql += Utils._.template(query)({
  181. tableName: this.quoteIdentifiers(tableName),
  182. query: this.quoteIdentifier(attributeName) + ' DROP DEFAULT'
  183. })
  184. }
  185. if (definition.match(/^ENUM\(/)) {
  186. query = this.pgEnum(tableName, attributeName, definition) + query
  187. definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier("enum_" + tableName + "_" + attributeName))
  188. }
  189. if (definition.match(/UNIQUE;*$/)) {
  190. definition = definition.replace(/UNIQUE;*$/, '')
  191. attrSql += Utils._.template(query.replace('ALTER COLUMN', ''))({
  192. tableName: this.quoteIdentifiers(tableName),
  193. query: 'ADD CONSTRAINT ' + this.quoteIdentifier(attributeName + '_unique_idx') + ' UNIQUE (' + this.quoteIdentifier(attributeName) + ')'
  194. })
  195. }
  196. attrSql += Utils._.template(query)({
  197. tableName: this.quoteIdentifiers(tableName),
  198. query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition
  199. })
  200. sql.push(attrSql)
  201. }
  202. return sql.join('')
  203. },
  204. renameColumnQuery: function(tableName, attrBefore, attributes) {
  205. var query = "ALTER TABLE <%= tableName %> RENAME COLUMN <%= attributes %>;"
  206. var attrString = []
  207. for (var attributeName in attributes) {
  208. attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
  209. before: this.quoteIdentifier(attrBefore),
  210. after: this.quoteIdentifier(attributeName)
  211. }))
  212. }
  213. return Utils._.template(query)({
  214. tableName: this.quoteIdentifiers(tableName),
  215. attributes: attrString.join(', ')
  216. })
  217. },
  218. bulkInsertQuery: function(tableName, attrValueHashes, options) {
  219. var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;"
  220. , tuples = []
  221. , serials = []
  222. , allAttributes = []
  223. Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
  224. Utils._.forOwn(attrValueHash, function(value, key, hash) {
  225. if (allAttributes.indexOf(key) === -1) allAttributes.push(key)
  226. if (tables[tableName] && tables[tableName][key]) {
  227. if (['bigserial', 'serial'].indexOf(tables[tableName][key]) !== -1 && serials.indexOf(key) === -1) {
  228. serials.push(key)
  229. }
  230. }
  231. })
  232. })
  233. Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
  234. tuples.push("(" +
  235. allAttributes.map(function (key) {
  236. if (serials.indexOf(key) !== -1) {
  237. return attrValueHash[key] || 'DEFAULT';
  238. }
  239. return this.escape(attrValueHash[key])
  240. }.bind(this)).join(",") +
  241. ")")
  242. }.bind(this))
  243. var replacements = {
  244. table: this.quoteIdentifiers(tableName)
  245. , attributes: allAttributes.map(function(attr){
  246. return this.quoteIdentifier(attr)
  247. }.bind(this)).join(",")
  248. , tuples: tuples.join(",")
  249. }
  250. return Utils._.template(query)(replacements)
  251. },
  252. deleteQuery: function(tableName, where, options, factory) {
  253. options = options || {}
  254. if (options.truncate === true) {
  255. return "TRUNCATE " + QueryGenerator.quoteIdentifier(tableName)
  256. }
  257. if(Utils._.isUndefined(options.limit)) {
  258. options.limit = 1;
  259. }
  260. primaryKeys[tableName] = primaryKeys[tableName] || [];
  261. if (!!factory && primaryKeys[tableName].length < 1) {
  262. primaryKeys[tableName] = Object.keys(factory.primaryKeys)
  263. }
  264. var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)"
  265. var pks;
  266. if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) {
  267. pks = primaryKeys[tableName].map(function(pk) {
  268. return this.quoteIdentifier(pk)
  269. }.bind(this)).join(',')
  270. } else {
  271. pks = this.quoteIdentifier('id')
  272. }
  273. var replacements = {
  274. table: this.quoteIdentifiers(tableName),
  275. where: this.getWhereConditions(where),
  276. limit: !!options.limit? " LIMIT " + this.escape(options.limit) : "",
  277. primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks,
  278. primaryKeysSelection: pks
  279. }
  280. return Utils._.template(query)(replacements)
  281. },
  282. addIndexQuery: function(tableName, attributes, options) {
  283. var transformedAttributes = attributes.map(function(attribute) {
  284. if (typeof attribute === 'string') {
  285. return this.quoteIdentifier(attribute)
  286. } else {
  287. var result = ""
  288. if (!attribute.attribute) {
  289. throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute))
  290. }
  291. result += this.quoteIdentifier(attribute.attribute)
  292. if (attribute.length) {
  293. result += '(' + attribute.length + ')'
  294. }
  295. if (attribute.order) {
  296. result += ' ' + attribute.order
  297. }
  298. return result
  299. }
  300. }.bind(this))
  301. var onlyAttributeNames = attributes.map(function(attribute) {
  302. return (typeof attribute === "string") ? attribute : attribute.attribute
  303. }.bind(this))
  304. var indexTable = tableName.split('.')
  305. options = Utils._.extend({
  306. indicesType: null,
  307. indexName: Utils._.underscored(indexTable[indexTable.length-1] + '_' + onlyAttributeNames.join('_')),
  308. parser: null
  309. }, options || {})
  310. return Utils._.compact([
  311. "CREATE", options.indicesType, "INDEX", this.quoteIdentifiers(options.indexName),
  312. "ON", this.quoteIdentifiers(tableName), (options.indexType ? ('USING ' + options.indexType) : undefined),
  313. '(' + transformedAttributes.join(', ') + ')'
  314. ]).join(' ')
  315. },
  316. showIndexQuery: function(tableName, options) {
  317. var query = "SELECT relname FROM pg_class WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname='<%= tableName %>' AND pg_class.oid=pg_index.indrelid);"
  318. return Utils._.template(query)({ tableName: tableName });
  319. },
  320. removeIndexQuery: function(tableName, indexNameOrAttributes) {
  321. var sql = "DROP INDEX IF EXISTS <%= indexName %>"
  322. , indexName = indexNameOrAttributes
  323. if (typeof indexName !== "string") {
  324. indexName = Utils._.underscored(tableName + '_' + indexNameOrAttributes.join('_'))
  325. }
  326. return Utils._.template(sql)({
  327. tableName: this.quoteIdentifiers(tableName),
  328. indexName: this.quoteIdentifiers(indexName)
  329. })
  330. },
  331. addLimitAndOffset: function(options, query){
  332. query = query || ""
  333. if (options.limit) {
  334. query += " LIMIT " + options.limit
  335. }
  336. if (options.offset) {
  337. query += " OFFSET " + options.offset
  338. }
  339. return query;
  340. },
  341. attributesToSQL: function(attributes) {
  342. var result = {}
  343. for (var name in attributes) {
  344. var dataType = attributes[name]
  345. if(Utils.isHash(dataType)) {
  346. var template = "<%= type %>"
  347. , replacements = { type: dataType.type }
  348. if (dataType.type.toString() === DataTypes.ENUM.toString()) {
  349. if (Array.isArray(dataType.values) && (dataType.values.length > 0)) {
  350. replacements.type = "ENUM(" + Utils._.map(dataType.values, function(value) {
  351. return this.escape(value)
  352. }.bind(this)).join(", ") + ")"
  353. } else {
  354. throw new Error('Values for ENUM haven\'t been defined.')
  355. }
  356. }
  357. if (dataType.type === "TINYINT(1)") {
  358. dataType.type = 'BOOLEAN'
  359. }
  360. if (dataType.type === "DATETIME") {
  361. dataType.originalType = "DATETIME"
  362. dataType.type = 'TIMESTAMP WITH TIME ZONE'
  363. }
  364. if (dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) {
  365. template += " NOT NULL"
  366. }
  367. if (dataType.autoIncrement) {
  368. template += " SERIAL"
  369. }
  370. if (Utils.defaultValueSchemable(dataType.defaultValue)) {
  371. // TODO thoroughly check that DataTypes.NOW will properly
  372. // get populated on all databases as DEFAULT value
  373. // i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP
  374. template += " DEFAULT <%= defaultValue %>"
  375. replacements.defaultValue = this.escape(dataType.defaultValue)
  376. }
  377. if (dataType.unique === true) {
  378. template += " UNIQUE"
  379. }
  380. if (dataType.primaryKey) {
  381. template += " PRIMARY KEY"
  382. }
  383. if(dataType.references) {
  384. template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)"
  385. replacements.referencesTable = this.quoteIdentifiers(dataType.references)
  386. if(dataType.referencesKey) {
  387. replacements.referencesKey = this.quoteIdentifiers(dataType.referencesKey)
  388. } else {
  389. replacements.referencesKey = this.quoteIdentifier('id')
  390. }
  391. if(dataType.onDelete) {
  392. template += " ON DELETE <%= onDeleteAction %>"
  393. replacements.onDeleteAction = dataType.onDelete.toUpperCase()
  394. }
  395. if(dataType.onUpdate) {
  396. template += " ON UPDATE <%= onUpdateAction %>"
  397. replacements.onUpdateAction = dataType.onUpdate.toUpperCase()
  398. }
  399. }
  400. if (dataType.comment && Utils._.isString(dataType.comment)) {
  401. template += " COMMENT ON COLUMN <%= tableName %>.<%= columnName %> IS <%= comment %>"
  402. replacements.columnName = this.quoteIdentifier(name)
  403. replacements.tableName = '<%= table %>' // Hacky, table name will be inserted by create table
  404. replacements.comment = this.escape(dataType.comment)
  405. }
  406. result[name] = Utils._.template(template)(replacements)
  407. } else {
  408. result[name] = dataType
  409. }
  410. }
  411. return result
  412. },
  413. findAutoIncrementField: function(factory) {
  414. var fields = []
  415. for (var name in factory.attributes) {
  416. var definition = factory.attributes[name]
  417. if (definition && (definition.indexOf('SERIAL') > -1)) {
  418. fields.push(name)
  419. }
  420. }
  421. return fields
  422. },
  423. createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
  424. var sql = [
  425. 'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
  426. , '<%= eventType %> <%= eventSpec %>'
  427. , 'ON <%= tableName %>'
  428. , '<%= optionsSpec %>'
  429. , 'EXECUTE PROCEDURE <%= functionName %>(<%= paramList %>);'
  430. ].join('\n\t')
  431. return Utils._.template(sql)({
  432. constraintVal: this.triggerEventTypeIsConstraint(eventType),
  433. triggerName: triggerName,
  434. eventType: this.decodeTriggerEventType(eventType),
  435. eventSpec: this.expandTriggerEventSpec(fireOnSpec),
  436. tableName: tableName,
  437. optionsSpec: this.expandOptions(optionsArray),
  438. functionName: functionName,
  439. paramList: this.expandFunctionParamList(functionParams)
  440. })
  441. },
  442. dropTrigger: function(tableName, triggerName) {
  443. var sql = 'DROP TRIGGER <%= triggerName %> ON <%= tableName %> RESTRICT;'
  444. return Utils._.template(sql)({
  445. triggerName: triggerName,
  446. tableName: tableName
  447. })
  448. },
  449. renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
  450. var sql = 'ALTER TRIGGER <%= oldTriggerName %> ON <%= tableName %> RENAME TO <%= newTriggerName%>;'
  451. return Utils._.template(sql)({
  452. tableName: tableName,
  453. oldTriggerName: oldTriggerName,
  454. newTriggerName: newTriggerName
  455. })
  456. },
  457. createFunction: function(functionName, params, returnType, language, body, options) {
  458. var sql = [ "CREATE FUNCTION <%= functionName %>(<%= paramList %>)"
  459. , "RETURNS <%= returnType %> AS $$"
  460. , "BEGIN"
  461. , "\t<%= body %>"
  462. , "END;"
  463. , "$$ language '<%= language %>'<%= options %>;"
  464. ].join('\n')
  465. return Utils._.template(sql)({
  466. functionName: functionName,
  467. paramList: this.expandFunctionParamList(params),
  468. returnType: returnType,
  469. body: body.replace('\n', '\n\t'),
  470. language: language,
  471. options: this.expandOptions(options)
  472. })
  473. },
  474. dropFunction: function(functionName, params) {
  475. // RESTRICT is (currently, as of 9.2) default but we'll be explicit
  476. var sql = 'DROP FUNCTION <%= functionName %>(<%= paramList %>) RESTRICT;'
  477. return Utils._.template(sql)({
  478. functionName: functionName,
  479. paramList: this.expandFunctionParamList(params)
  480. })
  481. },
  482. renameFunction: function(oldFunctionName, params, newFunctionName) {
  483. var sql = 'ALTER FUNCTION <%= oldFunctionName %>(<%= paramList %>) RENAME TO <%= newFunctionName %>;'
  484. return Utils._.template(sql)({
  485. oldFunctionName: oldFunctionName,
  486. paramList: this.expandFunctionParamList(params),
  487. newFunctionName: newFunctionName
  488. })
  489. },
  490. databaseConnectionUri: function(config) {
  491. var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'
  492. return Utils._.template(template)({
  493. user: config.username,
  494. password: config.password,
  495. database: config.database,
  496. host: config.host,
  497. port: config.port,
  498. protocol: config.protocol
  499. })
  500. },
  501. pgEscapeAndQuote: function (val) {
  502. return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"))
  503. },
  504. expandFunctionParamList: function expandFunctionParamList(params) {
  505. if (Utils._.isUndefined(params) || !Utils._.isArray(params)) {
  506. throw new Error("expandFunctionParamList: function parameters array required, including an empty one for no arguments")
  507. }
  508. var paramList = Utils._.each(params, function expandParam(curParam){
  509. paramDef = []
  510. if (Utils._.has(curParam, 'type')) {
  511. if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam.direction) }
  512. if (Utils._.has(curParam, 'name')) { paramDef.push(curParam.name) }
  513. paramDef.push(curParam.type)
  514. } else {
  515. throw new Error('createFunction called with a parameter with no type')
  516. }
  517. return paramDef.join(' ')
  518. })
  519. return paramList.join(', ')
  520. },
  521. expandOptions: function expandOptions(options) {
  522. return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
  523. '' : '\n\t' + options.join('\n\t')
  524. },
  525. decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) {
  526. var EVENT_DECODER = {
  527. 'after': 'AFTER',
  528. 'before': 'BEFORE',
  529. 'instead_of': 'INSTEAD OF',
  530. 'after_constraint': 'AFTER'
  531. }
  532. if (!Utils._.has(EVENT_DECODER, eventSpecifier)) {
  533. throw new Error('Invalid trigger event specified: ' + eventSpecifier)
  534. }
  535. return EVENT_DECODER[eventSpecifier]
  536. },
  537. triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) {
  538. return eventSpecifier === 'after_constrain' ? 'CONSTRAINT ' : ''
  539. },
  540. expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) {
  541. if (Utils._.isEmpty(fireOnSpec)) {
  542. throw new Error('no table change events specified to trigger on')
  543. }
  544. return Utils._.map(fireOnSpec, function parseTriggerEventSpec(fireValue, fireKey){
  545. var EVENT_MAP = {
  546. 'insert': 'INSERT',
  547. 'update': 'UPDATE',
  548. 'delete': 'DELETE',
  549. 'truncate': 'TRUNCATE'
  550. }
  551. if (!Utils._.has(EVENT_MAP, fireKey)) {
  552. throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey)
  553. }
  554. var eventSpec = EVENT_MAP[fireKey]
  555. if (eventSpec === 'UPDATE') {
  556. if (Utils._.isArray(fireValue) && fireValue.length > 0) {
  557. eventSpec += ' OF ' + fireValue.join(', ')
  558. }
  559. }
  560. return eventSpec
  561. }).join(' OR ')
  562. },
  563. pgListEnums: function(tableName, attrName, options) {
  564. if (arguments.length === 1) {
  565. options = tableName
  566. tableName = null
  567. }
  568. var enumName = ''
  569. if (!!tableName && !!attrName) {
  570. enumName = ' AND t.typname=' + this.escape("enum_" + tableName + "_" + attrName) + ' '
  571. }
  572. var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel) enum_value FROM pg_type t ' +
  573. 'JOIN pg_enum e ON t.oid = e.enumtypid ' +
  574. 'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
  575. 'WHERE n.nspname = \'public\' ' + enumName + ' GROUP BY 1'
  576. return query
  577. },
  578. pgEnum: function (tableName, attr, dataType, options) {
  579. var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
  580. , values
  581. if (dataType.values) {
  582. values = "ENUM('"+dataType.values.join("', '")+"')";
  583. } else {
  584. values = dataType.toString().match(/^ENUM\(.+\)/)[0]
  585. }
  586. var sql = "CREATE TYPE " + enumName + " AS " + values + "; "
  587. if (!!options && options.force === true) {
  588. sql = this.pgEnumDrop(tableName, attr) + sql
  589. }
  590. return sql
  591. },
  592. pgEnumAdd: function(tableName, attr, value, options) {
  593. var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
  594. var sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ' + this.escape(value)
  595. if (!!options.before) {
  596. sql += ' BEFORE ' + this.escape(options.before)
  597. }
  598. else if (!!options.after) {
  599. sql += ' AFTER ' + this.escape(options.after)
  600. }
  601. return sql
  602. },
  603. pgEnumDrop: function(tableName, attr, enumName) {
  604. enumName = enumName || this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
  605. return 'DROP TYPE IF EXISTS ' + enumName + '; '
  606. },
  607. fromArray: function(text) {
  608. text = text.replace(/^{/, '').replace(/}$/, '')
  609. var matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig)
  610. if (matches.length < 1) {
  611. return []
  612. }
  613. matches = matches.map(function(m){
  614. return m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '')
  615. })
  616. return matches.slice(0, -1)
  617. },
  618. padInt: function (i) {
  619. return (i < 10) ? '0' + i.toString() : i.toString()
  620. },
  621. pgDataTypeMapping: function (tableName, attr, dataType) {
  622. if (Utils._.includes(dataType, 'PRIMARY KEY')) {
  623. primaryKeys[tableName].push(attr)
  624. dataType = dataType.replace(/PRIMARY KEY/, '')
  625. }
  626. if (Utils._.includes(dataType, 'TINYINT(1)')) {
  627. dataType = dataType.replace(/TINYINT\(1\)/, 'BOOLEAN')
  628. }
  629. if (Utils._.includes(dataType, 'DATETIME')) {
  630. dataType = dataType.replace(/DATETIME/, 'TIMESTAMP WITH TIME ZONE')
  631. }
  632. if (Utils._.includes(dataType, 'SERIAL')) {
  633. if (Utils._.includes(dataType, 'BIGINT')) {
  634. dataType = dataType.replace(/SERIAL/, 'BIGSERIAL')
  635. dataType = dataType.replace(/BIGINT/, '')
  636. tables[tableName][attr] = 'bigserial'
  637. } else {
  638. dataType = dataType.replace(/INTEGER/, '')
  639. tables[tableName][attr] = 'serial'
  640. }
  641. dataType = dataType.replace(/NOT NULL/, '')
  642. }
  643. if (dataType.lastIndexOf('BLOB') !== -1 || dataType.lastIndexOf('BINARY') !== -1) {
  644. dataType = 'bytea'
  645. }
  646. if (dataType.match(/^ENUM\(/)) {
  647. dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote("enum_" + tableName + "_" + attr))
  648. }
  649. return dataType
  650. },
  651. quoteIdentifier: function(identifier, force) {
  652. if (identifier === '*') return identifier
  653. if(!force && this.options && this.options.quoteIdentifiers === false) { // default is `true`
  654. // In Postgres, if tables or attributes are created double-quoted,
  655. // they are also case sensitive. If they contain any uppercase
  656. // characters, they must always be double-quoted. This makes it
  657. // impossible to write queries in portable SQL if tables are created in
  658. // this way. Hence, we strip quotes if we don't want case sensitivity.
  659. return Utils.removeTicks(identifier, '"')
  660. } else {
  661. return Utils.addTicks(identifier, '"')
  662. }
  663. },
  664. quoteIdentifiers: function(identifiers, force) {
  665. return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.')
  666. },
  667. quoteTable: function(table) {
  668. return this.quoteIdentifiers(table)
  669. },
  670. /**
  671. * Generates an SQL query that returns all foreign keys of a table.
  672. *
  673. * @param {String} tableName The name of the table.
  674. * @param {String} schemaName The name of the schema.
  675. * @return {String} The generated sql query.
  676. */
  677. getForeignKeysQuery: function(tableName, schemaName) {
  678. return "SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '" + tableName + "' LIMIT 1) AND r.contype = 'f' ORDER BY 1;"
  679. },
  680. /**
  681. * Generates an SQL query that removes a foreign key from a table.
  682. *
  683. * @param {String} tableName The name of the table.
  684. * @param {String} foreignKey The name of the foreign key constraint.
  685. * @return {String} The generated sql query.
  686. */
  687. dropForeignKeyQuery: function(tableName, foreignKey) {
  688. return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';'
  689. }
  690. }
  691. // Private
  692. var removeSerialsFromHash = function(tableName, attrValueHash) {
  693. var returning = [];
  694. Utils._.forEach(attrValueHash, function(value, key, hash) {
  695. if (tables[tableName] && tables[tableName][key]) {
  696. switch (tables[tableName][key]) {
  697. case 'bigserial':
  698. case 'serial':
  699. delete hash[key]
  700. returning.push(key)
  701. break
  702. }
  703. }
  704. });
  705. return returning;
  706. }
  707. return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
  708. })()