'use strict'; /* jshint -W110 */ var Utils = require('../../utils') , hstore = require('./hstore') , range = require('./range') , util = require('util') , DataTypes = require('../../data-types') , SqlString = require('../../sql-string') , AbstractQueryGenerator = require('../abstract/query-generator') , primaryKeys = {}; var QueryGenerator = { options: {}, dialect: 'postgres', createSchema: function(schema) { var query = 'CREATE SCHEMA <%= schema%>;'; return Utils._.template(query)({schema: schema}); }, dropSchema: function(schema) { var query = 'DROP SCHEMA <%= schema%> CASCADE;'; return Utils._.template(query)({schema: schema}); }, showSchemasQuery: function() { return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';"; }, versionQuery: function() { return 'SELECT VERSION() as "version"'; }, createTableQuery: function(tableName, attributes, options) { var self = this; options = Utils._.extend({ }, options || {}); primaryKeys[tableName] = []; var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comments %>' , comments = '' , attrStr = [] , i; if (options.comment && Utils._.isString(options.comment)) { comments += '; COMMENT ON TABLE <%= table %> IS ' + this.escape(options.comment); } for (var attr in attributes) { if ((i = attributes[attr].indexOf('COMMENT')) !== -1) { // Move comment to a seperate query comments += '; ' + attributes[attr].substring(i); attributes[attr] = attributes[attr].substring(0, i); } var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr]); attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType); } var values = { table: this.quoteTable(tableName), attributes: attrStr.join(', '), comments: Utils._.template(comments)({ table: this.quoteTable(tableName)}) }; if (!!options.uniqueKeys) { Utils._.each(options.uniqueKeys, function(columns) { if (!columns.singleField) { // If it's a single field its handled in column def, not as an index values.attributes += ', UNIQUE (' + columns.fields.map(function(f) { return self.quoteIdentifiers(f); }).join(', ') + ')'; } }); } var pks = primaryKeys[tableName].map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(','); if (pks.length > 0) { values.attributes += ', PRIMARY KEY (' + pks + ')'; } return Utils._.template(query)(values).trim() + ';'; }, dropTableQuery: function(tableName, options) { options = options || {}; var query = 'DROP TABLE IF EXISTS <%= table %><%= cascade %>;'; return Utils._.template(query)({ table: this.quoteTable(tableName), cascade: options.cascade ? ' CASCADE' : '' }); }, showTablesQuery: function() { return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"; }, describeTableQuery: function(tableName, schema) { if (!schema) { schema = 'public'; } 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 %>'; return Utils._.template(query)({ table: this.escape(tableName), schema: this.escape(schema) }); }, // A recursive parser for nested where conditions parseConditionObject: function(_conditions, path) { var self = this; path = path || []; return Utils._.reduce(_conditions, function (r, v, k) { // result, key, value if (Utils._.isObject(v)) { r = r.concat(self.parseConditionObject(v, path.concat(k))); // Recursively parse objects } else { r.push({ path: path.concat(k), value: v }); } return r; }, []); }, handleSequelizeMethod: function (smth, tableName, factory, options, prepend) { var _ = Utils._; if (smth instanceof Utils.json) { // Parse nested object if (smth.conditions) { var conditions = _.map(this.parseConditionObject(smth.conditions), function generateSql(condition) { return util.format("%s#>>'{%s}' = '%s'", _.first(condition.path), _.rest(condition.path).join(','), condition.value); }); return conditions.join(' and '); } else if (smth.path) { var str; // Allow specifying conditions using the postgres json syntax if (_.any(['->', '->>', '#>'], _.partial(_.contains, smth.path))) { str = smth.path; } else { // Also support json dot notation var path = smth.path.split('.'); str = util.format("%s#>>'{%s}'", _.first(path), _.rest(path).join(',')); } if (smth.value) { str += util.format(' = %s', this.escape(smth.value)); } return str; } } else { return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend); } }, addColumnQuery: function(table, key, dataType) { var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;' , dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'}) , attribute; if (dataType.type && dataType.type instanceof DataTypes.ENUM || dataType instanceof DataTypes.ENUM) { query = this.pgEnum(table, key, dataType) + query; } attribute = Utils._.template('<%= key %> <%= definition %>')({ key: this.quoteIdentifier(key), definition: this.pgDataTypeMapping(table, key, dbDataType) }); return Utils._.template(query)({ table: this.quoteTable(table), attribute: attribute }); }, removeColumnQuery: function(tableName, attributeName) { var query = 'ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;'; return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributeName: this.quoteIdentifier(attributeName) }); }, changeColumnQuery: function(tableName, attributes) { var query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;' , sql = []; for (var attributeName in attributes) { var definition = this.pgDataTypeMapping(tableName, attributeName, attributes[attributeName]); var attrSql = ''; if (definition.indexOf('NOT NULL') > 0) { attrSql += Utils._.template(query)({ tableName: this.quoteTable(tableName), query: this.quoteIdentifier(attributeName) + ' SET NOT NULL' }); definition = definition.replace('NOT NULL', '').trim(); } else { attrSql += Utils._.template(query)({ tableName: this.quoteTable(tableName), query: this.quoteIdentifier(attributeName) + ' DROP NOT NULL' }); } if (definition.indexOf('DEFAULT') > 0) { attrSql += Utils._.template(query)({ tableName: this.quoteTable(tableName), query: this.quoteIdentifier(attributeName) + ' SET DEFAULT ' + definition.match(/DEFAULT ([^;]+)/)[1] }); definition = definition.replace(/(DEFAULT[^;]+)/, '').trim(); } else { attrSql += Utils._.template(query)({ tableName: this.quoteTable(tableName), query: this.quoteIdentifier(attributeName) + ' DROP DEFAULT' }); } if (attributes[attributeName].match(/^ENUM\(/)) { query = this.pgEnum(tableName, attributeName, attributes[attributeName]) + query; definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier('enum_' + tableName + '_' + attributeName)); definition += ' USING (' + this.quoteIdentifier(attributeName) + '::' + this.quoteIdentifier(definition) + ')'; } if (definition.match(/UNIQUE;*$/)) { definition = definition.replace(/UNIQUE;*$/, ''); attrSql += Utils._.template(query.replace('ALTER COLUMN', ''))({ tableName: this.quoteTable(tableName), query: 'ADD CONSTRAINT ' + this.quoteIdentifier(attributeName + '_unique_idx') + ' UNIQUE (' + this.quoteIdentifier(attributeName) + ')' }); } attrSql += Utils._.template(query)({ tableName: this.quoteTable(tableName), query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition }); sql.push(attrSql); } return sql.join(''); }, renameColumnQuery: function(tableName, attrBefore, attributes) { var query = 'ALTER TABLE <%= tableName %> RENAME COLUMN <%= attributes %>;'; var attrString = []; for (var attributeName in attributes) { attrString.push(Utils._.template('<%= before %> TO <%= after %>')({ before: this.quoteIdentifier(attrBefore), after: this.quoteIdentifier(attributeName) })); } return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') }); }, fn: function(fnName, tableName, body, returns, language) { fnName = fnName || 'testfunc'; language = language || 'plpgsql'; returns = returns || 'SETOF ' + this.quoteTable(tableName); var query = 'CREATE OR REPLACE FUNCTION pg_temp.<%= fnName %>() RETURNS <%= returns %> AS $func$ BEGIN <%= body %> END; $func$ LANGUAGE <%= language %>; SELECT * FROM pg_temp.<%= fnName %>();'; return Utils._.template(query)({ fnName: fnName, returns: returns, language: language, body: body }); }, exceptionFn: function(fnName, tableName, main, then, when, returns, language) { when = when || 'unique_violation'; var body = '<%= main %> EXCEPTION WHEN <%= when %> THEN <%= then %>;'; body = Utils._.template(body)({ main: main, when: when, then: then }); return this.fn(fnName, tableName, body, returns, language); }, // http://www.maori.geek.nz/post/postgres_upsert_update_or_insert_in_ger_using_knex_js upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) { var insert = this.insertQuery(tableName, insertValues, rawAttributes, options); var update = this.updateQuery(tableName, updateValues, where, options, rawAttributes); // The numbers here are selected to match the number of affected rows returned by MySQL return this.exceptionFn( 'sequelize_upsert', tableName, insert + ' RETURN 1;', update + '; RETURN 2', 'unique_violation', 'integer' ); }, bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) { options = options || {}; var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>' , tuples = [] , serials = [] , allAttributes = []; if (options.returning) { query += ' RETURNING *'; } Utils._.forEach(attrValueHashes, function(attrValueHash) { Utils._.forOwn(attrValueHash, function(value, key) { if (allAttributes.indexOf(key) === -1) { allAttributes.push(key); } if (modelAttributes && modelAttributes[key] && modelAttributes[key].autoIncrement === true) { serials.push(key); } }); }); Utils._.forEach(attrValueHashes, function(attrValueHash) { tuples.push('(' + allAttributes.map(function(key) { if (serials.indexOf(key) !== -1) { return attrValueHash[key] || 'DEFAULT'; } return this.escape(attrValueHash[key], modelAttributes && modelAttributes[key]); }.bind(this)).join(',') + ')'); }.bind(this)); var replacements = { table: this.quoteTable(tableName) , attributes: allAttributes.map(function(attr) { return this.quoteIdentifier(attr); }.bind(this)).join(',') , tuples: tuples.join(',') }; query = query + ';'; return Utils._.template(query)(replacements); }, deleteQuery: function(tableName, where, options, model) { var query; options = options || {}; tableName = Utils.removeTicks(this.quoteTable(tableName), '"'); if (options.truncate === true) { query = 'TRUNCATE ' + QueryGenerator.quoteIdentifier(tableName); if (options.cascade) { query += ' CASCADE'; } return query; } if (Utils._.isUndefined(options.limit)) { options.limit = 1; } primaryKeys[tableName] = primaryKeys[tableName] || []; if (!!model && primaryKeys[tableName].length < 1) { primaryKeys[tableName] = Object.keys(model.primaryKeys); } if (options.limit) { query = 'DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %><%= where %><%= limit %>)'; } else { query = 'DELETE FROM <%= table %><%= where %>'; } var pks; if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) { pks = primaryKeys[tableName].map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(','); } else { pks = this.quoteIdentifier('id'); } var replacements = { table: this.quoteIdentifiers(tableName), where: this.getWhereConditions(where), limit: !!options.limit ? ' LIMIT ' + this.escape(options.limit) : '', primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks, primaryKeysSelection: pks }; if (replacements.where) { replacements.where = ' WHERE ' + replacements.where; } return Utils._.template(query)(replacements); }, showIndexesQuery: function(tableName) { if (!Utils._.isString(tableName)) { tableName = tableName.tableName; } // This is ARCANE! var query = 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' + 'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' + 'AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a ' + 'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND '+ "t.relkind = 'r' and t.relname = '<%= tableName %>' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;"; return Utils._.template(query)({ tableName: tableName }); }, removeIndexQuery: function(tableName, indexNameOrAttributes) { var sql = 'DROP INDEX IF EXISTS <%= indexName %>' , indexName = indexNameOrAttributes; if (typeof indexName !== 'string') { indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_')); } return Utils._.template(sql)({ tableName: this.quoteIdentifiers(tableName), indexName: this.quoteIdentifiers(indexName) }); }, addLimitAndOffset: function(options) { var fragment = ''; if (options.limit) fragment += ' LIMIT ' + options.limit; if (options.offset) fragment += ' OFFSET ' + options.offset; return fragment; }, attributeToSQL: function(attribute) { if (!Utils._.isPlainObject(attribute)) { attribute = { type: attribute }; } var template = '<%= type %>' , replacements = {}; if (attribute.type instanceof DataTypes.ENUM) { if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values; if (Array.isArray(attribute.values) && (attribute.values.length > 0)) { replacements.type = 'ENUM(' + Utils._.map(attribute.values, function(value) { return this.escape(value); }.bind(this)).join(', ') + ')'; } else { throw new Error("Values for ENUM haven't been defined."); } } if (!replacements.type) { replacements.type = attribute.type; } if (attribute.hasOwnProperty('allowNull') && (!attribute.allowNull)) { template += ' NOT NULL'; } if (attribute.autoIncrement) { template += ' SERIAL'; } if (Utils.defaultValueSchemable(attribute.defaultValue)) { template += ' DEFAULT <%= defaultValue %>'; replacements.defaultValue = this.escape(attribute.defaultValue, attribute); } if (attribute.unique === true) { template += ' UNIQUE'; } if (attribute.primaryKey) { template += ' PRIMARY KEY'; } if (attribute.references) { attribute = Utils.formatReferences(attribute); template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)'; replacements.referencesTable = this.quoteTable(attribute.references.model); if (attribute.references.key) { replacements.referencesKey = this.quoteIdentifiers(attribute.references.key); } else { replacements.referencesKey = this.quoteIdentifier('id'); } if (attribute.onDelete) { template += ' ON DELETE <%= onDeleteAction %>'; replacements.onDeleteAction = attribute.onDelete.toUpperCase(); } if (attribute.onUpdate) { template += ' ON UPDATE <%= onUpdateAction %>'; replacements.onUpdateAction = attribute.onUpdate.toUpperCase(); } if (attribute.references.deferrable) { template += ' <%= deferrable %>'; replacements.deferrable = attribute.references.deferrable.toString(this); } } return Utils._.template(template)(replacements); }, deferConstraintsQuery: function (options) { return options.deferrable.toString(this); }, setConstraintQuery: function (columns, type) { var columnFragment = 'ALL'; if (columns) { columnFragment = columns.map(function (column) { return this.quoteIdentifier(column); }.bind(this)).join(', '); } return 'SET CONSTRAINTS ' + columnFragment + ' ' + type; }, setDeferredQuery: function (columns) { return this.setConstraintQuery(columns, 'DEFERRED'); }, setImmediateQuery: function (columns) { return this.setConstraintQuery(columns, 'IMMEDIATE'); }, attributesToSQL: function(attributes, options) { var result = {} , key , attribute; for (key in attributes) { attribute = attributes[key]; result[attribute.field || key] = this.attributeToSQL(attribute, options); } return result; }, findAutoIncrementField: function(factory) { var fields = []; for (var name in factory.attributes) { var definition = factory.attributes[name]; if (definition && definition.autoIncrement) { fields.push(name); } } return fields; }, createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) { var sql = [ 'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>' , '<%= eventType %> <%= eventSpec %>' , 'ON <%= tableName %>' , '<%= optionsSpec %>' , 'EXECUTE PROCEDURE <%= functionName %>(<%= paramList %>);' ].join('\n\t'); return Utils._.template(sql)({ constraintVal: this.triggerEventTypeIsConstraint(eventType), triggerName: triggerName, eventType: this.decodeTriggerEventType(eventType), eventSpec: this.expandTriggerEventSpec(fireOnSpec), tableName: tableName, optionsSpec: this.expandOptions(optionsArray), functionName: functionName, paramList: this.expandFunctionParamList(functionParams) }); }, dropTrigger: function(tableName, triggerName) { var sql = 'DROP TRIGGER <%= triggerName %> ON <%= tableName %> RESTRICT;'; return Utils._.template(sql)({ triggerName: triggerName, tableName: tableName }); }, renameTrigger: function(tableName, oldTriggerName, newTriggerName) { var sql = 'ALTER TRIGGER <%= oldTriggerName %> ON <%= tableName %> RENAME TO <%= newTriggerName%>;'; return Utils._.template(sql)({ tableName: tableName, oldTriggerName: oldTriggerName, newTriggerName: newTriggerName }); }, createFunction: function(functionName, params, returnType, language, body, options) { var sql = ['CREATE FUNCTION <%= functionName %>(<%= paramList %>)' , 'RETURNS <%= returnType %> AS $func$' , 'BEGIN' , '\t<%= body %>' , 'END;' , "$func$ language '<%= language %>'<%= options %>;" ].join('\n'); return Utils._.template(sql)({ functionName: functionName, paramList: this.expandFunctionParamList(params), returnType: returnType, body: body.replace('\n', '\n\t'), language: language, options: this.expandOptions(options) }); }, dropFunction: function(functionName, params) { // RESTRICT is (currently, as of 9.2) default but we'll be explicit var sql = 'DROP FUNCTION <%= functionName %>(<%= paramList %>) RESTRICT;'; return Utils._.template(sql)({ functionName: functionName, paramList: this.expandFunctionParamList(params) }); }, renameFunction: function(oldFunctionName, params, newFunctionName) { var sql = 'ALTER FUNCTION <%= oldFunctionName %>(<%= paramList %>) RENAME TO <%= newFunctionName %>;'; return Utils._.template(sql)({ oldFunctionName: oldFunctionName, paramList: this.expandFunctionParamList(params), newFunctionName: newFunctionName }); }, databaseConnectionUri: function(config) { var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %><% if(ssl) { %>?ssl=<%= ssl %><% } %>'; return Utils._.template(template)({ user: config.username, password: config.password, database: config.database, host: config.host, port: config.port, protocol: config.protocol, ssl: config.ssl }); }, pgEscapeAndQuote: function(val) { return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'")); }, expandFunctionParamList: function expandFunctionParamList(params) { if (Utils._.isUndefined(params) || !Utils._.isArray(params)) { throw new Error('expandFunctionParamList: function parameters array required, including an empty one for no arguments'); } var paramList = Utils._.each(params, function expandParam(curParam) { var paramDef = []; if (Utils._.has(curParam, 'type')) { if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam.direction); } if (Utils._.has(curParam, 'name')) { paramDef.push(curParam.name); } paramDef.push(curParam.type); } else { throw new Error('createFunction called with a parameter with no type'); } return paramDef.join(' '); }); return paramList.join(', '); }, expandOptions: function expandOptions(options) { return Utils._.isUndefined(options) || Utils._.isEmpty(options) ? '' : '\n\t' + options.join('\n\t'); }, decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) { var EVENT_DECODER = { 'after': 'AFTER', 'before': 'BEFORE', 'instead_of': 'INSTEAD OF', 'after_constraint': 'AFTER' }; if (!Utils._.has(EVENT_DECODER, eventSpecifier)) { throw new Error('Invalid trigger event specified: ' + eventSpecifier); } return EVENT_DECODER[eventSpecifier]; }, triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) { return eventSpecifier === 'after_constrain' ? 'CONSTRAINT ' : ''; }, expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) { if (Utils._.isEmpty(fireOnSpec)) { throw new Error('no table change events specified to trigger on'); } return Utils._.map(fireOnSpec, function parseTriggerEventSpec(fireValue, fireKey) { var EVENT_MAP = { 'insert': 'INSERT', 'update': 'UPDATE', 'delete': 'DELETE', 'truncate': 'TRUNCATE' }; if (!Utils._.has(EVENT_MAP, fireKey)) { throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey); } var eventSpec = EVENT_MAP[fireKey]; if (eventSpec === 'UPDATE') { if (Utils._.isArray(fireValue) && fireValue.length > 0) { eventSpec += ' OF ' + fireValue.join(', '); } } return eventSpec; }).join(' OR '); }, pgListEnums: function(tableName, attrName, options) { if (arguments.length === 1) { options = tableName; tableName = null; } var enumName = ''; if (!!tableName && !!attrName) { enumName = ' AND t.typname=' + this.escape('enum_' + tableName + '_' + attrName) + ' '; } var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel) enum_value FROM pg_type t ' + 'JOIN pg_enum e ON t.oid = e.enumtypid ' + 'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' + "WHERE n.nspname = 'public' " + enumName + ' GROUP BY 1'; return query; }, pgEnum: function(tableName, attr, dataType, options) { var enumName = this.pgEscapeAndQuote('enum_' + tableName + '_' + attr) , values; if (dataType.values) { values = "ENUM('" + dataType.values.join("', '") + "')"; } else { values = dataType.toString().match(/^ENUM\(.+\)/)[0]; } var sql = 'CREATE TYPE ' + enumName + ' AS ' + values + '; '; if (!!options && options.force === true) { sql = this.pgEnumDrop(tableName, attr) + sql; } return sql; }, pgEnumAdd: function(tableName, attr, value, options) { var enumName = this.pgEscapeAndQuote('enum_' + tableName + '_' + attr); var sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ' + this.escape(value); if (!!options.before) { sql += ' BEFORE ' + this.escape(options.before); } else if (!!options.after) { sql += ' AFTER ' + this.escape(options.after); } return sql; }, pgEnumDrop: function(tableName, attr, enumName) { enumName = enumName || this.pgEscapeAndQuote('enum_' + tableName + '_' + attr); return 'DROP TYPE IF EXISTS ' + enumName + '; '; }, fromArray: function(text) { text = text.replace(/^{/, '').replace(/}$/, ''); var matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig); if (matches.length < 1) { return []; } matches = matches.map(function(m) { return m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, ''); }); return matches.slice(0, -1); }, padInt: function(i) { return (i < 10) ? '0' + i.toString() : i.toString(); }, pgDataTypeMapping: function(tableName, attr, dataType) { return this.dataTypeMapping(tableName, attr, dataType); }, dataTypeMapping: function(tableName, attr, dataType) { if (Utils._.includes(dataType, 'PRIMARY KEY')) { primaryKeys[tableName].push(attr); dataType = dataType.replace(/PRIMARY KEY/, ''); } if (Utils._.includes(dataType, 'SERIAL')) { if (Utils._.includes(dataType, 'BIGINT')) { dataType = dataType.replace(/SERIAL/, 'BIGSERIAL'); dataType = dataType.replace(/BIGINT/, ''); } else { dataType = dataType.replace(/INTEGER/, ''); } dataType = dataType.replace(/NOT NULL/, ''); } if (dataType.match(/^ENUM\(/)) { dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote('enum_' + tableName + '_' + attr)); } return dataType; }, quoteIdentifier: function(identifier, force) { if (identifier === '*') return identifier; if (!force && this.options && this.options.quoteIdentifiers === false) { // default is `true` // In Postgres, if tables or attributes are created double-quoted, // they are also case sensitive. If they contain any uppercase // characters, they must always be double-quoted. This makes it // impossible to write queries in portable SQL if tables are created in // this way. Hence, we strip quotes if we don't want case sensitivity. return Utils.removeTicks(identifier, '"'); } else { return Utils.addTicks(identifier, '"'); } }, /* Escape a value (e.g. a string, number or date) */ escape: function(value, field) { if (value && value._isSequelizeMethod) { return this.handleSequelizeMethod(value); } if (Utils._.isObject(value) && field && (field.type instanceof DataTypes.HSTORE || DataTypes.ARRAY.is(field.type, DataTypes.HSTORE))) { if (field.type instanceof DataTypes.HSTORE){ return "'" + hstore.stringify(value) + "'"; } else if (DataTypes.ARRAY.is(field.type, DataTypes.HSTORE)) { return 'ARRAY[' + Utils._.map(value, function(v){return "'" + hstore.stringify(v) + "'::hstore";}).join(',') + ']::HSTORE[]'; } } else if(Utils._.isArray(value) && field && (field.type instanceof DataTypes.RANGE || DataTypes.ARRAY.is(field.type, DataTypes.RANGE))) { if(field.type instanceof DataTypes.RANGE) { // escape single value return "'" + range.stringify(value) + "'"; } else if (DataTypes.ARRAY.is(field.type, DataTypes.RANGE)) { // escape array of ranges return 'ARRAY[' + Utils._.map(value, function(v){return "'" + range.stringify(v) + "'";}).join(',') + ']::' + field.type.toString(); } } else if (value!==null && field && field.type instanceof DataTypes.JSON) { value = JSON.stringify(value); } else if (Array.isArray(value) && field && DataTypes.ARRAY.is(field.type, DataTypes.JSON)) { var jsonType = field.type.type; // type may be JSON or JSONB return 'ARRAY[' + value.map(function (v) { return SqlString.escape(JSON.stringify(v), false, this.options.timezone, this.dialect, field); }, this).join(',') + ']::' + jsonType.key + '[]'; } return SqlString.escape(value, false, this.options.timezone, this.dialect, field); }, /** * Generates an SQL query that returns all foreign keys of a table. * * @param {String} tableName The name of the table. * @param {String} schemaName The name of the schema. * @return {String} The generated sql query. */ getForeignKeysQuery: function(tableName, schemaName) { 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;"; }, /** * Generates an SQL query that removes a foreign key from a table. * * @param {String} tableName The name of the table. * @param {String} foreignKey The name of the foreign key constraint. * @return {String} The generated sql query. */ dropForeignKeyQuery: function(tableName, foreignKey) { return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';'; }, setAutocommitQuery: function(value, options) { if (options.parent) { return; } // POSTGRES does not support setting AUTOCOMMIT = OFF if (!value) { return; } return AbstractQueryGenerator.setAutocommitQuery.call(this, value, options); } }; module.exports = Utils._.extend(Utils._.clone(AbstractQueryGenerator), QueryGenerator);