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

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