不要怂,就是干,撸起袖子干!

Commit 3107396d by Felix Becker Committed by Jan Aagaard Meier

ES6 refactor: dialects / PostgreSQL (#6048)

* ES6 refactor of hstore.js

const, export

* ES6 refactor of PostgresDialect

const, classes, property shorthands, export default

* ES6 refactor of postgres ConnectionManager

classes, let, const, arrow functions, for of, export default

* Make postgres Query an ES6 class

* ES6 refactor of postgres Query

let, const, arrow functions, property shorthands, export default

* ES6 refactor of postgres QueryGenerator

let, const, arrow functions, template strings where it was easy to implement

* ES6 refactor of postgres range.js

let, const, arrow functions, export
1 parent 3e911332
'use strict';
var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager
, Utils = require('../../utils')
, Promise = require('../../promise')
, sequelizeErrors = require('../../errors')
, semver = require('semver')
, dataTypes = require('../../data-types')
, moment = require('moment-timezone');
ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize);
this.sequelize = sequelize;
this.sequelize.config.port = this.sequelize.config.port || 5432;
try {
var pgLib;
if (sequelize.config.dialectModulePath) {
pgLib = require(sequelize.config.dialectModulePath);
} else {
pgLib = require('pg');
}
this.lib = sequelize.config.native ? pgLib.native : pgLib;
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
throw new Error('Please install \'' + (sequelize.config.dialectModulePath || 'pg') + '\' module manually');
const AbstractConnectionManager = require('../abstract/connection-manager');
const Utils = require('../../utils');
const Promise = require('../../promise');
const sequelizeErrors = require('../../errors');
const semver = require('semver');
const dataTypes = require('../../data-types');
const moment = require('moment-timezone');
class ConnectionManager extends AbstractConnectionManager {
constructor(dialect, sequelize) {
super(dialect, sequelize);
this.sequelize = sequelize;
this.sequelize.config.port = this.sequelize.config.port || 5432;
try {
let pgLib;
if (sequelize.config.dialectModulePath) {
pgLib = require(sequelize.config.dialectModulePath);
} else {
pgLib = require('pg');
}
this.lib = sequelize.config.native ? pgLib.native : pgLib;
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
throw new Error('Please install \'' + (sequelize.config.dialectModulePath || 'pg') + '\' module manually');
}
throw err;
}
throw err;
this.refreshTypeParser(dataTypes.postgres);
}
this.refreshTypeParser(dataTypes.postgres);
};
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
$refreshTypeParser(dataType) {
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.$refreshTypeParser = function (dataType) {
var self = this;
if (dataType.types.postgres.oids) {
for (const oid of dataType.types.postgres.oids) {
this.lib.types.setTypeParser(oid, value => dataType.parse(value, oid, this.lib.types.getTypeParser));
}
}
if (dataType.types.postgres.oids) {
dataType.types.postgres.oids.forEach(function (oid) {
self.lib.types.setTypeParser(oid, function (value) {
return dataType.parse(value, oid, self.lib.types.getTypeParser);
});
});
if (dataType.types.postgres.array_oids) {
for (const oid of dataType.types.postgres.array_oids) {
this.lib.types.setTypeParser(oid, value =>
this.lib.types.arrayParser.create(value, value =>
dataType.parse(value, oid, this.lib.types.getTypeParser)
).parse()
);
}
}
}
if (dataType.types.postgres.array_oids) {
dataType.types.postgres.array_oids.forEach(function (oid) {
self.lib.types.setTypeParser(oid, function (value) {
return self.lib.types.arrayParser.create(value, function (value) {
return dataType.parse(value, oid, self.lib.types.getTypeParser);
}).parse();
});
});
}
};
ConnectionManager.prototype.connect = function(config) {
var self = this
, connectionConfig = {};
config.user = config.username;
connectionConfig = Utils._.pick(config, [
'user', 'password', 'host', 'database', 'port'
]);
if (config.dialectOptions) {
Utils._.merge(connectionConfig,
Utils._.pick(config.dialectOptions, [
// see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME]
'application_name',
// choose the SSL mode with the PGSSLMODE environment variable
// object format: [https://github.com/brianc/node-postgres/blob/master/lib/connection.js#L79]
// see also [http://www.postgresql.org/docs/9.3/static/libpq-ssl.html]
'ssl',
// In addition to the values accepted by the corresponding server,
// you can use "auto" to determine the right encoding from the
// current locale in the client (LC_CTYPE environment variable on Unix systems)
'client_encoding',
// !! DONT SET THIS TO TRUE !!
// (unless you know what you're doing)
// see [http://www.postgresql.org/message-id/flat/bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com#bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com]
'binary'
]));
}
connect(config) {
config.user = config.username;
const connectionConfig = Utils._.pick(config, [
'user', 'password', 'host', 'database', 'port'
]);
if (config.dialectOptions) {
Utils._.merge(connectionConfig,
Utils._.pick(config.dialectOptions, [
// see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME]
'application_name',
// choose the SSL mode with the PGSSLMODE environment variable
// object format: [https://github.com/brianc/node-postgres/blob/master/lib/connection.js#L79]
// see also [http://www.postgresql.org/docs/9.3/static/libpq-ssl.html]
'ssl',
// In addition to the values accepted by the corresponding server,
// you can use "auto" to determine the right encoding from the
// current locale in the client (LC_CTYPE environment variable on Unix systems)
'client_encoding',
// !! DONT SET THIS TO TRUE !!
// (unless you know what you're doing)
// see [http://www.postgresql.org/message-id/flat/bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com#bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com]
'binary'
]));
}
return new Promise(function (resolve, reject) {
var connection = new self.lib.Client(connectionConfig)
, responded = false;
connection.connect(function(err) {
if (err) {
if (err.code) {
switch (err.code) {
case 'ECONNREFUSED':
reject(new sequelizeErrors.ConnectionRefusedError(err));
break;
case 'ENOTFOUND':
reject(new sequelizeErrors.HostNotFoundError(err));
break;
case 'EHOSTUNREACH':
reject(new sequelizeErrors.HostNotReachableError(err));
break;
case 'EINVAL':
reject(new sequelizeErrors.InvalidConnectionError(err));
break;
default:
return new Promise((resolve, reject) => {
const connection = new this.lib.Client(connectionConfig);
let responded = false;
connection.connect(err => {
if (err) {
if (err.code) {
switch (err.code) {
case 'ECONNREFUSED':
reject(new sequelizeErrors.ConnectionRefusedError(err));
break;
case 'ENOTFOUND':
reject(new sequelizeErrors.HostNotFoundError(err));
break;
case 'EHOSTUNREACH':
reject(new sequelizeErrors.HostNotReachableError(err));
break;
case 'EINVAL':
reject(new sequelizeErrors.InvalidConnectionError(err));
break;
default:
reject(new sequelizeErrors.ConnectionError(err));
break;
}
} else {
reject(new sequelizeErrors.ConnectionError(err));
break;
}
} else {
reject(new sequelizeErrors.ConnectionError(err));
return;
}
return;
}
responded = true;
resolve(connection);
});
// If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted
connection.on('end', function () {
if (!responded) {
reject(new sequelizeErrors.ConnectionTimedOutError(new Error('Connection timed out')));
}
});
responded = true;
resolve(connection);
});
// Don't let a Postgres restart (or error) to take down the whole app
connection.on('error', function() {
connection._invalid = true;
});
}).tap(function (connection) {
// Disable escape characters in strings, see https://github.com/sequelize/sequelize/issues/3545
var query = '';
// If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted
connection.on('end', () => {
if (!responded) {
reject(new sequelizeErrors.ConnectionTimedOutError(new Error('Connection timed out')));
}
});
if (self.sequelize.options.databaseVersion !== 0 && semver.gte(self.sequelize.options.databaseVersion, '8.2.0')) {
query += 'SET standard_conforming_strings=on;';
}
// Don't let a Postgres restart (or error) to take down the whole app
connection.on('error', () => {
connection._invalid = true;
});
}).tap(connection => {
// Disable escape characters in strings, see https://github.com/sequelize/sequelize/issues/3545
let query = '';
if (!self.sequelize.config.keepDefaultTimezone) {
var isZone = !!moment.tz.zone(self.sequelize.options.timezone);
if (isZone) {
query += 'SET client_min_messages TO warning; SET TIME ZONE \'' + self.sequelize.options.timezone + '\';';
} else {
query += 'SET client_min_messages TO warning; SET TIME ZONE INTERVAL \'' + self.sequelize.options.timezone + '\' HOUR TO MINUTE;';
if (this.sequelize.options.databaseVersion !== 0 && semver.gte(this.sequelize.options.databaseVersion, '8.2.0')) {
query += 'SET standard_conforming_strings=on;';
}
}
// oids for hstore and geometry are dynamic - so select them at connection time
if (dataTypes.HSTORE.types.postgres.oids.length === 0) {
query += 'SELECT typname, oid, typarray FROM pg_type WHERE typtype = \'b\' AND typname IN (\'hstore\', \'geometry\', \'geography\')';
}
return new Promise(function (resolve, reject) {
connection.query(query).on('error', function (err) {
reject(err);
}).on('row', function (row) {
var type;
if (row.typname === 'geometry') {
type = dataTypes.postgres.GEOMETRY;
} else if (row.typname === 'hstore') {
type = dataTypes.postgres.HSTORE;
} else if (row.typname === 'geography'){
type = dataTypes.postgres.GEOGRAPHY;
if (!this.sequelize.config.keepDefaultTimezone) {
const isZone = !!moment.tz.zone(this.sequelize.options.timezone);
if (isZone) {
query += 'SET client_min_messages TO warning; SET TIME ZONE \'' + this.sequelize.options.timezone + '\';';
} else {
query += 'SET client_min_messages TO warning; SET TIME ZONE INTERVAL \'' + this.sequelize.options.timezone + '\' HOUR TO MINUTE;';
}
}
type.types.postgres.oids.push(row.oid);
type.types.postgres.array_oids.push(row.typarray);
// oids for hstore and geometry are dynamic - so select them at connection time
if (dataTypes.HSTORE.types.postgres.oids.length === 0) {
query += 'SELECT typname, oid, typarray FROM pg_type WHERE typtype = \'b\' AND typname IN (\'hstore\', \'geometry\', \'geography\')';
}
self.$refreshTypeParser(type);
}).on('end', function () {
resolve();
return new Promise((resolve, reject) => {
connection.query(query)
.on('error', err => reject(err))
.on('row', row => {
let type;
if (row.typname === 'geometry') {
type = dataTypes.postgres.GEOMETRY;
} else if (row.typname === 'hstore') {
type = dataTypes.postgres.HSTORE;
} else if (row.typname === 'geography'){
type = dataTypes.postgres.GEOGRAPHY;
}
type.types.postgres.oids.push(row.oid);
type.types.postgres.array_oids.push(row.typarray);
this.$refreshTypeParser(type);
})
.on('end', () => resolve());
});
});
});
};
ConnectionManager.prototype.disconnect = function(connection) {
return new Promise(function (resolve, reject) {
connection.end();
resolve();
});
};
ConnectionManager.prototype.validate = function(connection) {
return connection._invalid === undefined;
};
}
disconnect(connection) {
return new Promise((resolve, reject) => {
connection.end();
resolve();
});
}
validate(connection) {
return connection._invalid === undefined;
}
}
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
module.exports = ConnectionManager;
module.exports.ConnectionManager = ConnectionManager;
module.exports.default = ConnectionManager;
'use strict';
var hstore = require('pg-hstore')({sanitize : true});
const hstore = require('pg-hstore')({sanitize : true});
function stringify (data) {
if (data === null) return null;
return hstore.stringify(data);
}
exports.stringify = stringify;
function parse (value) {
if (value === null) return null;
return hstore.parse(value);
}
module.exports = {
stringify: stringify,
parse: parse
};
exports.parse = parse;
'use strict';
var _ = require('lodash')
, Abstract = require('../abstract')
, ConnectionManager = require('./connection-manager')
, Query = require('./query')
, QueryGenerator = require('./query-generator')
, DataTypes = require('../../data-types').postgres;
const _ = require('lodash');
const AbstractDialect = require('../abstract');
const ConnectionManager = require('./connection-manager');
const Query = require('./query');
const QueryGenerator = require('./query-generator');
const DataTypes = require('../../data-types').postgres;
var PostgresDialect = function(sequelize) {
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.connectionManager.initPools();
class PostgresDialect extends AbstractDialect {
constructor(sequelize) {
super();
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.connectionManager.initPools();
this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options,
_dialect: this,
sequelize: sequelize
});
};
this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options,
_dialect: this,
sequelize
});
}
}
PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), {
PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), {
'DEFAULT VALUES': true,
'EXCEPTION': true,
'ON DUPLICATE KEY': false,
......@@ -59,3 +62,5 @@ PostgresDialect.prototype.TICK_CHAR_LEFT = PostgresDialect.prototype.TICK_CHAR;
PostgresDialect.prototype.TICK_CHAR_RIGHT = PostgresDialect.prototype.TICK_CHAR;
module.exports = PostgresDialect;
module.exports.default = PostgresDialect;
module.exports.PostgresDialect = PostgresDialect;
'use strict';
/* jshint -W110 */
var Utils = require('../../utils')
, util = require('util')
, DataTypes = require('../../data-types')
, AbstractQueryGenerator = require('../abstract/query-generator')
, semver = require('semver')
, _ = require('lodash');
var QueryGenerator = {
const Utils = require('../../utils');
const util = require('util');
const DataTypes = require('../../data-types');
const AbstractQueryGenerator = require('../abstract/query-generator');
const semver = require('semver');
const _ = require('lodash');
const QueryGenerator = {
options: {},
dialect: 'postgres',
setSearchPath: function(searchPath) {
var query = 'SET search_path to <%= searchPath%>;';
return Utils._.template(query)({searchPath: searchPath});
setSearchPath(searchPath) {
return `SET search_path to ${searchPath};`;
},
createSchema: function(schema) {
var query = 'CREATE SCHEMA <%= schema%>;';
return Utils._.template(query)({schema: schema});
createSchema(schema) {
return `CREATE SCHEMA ${schema};`;
},
dropSchema: function(schema) {
var query = 'DROP SCHEMA IF EXISTS <%= schema%> CASCADE;';
return Utils._.template(query)({schema: schema});
dropSchema(schema) {
return `DROP SCHEMA IF EXISTS ${schema} CASCADE;`;
},
showSchemasQuery: function() {
showSchemasQuery() {
return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';";
},
versionQuery: function() {
versionQuery() {
return 'SHOW SERVER_VERSION';
},
createTableQuery: function(tableName, attributes, options) {
var self = this;
createTableQuery(tableName, attributes, options) {
options = Utils._.extend({
}, options || {});
var databaseVersion = Utils._.get(self, 'sequelize.options.databaseVersion', 0);
//Postgres 9.0 does not support CREATE TABLE IF NOT EXISTS, 9.1 and above do
var query = 'CREATE TABLE ' +
( (databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0')) ? 'IF NOT EXISTS ' : '') +
'<%= table %> (<%= attributes%>)<%= comments %>'
, comments = ''
, attrStr = []
, i;
const databaseVersion = Utils._.get(this, 'sequelize.options.databaseVersion', 0);
const attrStr = [];
let comments = '';
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) {
for (const attr in attributes) {
const i = attributes[attr].indexOf('COMMENT');
if (i !== -1) {
// Move comment to a separate query
comments += '; ' + attributes[attr].substring(i);
attributes[attr] = attributes[attr].substring(0, i);
}
var dataType = this.dataTypeMapping(tableName, attr, attributes[attr]);
const dataType = this.dataTypeMapping(tableName, attr, attributes[attr]);
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
}
var values = {
const 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) {
Utils._.each(options.uniqueKeys, 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(', ') + ')';
values.attributes += ', UNIQUE (' + columns.fields.map(f => this.quoteIdentifiers(f)).join(', ') + ')';
}
});
}
var pks = _.reduce(attributes, function (acc, attribute, key) {
const pks = _.reduce(attributes, (acc, attribute, key) => {
if (_.includes(attribute, 'PRIMARY KEY')) {
acc.push(this.quoteIdentifier(key));
}
return acc;
}.bind(this), []).join(',');
}, []).join(',');
if (pks.length > 0) {
values.attributes += ', PRIMARY KEY (' + pks + ')';
}
return Utils._.template(query)(values).trim() + ';';
return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${values.table} (${values.attributes})${values.comments};`;
},
dropTableQuery: function(tableName, options) {
dropTableQuery(tableName, options) {
options = options || {};
var query = 'DROP TABLE IF EXISTS <%= table %><%= cascade %>;';
return Utils._.template(query)({
table: this.quoteTable(tableName),
cascade: options.cascade ? ' CASCADE' : ''
});
return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)}${options.cascade ? ' CASCADE' : ''};`;
},
showTablesQuery: function() {
showTablesQuery() {
return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type LIKE '%TABLE' AND table_name != 'spatial_ref_sys';";
},
describeTableQuery: function(tableName, schema) {
describeTableQuery(tableName, schema) {
if (!schema) {
schema = 'public';
}
var query = 'SELECT tc.constraint_type as "Constraint", c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", ' +
return 'SELECT tc.constraint_type as "Constraint", 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 ' +
'LEFT JOIN information_schema.key_column_usage cu ON c.table_name = cu.table_name AND cu.column_name = c.column_name ' +
'LEFT JOIN information_schema.table_constraints tc ON c.table_name = tc.table_name AND cu.column_name = c.column_name AND tc.constraint_type = \'PRIMARY KEY\' ' +
' WHERE c.table_name = <%= table %> AND c.table_schema = <%= schema %> ';
return Utils._.template(query)({
table: this.escape(tableName),
schema: this.escape(schema)
});
`WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)} `;
},
// A recursive parser for nested where conditions
parseConditionObject: function(_conditions, path) {
var self = this;
parseConditionObject(_conditions, path) {
path = path || [];
return Utils._.reduce(_conditions, function (r, v, k) { // result, key, value
return Utils._.reduce(_conditions, (r, v, k) => { // result, key, value
if (Utils._.isObject(v)) {
r = r.concat(self.parseConditionObject(v, path.concat(k))); // Recursively parse objects
r = r.concat(this.parseConditionObject(v, path.concat(k))); // Recursively parse objects
} else {
r.push({ path: path.concat(k), value: v });
}
......@@ -141,30 +123,25 @@ var QueryGenerator = {
}, []);
},
handleSequelizeMethod: function (smth, tableName, factory, options, prepend) {
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
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),
_.tail(condition.path).join(','),
condition.value);
});
const conditions = _.map(this.parseConditionObject(smth.conditions), condition =>
`${_.first(condition.path)}#>>'{${_.tail(condition.path).join(',')}}' = '${condition.value}'`
);
return conditions.join(' and ');
} else if (smth.path) {
var str;
let str;
// Allow specifying conditions using the postgres json syntax
if (_.some(['->', '->>', '#>'], _.partial(_.includes, smth.path))) {
str = smth.path;
} else {
// Also support json dot notation
var path = smth.path.split('.');
str = util.format("%s#>>'{%s}'",
_.first(path),
_.tail(path).join(','));
const path = smth.path.split('.');
str = `${_.first(path)}#>>'{${_.tail(path).join(',')}}'`;
}
if (smth.value) {
......@@ -178,41 +155,35 @@ var QueryGenerator = {
}
},
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;'
, dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'})
, attribute;
addColumnQuery(table, key, dataType) {
const dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'});
const definition = this.dataTypeMapping(table, key, dbDataType);
const quotedKey = this.quoteIdentifier(key);
const quotedTable = this.quoteTable(this.extractTableDetails(table));
let query = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedKey} ${definition};`;
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.dataTypeMapping(table, key, dbDataType)
});
return Utils._.template(query)({
table: this.quoteTable(this.extractTableDetails(table)),
attribute: attribute
});
return query;
},
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(this.extractTableDetails(tableName)),
attributeName: this.quoteIdentifier(attributeName)
});
removeColumnQuery(tableName, attributeName) {
const quotedTableName = this.quoteTable(this.extractTableDetails(tableName));
const quotedAttributeName = this.quoteIdentifier(attributeName);
return `ALTER TABLE ${quotedTableName} DROP COLUMN ${quotedAttributeName};`;
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;'
, sql = [];
changeColumnQuery(tableName, attributes) {
let query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;';
const sql = [];
for (var attributeName in attributes) {
var definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]);
var attrSql = '';
for (const attributeName in attributes) {
let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]);
let attrSql = '';
if (definition.indexOf('NOT NULL') > 0) {
attrSql += Utils._.template(query)({
......@@ -276,54 +247,39 @@ var QueryGenerator = {
return sql.join('');
},
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = 'ALTER TABLE <%= tableName %> RENAME COLUMN <%= attributes %>;';
var attrString = [];
renameColumnQuery(tableName, attrBefore, attributes) {
const attrString = [];
for (var attributeName in attributes) {
for (const 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(', ')
});
return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`;
},
fn: function(fnName, tableName, body, returns, language) {
fn(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
});
return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}() RETURNS ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`;
},
exceptionFn: function(fnName, tableName, main, then, when, returns, language) {
exceptionFn(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
});
const body = `${main} EXCEPTION WHEN ${when} THEN ${then};`;
return this.fn(fnName, tableName, body, returns, language);
},
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);
upsertQuery(tableName, insertValues, updateValues, where, rawAttributes, options) {
const insert = this.insertQuery(tableName, insertValues, rawAttributes, options);
const 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(
......@@ -336,8 +292,8 @@ var QueryGenerator = {
);
},
deleteQuery: function(tableName, where, options, model) {
var query;
deleteQuery(tableName, where, options, model) {
let query;
options = options || {};
......@@ -357,7 +313,7 @@ var QueryGenerator = {
options.limit = 1;
}
var replacements = {
const replacements = {
table: tableName,
where: this.getWhereConditions(where, null, model, options),
limit: !!options.limit ? ' LIMIT ' + this.escape(options.limit) : ''
......@@ -368,9 +324,7 @@ var QueryGenerator = {
throw new Error('Cannot LIMIT delete without a model.');
}
var pks = _.map(_.values(model.primaryKeys), function (pk) {
return this.quoteIdentifier((pk.field));
}.bind(this)).join(',');
const pks = _.map(_.values(model.primaryKeys), pk => this.quoteIdentifier((pk.field))).join(',');
replacements.primaryKeys = model.primaryKeyAttributes.length > 1 ? '(' + pks + ')' : pks;
replacements.primaryKeysSelection = pks;
......@@ -387,41 +341,36 @@ var QueryGenerator = {
return Utils._.template(query)(replacements);
},
showIndexesQuery: function(tableName) {
var schemaJoin = '', schemaWhere = '';
showIndexesQuery(tableName) {
let schemaJoin = '';
let schemaWhere = '';
if (!Utils._.isString(tableName)) {
schemaJoin = ', pg_namespace s';
schemaWhere = Utils._.template(" AND s.oid = t.relnamespace AND s.nspname = '<%= schemaName %>'")({schemaName: tableName.schema});
schemaWhere = ` AND s.oid = t.relnamespace AND s.nspname = '${tableName.schema}'`;
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, ' +
return '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<%= schemaJoin%> ' +
`AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a${schemaJoin} ` +
'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND ' +
"t.relkind = 'r' and t.relname = '<%= tableName %>'<%= schemaWhere%> " +
`t.relkind = 'r' and t.relname = '${tableName}'${schemaWhere} ` +
'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;';
return Utils._.template(query)({tableName: tableName, schemaJoin: schemaJoin, schemaWhere: schemaWhere});
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX IF EXISTS <%= indexName %>'
, indexName = indexNameOrAttributes;
removeIndexQuery(tableName, indexNameOrAttributes) {
let 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)
});
return `DROP INDEX IF EXISTS ${this.quoteIdentifiers(indexName)}`;
},
addLimitAndOffset: function(options) {
var fragment = '';
addLimitAndOffset(options) {
let fragment = '';
/*jshint eqeqeq:false*/
if (options.limit != null) {
fragment += ' LIMIT ' + this.escape(options.limit);
......@@ -433,124 +382,116 @@ var QueryGenerator = {
return fragment;
},
attributeToSQL: function(attribute) {
attributeToSQL(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
var template = '<%= type %>'
, replacements = {};
let type;
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(', ') + ')';
type = 'ENUM(' + Utils._.map(attribute.values, value => this.escape(value)).join(', ') + ')';
} else {
throw new Error("Values for ENUM haven't been defined.");
}
}
if (!replacements.type) {
replacements.type = attribute.type;
if (!type) {
type = attribute.type;
}
let sql = type + '';
if (attribute.hasOwnProperty('allowNull') && (!attribute.allowNull)) {
template += ' NOT NULL';
sql += ' NOT NULL';
}
if (attribute.autoIncrement) {
template += ' SERIAL';
sql += ' SERIAL';
}
if (Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT <%= defaultValue %>';
replacements.defaultValue = this.escape(attribute.defaultValue, attribute);
sql += ' DEFAULT ' + this.escape(attribute.defaultValue, attribute);
}
if (attribute.unique === true) {
template += ' UNIQUE';
sql += ' UNIQUE';
}
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
sql += ' PRIMARY KEY';
}
if (attribute.references) {
template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)';
replacements.referencesTable = this.quoteTable(attribute.references.model);
const referencesTable = this.quoteTable(attribute.references.model);
let referencesKey;
if (attribute.references.key) {
replacements.referencesKey = this.quoteIdentifiers(attribute.references.key);
referencesKey = this.quoteIdentifiers(attribute.references.key);
} else {
replacements.referencesKey = this.quoteIdentifier('id');
referencesKey = this.quoteIdentifier('id');
}
sql += ` REFERENCES ${referencesTable} (${referencesKey})`;
if (attribute.onDelete) {
template += ' ON DELETE <%= onDeleteAction %>';
replacements.onDeleteAction = attribute.onDelete.toUpperCase();
sql += ' ON DELETE ' + attribute.onDelete.toUpperCase();
}
if (attribute.onUpdate) {
template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = attribute.onUpdate.toUpperCase();
sql += ' ON UPDATE ' + attribute.onUpdate.toUpperCase();
}
if (attribute.references.deferrable) {
template += ' <%= deferrable %>';
replacements.deferrable = attribute.references.deferrable.toString(this);
sql += ' ' + attribute.references.deferrable.toString(this);
}
}
return Utils._.template(template)(replacements);
return sql;
},
deferConstraintsQuery: function (options) {
deferConstraintsQuery(options) {
return options.deferrable.toString(this);
},
setConstraintQuery: function (columns, type) {
var columnFragment = 'ALL';
setConstraintQuery(columns, type) {
let columnFragment = 'ALL';
if (columns) {
columnFragment = columns.map(function (column) {
return this.quoteIdentifier(column);
}.bind(this)).join(', ');
columnFragment = columns.map(column => this.quoteIdentifier(column)).join(', ');
}
return 'SET CONSTRAINTS ' + columnFragment + ' ' + type;
},
setDeferredQuery: function (columns) {
setDeferredQuery(columns) {
return this.setConstraintQuery(columns, 'DEFERRED');
},
setImmediateQuery: function (columns) {
setImmediateQuery(columns) {
return this.setConstraintQuery(columns, 'IMMEDIATE');
},
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute;
attributesToSQL(attributes, options) {
const result = {};
for (key in attributes) {
attribute = attributes[key];
for (const key in attributes) {
const attribute = attributes[key];
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
return result;
},
findAutoIncrementField: function(factory) {
var fields = [];
findAutoIncrementField(factory) {
const fields = [];
for (var name in factory.attributes) {
var definition = factory.attributes[name];
for (const name in factory.attributes) {
const definition = factory.attributes[name];
if (definition && definition.autoIncrement) {
fields.push(name);
......@@ -560,106 +501,76 @@ var QueryGenerator = {
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)
});
createTrigger(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
const decodedEventType = this.decodeTriggerEventType(eventType);
const eventSpec = this.expandTriggerEventSpec(fireOnSpec);
const expandedOptions = this.expandOptions(optionsArray);
const paramList = this.expandFunctionParamList(functionParams);
return `CREATE ${this.triggerEventTypeIsConstraint(eventType)}TRIGGER ${triggerName}\n`
+ `\t${decodedEventType} ${eventSpec}\n`
+ `\tON ${tableName}\n`
+ `\t${expandedOptions}\n`
+ `\tEXECUTE PROCEDURE ${functionName}(${paramList});`;
},
dropTrigger: function(tableName, triggerName) {
var sql = 'DROP TRIGGER <%= triggerName %> ON <%= tableName %> RESTRICT;';
return Utils._.template(sql)({
triggerName: triggerName,
tableName: tableName
});
dropTrigger(tableName, triggerName) {
return `DROP TRIGGER ${triggerName} ON ${tableName} RESTRICT;`;
},
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
});
renameTrigger(tableName, oldTriggerName, newTriggerName) {
return `ALTER TRIGGER ${oldTriggerName} ON ${tableName} RENAME TO ${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)
});
createFunction(functionName, params, returnType, language, body, options) {
const paramList = this.expandFunctionParamList(params);
const indentedBody = body.replace('\n', '\n\t');
const expandedOptions = this.expandOptions(options);
return `CREATE FUNCTION ${functionName}(${paramList})\n`
+ `RETURNS ${returnType} AS $func$\n`
+ 'BEGIN\n'
+ `\t${indentedBody}\n`
+ 'END;\n'
+ `$func$ language '${language}'${expandedOptions};`;
},
dropFunction: function(functionName, params) {
dropFunction(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)
});
const paramList = this.expandFunctionParamList(params);
return `DROP FUNCTION${functionName}(${paramList}) RESTRICT;`;
},
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
});
renameFunction(oldFunctionName, params, newFunctionName) {
const paramList = this.expandFunctionParamList(params);
return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${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
});
databaseConnectionUri(config) {
let uri = config.protocol + '://' + config.user + ':' + config.password + '@' + config.host;
if (config.port) {
uri += ':' + config.port;
}
uri += '/' + config.database;
if (config.ssl) {
uri += '?ssl=' + config.ssl;
}
return uri;
},
pgEscapeAndQuote: function(val) {
pgEscapeAndQuote(val) {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"));
},
expandFunctionParamList: function expandFunctionParamList(params) {
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 = [];
const paramList = Utils._.each(params, curParam => {
const paramDef = [];
if (Utils._.has(curParam, 'type')) {
if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam.direction); }
if (Utils._.has(curParam, 'name')) { paramDef.push(curParam.name); }
......@@ -672,13 +583,13 @@ var QueryGenerator = {
return paramList.join(', ');
},
expandOptions: function expandOptions(options) {
expandOptions(options) {
return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
'' : '\n\t' + options.join('\n\t');
},
decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) {
var EVENT_DECODER = {
decodeTriggerEventType(eventSpecifier) {
const EVENT_DECODER = {
'after': 'AFTER',
'before': 'BEFORE',
'instead_of': 'INSTEAD OF',
......@@ -692,17 +603,17 @@ var QueryGenerator = {
return EVENT_DECODER[eventSpecifier];
},
triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) {
triggerEventTypeIsConstraint(eventSpecifier) {
return eventSpecifier === 'after_constraint' ? 'CONSTRAINT ' : '';
},
expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) {
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 = {
return Utils._.map(fireOnSpec, (fireValue, fireKey) => {
const EVENT_MAP = {
'insert': 'INSERT',
'update': 'UPDATE',
'delete': 'DELETE',
......@@ -713,7 +624,7 @@ var QueryGenerator = {
throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey);
}
var eventSpec = EVENT_MAP[fireKey];
let eventSpec = EVENT_MAP[fireKey];
if (eventSpec === 'UPDATE') {
if (Utils._.isArray(fireValue) && fireValue.length > 0) {
eventSpec += ' OF ' + fireValue.join(', ');
......@@ -724,10 +635,10 @@ var QueryGenerator = {
}).join(' OR ');
},
pgEnumName: function (tableName, attr, options) {
pgEnumName(tableName, attr, options) {
options = options || {};
var tableDetails = this.extractTableDetails(tableName, options)
, enumName = '"enum_' + tableDetails.tableName + '_' + attr + '"';
const tableDetails = this.extractTableDetails(tableName, options);
let enumName = '"enum_' + tableDetails.tableName + '_' + attr + '"';
// pgListEnums requires the enum name only, without the schema
if (options.schema !== false && tableDetails.schema) {
......@@ -738,25 +649,23 @@ var QueryGenerator = {
},
pgListEnums: function(tableName, attrName, options) {
var enumName = ''
, tableDetails = this.extractTableDetails(tableName, options);
pgListEnums(tableName, attrName, options) {
let enumName = '';
const tableDetails = this.extractTableDetails(tableName, options);
if (tableDetails.tableName && attrName) {
enumName = ' AND t.typname=' + this.pgEnumName(tableDetails.tableName, attrName, { schema: false }).replace(/"/g, "'");
}
var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value FROM pg_type t ' +
return 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) 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 = '" + tableDetails.schema + "'" + enumName + ' GROUP BY 1';
return query;
`WHERE n.nspname = '${tableDetails.schema}'${enumName} GROUP BY 1`;
},
pgEnum: function(tableName, attr, dataType, options) {
var enumName = this.pgEnumName(tableName, attr, options)
, values;
pgEnum(tableName, attr, dataType, options) {
const enumName = this.pgEnumName(tableName, attr, options);
let values;
if (dataType.values) {
values = "ENUM('" + dataType.values.join("', '") + "')";
......@@ -764,16 +673,16 @@ var QueryGenerator = {
values = dataType.toString().match(/^ENUM\(.+\)/)[0];
}
var sql = 'CREATE TYPE ' + enumName + ' AS ' + values + ';';
let 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.pgEnumName(tableName, attr)
, sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ';
pgEnumAdd(tableName, attr, value, options) {
const enumName = this.pgEnumName(tableName, attr);
let sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ';
if (semver.gte(this.sequelize.options.databaseVersion, '9.3.0')) {
sql += 'IF NOT EXISTS ';
......@@ -789,31 +698,29 @@ var QueryGenerator = {
return sql;
},
pgEnumDrop: function(tableName, attr, enumName) {
pgEnumDrop(tableName, attr, enumName) {
enumName = enumName || this.pgEnumName(tableName, attr);
return 'DROP TYPE IF EXISTS ' + enumName + '; ';
},
fromArray: function(text) {
fromArray(text) {
text = text.replace(/^{/, '').replace(/}$/, '');
var matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig);
let matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig);
if (matches.length < 1) {
return [];
}
matches = matches.map(function(m) {
return m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '');
});
matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, ''));
return matches.slice(0, -1);
},
padInt: function(i) {
padInt(i) {
return (i < 10) ? '0' + i.toString() : i.toString();
},
dataTypeMapping: function(tableName, attr, dataType) {
dataTypeMapping(tableName, attr, dataType) {
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
dataType = dataType.replace(/PRIMARY KEY/, '');
}
......@@ -835,7 +742,7 @@ var QueryGenerator = {
return dataType;
},
quoteIdentifier: function(identifier, force) {
quoteIdentifier(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,
......@@ -857,9 +764,9 @@ var QueryGenerator = {
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
getForeignKeysQuery(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;";
`WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '${tableName}' LIMIT 1) AND r.contype = 'f' ORDER BY 1;`;
},
/**
......@@ -869,12 +776,12 @@ var QueryGenerator = {
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
dropForeignKeyQuery(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';';
},
setAutocommitQuery: function(value, options) {
setAutocommitQuery(value, options) {
if (options.parent) {
return;
}
......
'use strict';
var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query')
, QueryTypes = require('../../query-types')
, Promise = require('../../promise')
, sequelizeErrors = require('../../errors.js')
, _ = require('lodash');
var Query = function(client, sequelize, options) {
this.client = client;
this.sequelize = sequelize;
this.instance = options.instance;
this.model = options.model;
this.options = _.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
/**
* rewrite query with parameters
*/
Query.formatBindParameters = function(sql, values, dialect) {
var bindParam = [];
if (Array.isArray(values)) {
bindParam = values;
sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0];
} else {
var i = 0;
var seen = {};
var replacementFunc = function(match, key, values, timeZone, dialect, options) {
if (seen[key] !== undefined) {
return seen[key];
}
if (values[key] !== undefined) {
i = i + 1;
bindParam.push(values[key]);
seen[key] = '$'+i;
return '$'+i;
}
return undefined;
};
sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
const Utils = require('../../utils');
const AbstractQuery = require('../abstract/query');
const QueryTypes = require('../../query-types');
const Promise = require('../../promise');
const sequelizeErrors = require('../../errors.js');
const _ = require('lodash');
class Query extends AbstractQuery {
constructor(client, sequelize, options) {
super();
this.client = client;
this.sequelize = sequelize;
this.instance = options.instance;
this.model = options.model;
this.options = _.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
}
return [sql, bindParam];
};
Query.prototype.run = function(sql, parameters) {
this.sql = sql;
if(!Utils._.isEmpty(this.options.searchPath)){
this.sql = this.sequelize.queryInterface.QueryGenerator.setSearchPath(this.options.searchPath) + sql;
/**
* rewrite query with parameters
*/
static formatBindParameters(sql, values, dialect) {
let bindParam = [];
if (Array.isArray(values)) {
bindParam = values;
sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0];
} else {
let i = 0;
const seen = {};
const replacementFunc = (match, key, values, timeZone, dialect, options) => {
if (seen[key] !== undefined) {
return seen[key];
}
if (values[key] !== undefined) {
i = i + 1;
bindParam.push(values[key]);
seen[key] = '$'+i;
return '$'+i;
}
return undefined;
};
sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
}
return [sql, bindParam];
}
var self = this
, receivedError = false
, query = ((parameters && parameters.length) ? this.client.query(this.sql, parameters) : this.client.query(this.sql))
, rows = [];
run(sql, parameters) {
this.sql = sql;
//do we need benchmark for this query execution
var benchmark = this.sequelize.options.benchmark || this.options.benchmark;
if(!Utils._.isEmpty(this.options.searchPath)){
this.sql = this.sequelize.queryInterface.QueryGenerator.setSearchPath(this.options.searchPath) + sql;
}
if (benchmark) {
var queryBegin = Date.now();
} else {
this.sequelize.log('Executing (' + (this.client.uuid || 'default') + '): ' + this.sql, this.options);
}
const query = ((parameters && parameters.length) ? this.client.query(this.sql, parameters) : this.client.query(this.sql));
const rows = [];
let receivedError = false;
var promise = new Promise(function(resolve, reject) {
query.on('row', function(row) {
rows.push(row);
});
//do we need benchmark for this query execution
const benchmark = this.sequelize.options.benchmark || this.options.benchmark;
query.on('error', function(err) {
let queryBegin;
if (benchmark) {
queryBegin = Date.now();
} else {
this.sequelize.log('Executing (' + (this.client.uuid || 'default') + '): ' + this.sql, this.options);
}
// set the client so that it will be reaped if the connection resets while executing
if(err.code === 'ECONNRESET') {
self.client._invalid = true;
}
return new Promise((resolve, reject) => {
query.on('row', row => {
rows.push(row);
});
receivedError = true;
err.sql = sql;
reject(self.formatError(err));
});
query.on('error', err => {
query.on('end', function(result) {
// set the client so that it will be reaped if the connection resets while executing
if (err.code === 'ECONNRESET') {
this.client._invalid = true;
}
if (benchmark) {
self.sequelize.log('Executed (' + (self.client.uuid || 'default') + '): ' + self.sql, (Date.now() - queryBegin), self.options);
}
receivedError = true;
err.sql = sql;
reject(this.formatError(err));
});
if (receivedError) {
return;
}
query.on('end', result => {
resolve([rows, sql, result]);
});
}).spread(function(rows, sql, result) {
var results = rows
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0);
if (isRelNameQuery) {
return rows.map(function(row) {
return {
if (benchmark) {
this.sequelize.log('Executed (' + (this.client.uuid || 'default') + '): ' + this.sql, (Date.now() - queryBegin), this.options);
}
if (receivedError) {
return;
}
resolve([rows, sql, result]);
});
}).spread((rows, sql, result) => {
const results = rows;
const isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0);
const isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0);
if (isRelNameQuery) {
return rows.map(row => ({
name: row.relname,
tableName: row.relname.split('_')[0]
};
});
} else if (isTableNameQuery) {
return rows.map(function(row) { return _.values(row); });
}
}));
} else if (isTableNameQuery) {
return rows.map(row => _.values(row));
}
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
if (rows[0].sequelize_caught_exception !== null) {
var err = self.formatError({
code: '23505',
detail: rows[0].sequelize_caught_exception
});
throw err;
} else {
rows = rows.map(function (row) {
delete row.sequelize_caught_exception;
return row;
});
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
if (rows[0].sequelize_caught_exception !== null) {
throw this.formatError({
code: '23505',
detail: rows[0].sequelize_caught_exception
});
} else {
rows = rows.map(row => {
delete row.sequelize_caught_exception;
return row;
});
}
}
}
if (self.isShowIndexesQuery()) {
results.forEach(function (result) {
var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
, field
, attribute
, columns;
// Map column index in table to column name
columns = _.zipObject(
result.column_indexes,
self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
);
delete result.column_indexes;
delete result.column_names;
// Indkey is the order of attributes in the index, specified by a string of attribute indexes
result.fields = result.indkey.split(' ').map(function (indKey, index) {
field = columns[indKey];
// for functional indices indKey = 0
if(!field) {
return null;
}
attribute = attributes[index];
return {
attribute: field,
collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
order: attribute.indexOf('DESC') !== -1 ? 'DESC' : attribute.indexOf('ASC') !== -1 ? 'ASC': undefined,
length: undefined
};
}).filter(function(n){ return n !== null; });
delete result.columns;
});
return results;
} else if (self.isForeignKeysQuery()) {
result = [];
rows.forEach(function(row) {
var defParts;
if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) {
row.id = row.constraint_name;
row.table = defParts[2];
row.from = defParts[1];
row.to = defParts[3];
var i;
for (i=5;i<=8;i+=3) {
if (/(UPDATE|DELETE)/.test(defParts[i])) {
row['on_'+defParts[i].toLowerCase()] = defParts[i+1];
if (this.isShowIndexesQuery()) {
for (const result of results) {
const attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',');
// Map column index in table to column name
const columns = _.zipObject(
result.column_indexes,
this.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
);
delete result.column_indexes;
delete result.column_names;
let field;
let attribute;
// Indkey is the order of attributes in the index, specified by a string of attribute indexes
result.fields = result.indkey.split(' ').map((indKey, index) => {
field = columns[indKey];
// for functional indices indKey = 0
if(!field) {
return null;
}
attribute = attributes[index];
return {
attribute: field,
collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
order: attribute.indexOf('DESC') !== -1 ? 'DESC' : attribute.indexOf('ASC') !== -1 ? 'ASC': undefined,
length: undefined
};
}).filter(n => n !== null);
delete result.columns;
}
return results;
} else if (this.isForeignKeysQuery()) {
result = [];
for (const row of rows) {
let defParts;
if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) {
row.id = row.constraint_name;
row.table = defParts[2];
row.from = defParts[1];
row.to = defParts[3];
let i;
for (i=5;i<=8;i+=3) {
if (/(UPDATE|DELETE)/.test(defParts[i])) {
row['on_'+defParts[i].toLowerCase()] = defParts[i+1];
}
}
}
result.push(row);
}
result.push(row);
});
return result;
} else if (self.isSelectQuery()) {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = _.reduce(self.model.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
rows.forEach(function(row) {
_.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key];
if (typeof targetAttr === 'string' && targetAttr !== key) {
row[targetAttr] = row[key];
delete row[key];
return result;
} else if (this.isSelectQuery()) {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
const attrsMap = _.reduce(this.model.attributes, (m, v, k) => {
m[k.toLowerCase()] = k;
return m;
}, {});
for (const row of rows) {
for (const key of Object.keys(row)) {
const targetAttr = attrsMap[key];
if (typeof targetAttr === 'string' && targetAttr !== key) {
row[targetAttr] = row[key];
delete row[key];
}
}
});
});
}
return self.handleSelectQuery(rows);
} else if (QueryTypes.DESCRIBE === self.options.type) {
result = {};
}
}
rows.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : []),
primaryKey: _result.Constraint === 'PRIMARY KEY'
};
return this.handleSelectQuery(rows);
} else if (QueryTypes.DESCRIBE === this.options.type) {
result = {};
for (const _result of rows) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default,
special: (!!_result.special ? this.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : []),
primaryKey: _result.Constraint === 'PRIMARY KEY'
};
if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];
if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];
if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null;
if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null;
}
}
}
if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');
if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');
if (result[_result.Field].defaultValue.indexOf('::') > -1) {
var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') {
result[_result.Field].defaultValue = split[0];
if (result[_result.Field].defaultValue.indexOf('::') > -1) {
const split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') {
result[_result.Field].defaultValue = split[0];
}
}
}
}
});
return result;
} else if (self.isVersionQuery()) {
return results[0].server_version;
} else if (self.isShowOrDescribeQuery()) {
return results;
} else if (QueryTypes.BULKUPDATE === self.options.type) {
if (!self.options.returning) {
return result;
} else if (this.isVersionQuery()) {
return results[0].server_version;
} else if (this.isShowOrDescribeQuery()) {
return results;
} else if (QueryTypes.BULKUPDATE === this.options.type) {
if (!this.options.returning) {
return parseInt(result.rowCount, 10);
}
return this.handleSelectQuery(rows);
} else if (QueryTypes.BULKDELETE === this.options.type) {
return parseInt(result.rowCount, 10);
}
} else if (this.isUpsertQuery()) {
return rows[0].sequelize_upsert;
} else if (this.isInsertQuery() || this.isUpdateQuery()) {
if (this.instance && this.instance.dataValues) {
for (const key in rows[0]) {
if (rows[0].hasOwnProperty(key)) {
const record = rows[0][key];
return self.handleSelectQuery(rows);
} else if (QueryTypes.BULKDELETE === self.options.type) {
return parseInt(result.rowCount, 10);
} else if (self.isUpsertQuery()) {
return rows[0].sequelize_upsert;
} else if (self.isInsertQuery() || self.isUpdateQuery()) {
if (self.instance && self.instance.dataValues) {
for (var key in rows[0]) {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key];
var attr = _.find(self.model.rawAttributes, function (attribute) {
return attribute.fieldName === key || attribute.field === key;
});
const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key);
self.instance.dataValues[attr && attr.fieldName || key] = record;
this.instance.dataValues[attr && attr.fieldName || key] = record;
}
}
}
return this.instance || (rows && ((this.options.plain && rows[0]) || rows)) || undefined;
} else if (this.isRawQuery()) {
return [rows, result];
} else {
return results;
}
});
}
return self.instance || (rows && ((self.options.plain && rows[0]) || rows)) || undefined;
} else if (self.isRawQuery()) {
return [rows, result];
} else {
return results;
}
});
return promise;
};
Query.prototype.formatError = function (err) {
var match
, table
, index
, fields
, errors
, message
, self = this;
var code = err.code || err.sqlState
, errMessage = err.message || err.messagePrimary
, errDetail = err.detail || err.messageDetail;
switch (code) {
case '23503':
index = errMessage.match(/violates foreign key constraint \"(.+?)\"/);
index = index ? index[1] : undefined;
table = errMessage.match(/on table \"(.+?)\"/);
table = table ? table[1] : undefined;
return new sequelizeErrors.ForeignKeyConstraintError({
message: errMessage,
fields: null,
index: index,
table: table,
parent: err
});
case '23505':
// there are multiple different formats of error messages for this error code
// this regex should check at least two
if (errDetail && (match = errDetail.replace(/"/g, '').match(/Key \((.*?)\)=\((.*?)\)/))) {
fields = _.zipObject(match[1].split(', '), match[2].split(', '));
errors = [];
message = 'Validation error';
_.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
self.getUniqueConstraintErrorMessage(field),
'unique violation', field, value));
});
formatError(err) {
let match;
let table;
let index;
let fields;
let errors;
let message;
const code = err.code || err.sqlState;
const errMessage = err.message || err.messagePrimary;
const errDetail = err.detail || err.messageDetail;
switch (code) {
case '23503':
index = errMessage.match(/violates foreign key constraint \"(.+?)\"/);
index = index ? index[1] : undefined;
table = errMessage.match(/on table \"(.+?)\"/);
table = table ? table[1] : undefined;
return new sequelizeErrors.ForeignKeyConstraintError({message: errMessage, fields: null, index, table, parent: err});
case '23505':
// there are multiple different formats of error messages for this error code
// this regex should check at least two
if (errDetail && (match = errDetail.replace(/"/g, '').match(/Key \((.*?)\)=\((.*?)\)/))) {
fields = _.zipObject(match[1].split(', '), match[2].split(', '));
errors = [];
message = 'Validation error';
_.forOwn(fields, (value, field) => {
errors.push(new sequelizeErrors.ValidationErrorItem(
this.getUniqueConstraintErrorMessage(field),
'unique violation', field, value));
});
if (this.model && this.model.uniqueKeys) {
_.forOwn(this.model.uniqueKeys, function(constraint) {
if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
message = constraint.msg;
return false;
}
if (this.model && this.model.uniqueKeys) {
_.forOwn(this.model.uniqueKeys, constraint => {
if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
}
return new sequelizeErrors.UniqueConstraintError({message, errors, parent: err, fields});
} else {
return new sequelizeErrors.UniqueConstraintError({
message: errMessage,
parent: err
});
}
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
} else {
return new sequelizeErrors.UniqueConstraintError({
message: errMessage,
break;
case '23P01':
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) {
fields = _.zipObject(match[1].split(', '), match[2].split(', '));
}
message = 'Exclusion constraint error';
return new sequelizeErrors.ExclusionConstraintError({
message,
constraint: err.constraint,
fields,
table: err.table,
parent: err
});
}
break;
case '23P01':
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) {
fields = _.zipObject(match[1].split(', '), match[2].split(', '));
}
message = 'Exclusion constraint error';
return new sequelizeErrors.ExclusionConstraintError({
message: message,
constraint: err.constraint,
fields: fields,
table: err.table,
parent: err
});
default:
return new sequelizeErrors.DatabaseError(err);
}
}
default:
return new sequelizeErrors.DatabaseError(err);
isForeignKeysQuery() {
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 = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql);
}
};
Query.prototype.isForeignKeysQuery = function() {
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 = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql);
};
getInsertIdField() {
return 'id';
}
}
Query.prototype.getInsertIdField = function() {
return 'id';
};
module.exports = Query;
module.exports.Query = Query;
module.exports.default = Query;
'use strict';
var _ = require('lodash');
const _ = require('lodash');
function stringifyRangeBound (bound) {
if (bound === null) {
......@@ -39,44 +39,38 @@ function stringify (data) {
data.inclusive = [true, false];
}
_.each(data, function (value, index) {
_.each(data, (value, index) => {
if (_.isObject(value)) {
if (value.hasOwnProperty('inclusive')) data.inclusive[index] = !!value.inclusive;
if (value.hasOwnProperty('value')) data[index] = value.value;
}
});
var lowerBound = stringifyRangeBound(data[0]);
var upperBound = stringifyRangeBound(data[1]);
const lowerBound = stringifyRangeBound(data[0]);
const upperBound = stringifyRangeBound(data[1]);
return (data.inclusive[0] ? '[' : '(') + lowerBound + ',' + upperBound + (data.inclusive[1] ? ']' : ')');
}
exports.stringify = stringify;
function parse (value, parser) {
if (value === null) return null;
if (value === 'empty') {
var empty = [];
const empty = [];
empty.inclusive = [];
return empty;
}
var result = value
let result = value
.substring(1, value.length - 1)
.split(',', 2);
if (result.length !== 2) return value;
result = result
.map(function (value) {
return parseRangeBound(value, parser);
});
result = result.map(value => parseRangeBound(value, parser));
result.inclusive = [(value[0] === '['), (value[value.length - 1] === ']')];
return result;
}
module.exports = {
stringify: stringify,
parse: parse
};
exports.parse = parse;
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!