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

Commit 722ed505 by Andy Edwards Committed by GitHub

refactor(dialects/postgres): asyncify methods (#12129)

1 parent ceb0de26
...@@ -84,7 +84,7 @@ class ConnectionManager extends AbstractConnectionManager { ...@@ -84,7 +84,7 @@ class ConnectionManager extends AbstractConnectionManager {
return this.lib.types.getTypeParser(oid, ...args); return this.lib.types.getTypeParser(oid, ...args);
} }
connect(config) { async connect(config) {
config.user = config.username; config.user = config.username;
const connectionConfig = _.pick(config, [ const connectionConfig = _.pick(config, [
'user', 'password', 'host', 'database', 'port' 'user', 'password', 'host', 'database', 'port'
...@@ -121,7 +121,7 @@ class ConnectionManager extends AbstractConnectionManager { ...@@ -121,7 +121,7 @@ class ConnectionManager extends AbstractConnectionManager {
])); ]));
} }
return new Promise((resolve, reject) => { const connection = await new Promise((resolve, reject) => {
let responded = false; let responded = false;
const connection = new this.lib.Client(connectionConfig); const connection = new this.lib.Client(connectionConfig);
...@@ -194,76 +194,71 @@ class ConnectionManager extends AbstractConnectionManager { ...@@ -194,76 +194,71 @@ class ConnectionManager extends AbstractConnectionManager {
resolve(connection); resolve(connection);
} }
}); });
}).then(connection => { });
let query = '';
if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') {
// Disable escape characters in strings
// see https://github.com/sequelize/sequelize/issues/3545 (security issue)
// see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS
query += 'SET standard_conforming_strings=on;';
}
if (this.sequelize.options.clientMinMessages !== false) { let query = '';
query += `SET client_min_messages TO ${this.sequelize.options.clientMinMessages};`;
}
if (!this.sequelize.config.keepDefaultTimezone) { if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') {
const isZone = !!moment.tz.zone(this.sequelize.options.timezone); // Disable escape characters in strings
if (isZone) { // see https://github.com/sequelize/sequelize/issues/3545 (security issue)
query += `SET TIME ZONE '${this.sequelize.options.timezone}';`; // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS
} else { query += 'SET standard_conforming_strings=on;';
query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`; }
}
}
if (query) { if (this.sequelize.options.clientMinMessages !== false) {
return Promise.resolve(connection.query(query)).then(() => connection); query += `SET client_min_messages TO ${this.sequelize.options.clientMinMessages};`;
} }
return connection;
}).then(connection => { if (!this.sequelize.config.keepDefaultTimezone) {
if (Object.keys(this.nameOidMap).length === 0 && const isZone = !!moment.tz.zone(this.sequelize.options.timezone);
this.enumOids.oids.length === 0 && if (isZone) {
this.enumOids.arrayOids.length === 0) { query += `SET TIME ZONE '${this.sequelize.options.timezone}';`;
return Promise.resolve(this._refreshDynamicOIDs(connection)).then(() => connection); } else {
query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`;
} }
return connection; }
}).then(connection => {
// Don't let a Postgres restart (or error) to take down the whole app if (query) {
connection.on('error', error => { await connection.query(query);
connection._invalid = true; }
debug(`connection error ${error.code || error.message}`); if (Object.keys(this.nameOidMap).length === 0 &&
this.pool.destroy(connection); this.enumOids.oids.length === 0 &&
}); this.enumOids.arrayOids.length === 0) {
return connection; await this._refreshDynamicOIDs(connection);
}
// Don't let a Postgres restart (or error) to take down the whole app
connection.on('error', error => {
connection._invalid = true;
debug(`connection error ${error.code || error.message}`);
this.pool.destroy(connection);
}); });
return connection;
} }
disconnect(connection) { async disconnect(connection) {
if (connection._ending) { if (connection._ending) {
debug('connection tried to disconnect but was already at ENDING state'); debug('connection tried to disconnect but was already at ENDING state');
return Promise.resolve(); return;
} }
return promisify(callback => connection.end(callback))(); return await promisify(callback => connection.end(callback))();
} }
validate(connection) { validate(connection) {
return !connection._invalid && !connection._ending; return !connection._invalid && !connection._ending;
} }
_refreshDynamicOIDs(connection) { async _refreshDynamicOIDs(connection) {
const databaseVersion = this.sequelize.options.databaseVersion; const databaseVersion = this.sequelize.options.databaseVersion;
const supportedVersion = '8.3.0'; const supportedVersion = '8.3.0';
// Check for supported version // Check for supported version
if ( (databaseVersion && semver.gte(databaseVersion, supportedVersion)) === false) { if ( (databaseVersion && semver.gte(databaseVersion, supportedVersion)) === false) {
return Promise.resolve(); return;
} }
// Refresh dynamic OIDs for some types const results = await (connection || this.sequelize).query(
// These include Geometry / Geography / HStore / Enum / Citext / Range
return (connection || this.sequelize).query(
'WITH ranges AS (' + 'WITH ranges AS (' +
' SELECT pg_range.rngtypid, pg_type.typname AS rngtypname,' + ' SELECT pg_range.rngtypid, pg_type.typname AS rngtypname,' +
' pg_type.typarray AS rngtyparray, pg_range.rngsubtype' + ' pg_type.typarray AS rngtyparray, pg_range.rngsubtype' +
...@@ -273,46 +268,46 @@ class ConnectionManager extends AbstractConnectionManager { ...@@ -273,46 +268,46 @@ class ConnectionManager extends AbstractConnectionManager {
' ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray' + ' ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray' +
' FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype' + ' FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype' +
' WHERE (pg_type.typtype IN(\'b\', \'e\'));' ' WHERE (pg_type.typtype IN(\'b\', \'e\'));'
).then(results => { );
let result = Array.isArray(results) ? results.pop() : results;
let result = Array.isArray(results) ? results.pop() : results;
// When searchPath is prepended then two statements are executed and the result is
// an array of those two statements. First one is the SET search_path and second is // When searchPath is prepended then two statements are executed and the result is
// the SELECT query result. // an array of those two statements. First one is the SET search_path and second is
if (Array.isArray(result)) { // the SELECT query result.
if (result[0].command === 'SET') { if (Array.isArray(result)) {
result = result.pop(); if (result[0].command === 'SET') {
} result = result.pop();
} }
}
const newNameOidMap = {}; const newNameOidMap = {};
const newEnumOids = { oids: [], arrayOids: [] }; const newEnumOids = { oids: [], arrayOids: [] };
for (const row of result.rows) { for (const row of result.rows) {
// Mapping enums, handled separatedly // Mapping enums, handled separatedly
if (row.typtype === 'e') { if (row.typtype === 'e') {
newEnumOids.oids.push(row.oid); newEnumOids.oids.push(row.oid);
if (row.typarray) newEnumOids.arrayOids.push(row.typarray); if (row.typarray) newEnumOids.arrayOids.push(row.typarray);
continue; continue;
} }
// Mapping base types and their arrays // Mapping base types and their arrays
newNameOidMap[row.typname] = { oid: row.oid }; newNameOidMap[row.typname] = { oid: row.oid };
if (row.typarray) newNameOidMap[row.typname].arrayOid = row.typarray; if (row.typarray) newNameOidMap[row.typname].arrayOid = row.typarray;
// Mapping ranges(of base types) and their arrays // Mapping ranges(of base types) and their arrays
if (row.rngtypid) { if (row.rngtypid) {
newNameOidMap[row.typname].rangeOid = row.rngtypid; newNameOidMap[row.typname].rangeOid = row.rngtypid;
if (row.rngtyparray) newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray; if (row.rngtyparray) newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray;
}
} }
}
// Replace all OID mappings. Avoids temporary empty OID mappings. // Replace all OID mappings. Avoids temporary empty OID mappings.
this.nameOidMap = newNameOidMap; this.nameOidMap = newNameOidMap;
this.enumOids = newEnumOids; this.enumOids = newEnumOids;
this.refreshTypeParser(dataTypes.postgres); this.refreshTypeParser(dataTypes.postgres);
});
} }
_clearDynamicOIDs() { _clearDynamicOIDs() {
......
...@@ -26,7 +26,7 @@ const _ = require('lodash'); ...@@ -26,7 +26,7 @@ const _ = require('lodash');
* @returns {Promise} * @returns {Promise}
* @private * @private
*/ */
function ensureEnums(qi, tableName, attributes, options, model) { async function ensureEnums(qi, tableName, attributes, options, model) {
const keys = Object.keys(attributes); const keys = Object.keys(attributes);
const keyLen = keys.length; const keyLen = keys.length;
...@@ -50,108 +50,106 @@ function ensureEnums(qi, tableName, attributes, options, model) { ...@@ -50,108 +50,106 @@ function ensureEnums(qi, tableName, attributes, options, model) {
} }
} }
return Promise.all(promises).then(results => { const results = await Promise.all(promises);
promises = []; promises = [];
let enumIdx = 0; let enumIdx = 0;
// This little function allows us to re-use the same code that prepends or appends new value to enum array // This little function allows us to re-use the same code that prepends or appends new value to enum array
const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => {
const valueOptions = _.clone(options); const valueOptions = _.clone(options);
valueOptions.before = null; valueOptions.before = null;
valueOptions.after = null; valueOptions.after = null;
switch (position) { switch (position) {
case 'after': case 'after':
valueOptions.after = relativeValue; valueOptions.after = relativeValue;
break; break;
case 'before': case 'before':
default: default:
valueOptions.before = relativeValue; valueOptions.before = relativeValue;
break; break;
} }
promises.splice(spliceStart, 0, () => { promises.splice(spliceStart, 0, () => {
return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd( return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd(
tableName, field, value, valueOptions tableName, field, value, valueOptions
), valueOptions); ), valueOptions);
}); });
}; };
for (i = 0; i < keyLen; i++) {
const attribute = attributes[keys[i]];
const type = attribute.type;
const enumType = type.type || type;
const field = attribute.field || keys[i];
if (
type instanceof DataTypes.ENUM ||
type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM
) {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
promises.push(() => {
return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true }));
});
} else if (!!results[enumIdx] && !!model) {
const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value);
const vals = enumType.values;
// Going through already existing values allows us to make queries that depend on those values
// We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values
// Then we append the rest of new values AFTER the latest already existing value
// E.g.: [1,2] -> [0,2,1] ==> [1,0,2]
// E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4]
// E.g.: [1] -> [0,2,3] ==> [1,0,2,3]
let lastOldEnumValue;
let rightestPosition = -1;
for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) {
const enumVal = enumVals[oldIndex];
const newIdx = vals.indexOf(enumVal);
lastOldEnumValue = enumVal;
if (newIdx === -1) {
continue;
}
const newValuesBefore = vals.slice(0, newIdx); for (i = 0; i < keyLen; i++) {
const promisesLength = promises.length; const attribute = attributes[keys[i]];
// we go in reverse order so we could stop when we meet old value const type = attribute.type;
for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { const enumType = type.type || type;
if (~enumVals.indexOf(newValuesBefore[reverseIdx])) { const field = attribute.field || keys[i];
break;
}
addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength); if (
} type instanceof DataTypes.ENUM ||
type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM
) {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
promises.push(() => {
return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true }));
});
} else if (!!results[enumIdx] && !!model) {
const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value);
const vals = enumType.values;
// Going through already existing values allows us to make queries that depend on those values
// We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values
// Then we append the rest of new values AFTER the latest already existing value
// E.g.: [1,2] -> [0,2,1] ==> [1,0,2]
// E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4]
// E.g.: [1] -> [0,2,3] ==> [1,0,2,3]
let lastOldEnumValue;
let rightestPosition = -1;
for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) {
const enumVal = enumVals[oldIndex];
const newIdx = vals.indexOf(enumVal);
lastOldEnumValue = enumVal;
if (newIdx === -1) {
continue;
}
// we detect the most 'right' position of old value in new enum array so we can append new values to it const newValuesBefore = vals.slice(0, newIdx);
if (newIdx > rightestPosition) { const promisesLength = promises.length;
rightestPosition = newIdx; // we go in reverse order so we could stop when we meet old value
for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) {
if (~enumVals.indexOf(newValuesBefore[reverseIdx])) {
break;
} }
addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength);
} }
if (lastOldEnumValue && rightestPosition < vals.length - 1) { // we detect the most 'right' position of old value in new enum array so we can append new values to it
const remainingEnumValues = vals.slice(rightestPosition + 1); if (newIdx > rightestPosition) {
for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { rightestPosition = newIdx;
addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after');
}
} }
}
enumIdx++; if (lastOldEnumValue && rightestPosition < vals.length - 1) {
const remainingEnumValues = vals.slice(rightestPosition + 1);
for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) {
addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after');
}
} }
enumIdx++;
} }
} }
}
return promises const result = await promises
.reduce((promise, asyncFunction) => promise.then(asyncFunction), Promise.resolve()) .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve());
.then(result => {
// If ENUM processed, then refresh OIDs // If ENUM processed, then refresh OIDs
if (promises.length) { if (promises.length) {
return Promise.resolve(qi.sequelize.dialect.connectionManager._refreshDynamicOIDs()).then(() => result); await qi.sequelize.dialect.connectionManager._refreshDynamicOIDs();
} }
return result; return result;
});
});
} }
......
...@@ -47,7 +47,7 @@ class Query extends AbstractQuery { ...@@ -47,7 +47,7 @@ class Query extends AbstractQuery {
return [sql, bindParam]; return [sql, bindParam];
} }
run(sql, parameters) { async run(sql, parameters) {
const { connection } = this; const { connection } = this;
if (!_.isEmpty(this.options.searchPath)) { if (!_.isEmpty(this.options.searchPath)) {
...@@ -73,7 +73,11 @@ class Query extends AbstractQuery { ...@@ -73,7 +73,11 @@ class Query extends AbstractQuery {
const complete = this._logQuery(sql, debug, parameters); const complete = this._logQuery(sql, debug, parameters);
return query.catch(err => { let queryResult;
try {
queryResult = await query;
} catch (err) {
// set the client so that it will be reaped if the connection resets while executing // set the client so that it will be reaped if the connection resets while executing
if (err.code === 'ECONNRESET') { if (err.code === 'ECONNRESET') {
connection._invalid = true; connection._invalid = true;
...@@ -82,207 +86,206 @@ class Query extends AbstractQuery { ...@@ -82,207 +86,206 @@ class Query extends AbstractQuery {
err.sql = sql; err.sql = sql;
err.parameters = parameters; err.parameters = parameters;
throw this.formatError(err); throw this.formatError(err);
}) }
.then(queryResult => {
complete();
let rows = Array.isArray(queryResult)
? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), [])
: queryResult.rows;
const rowCount = Array.isArray(queryResult)
? queryResult.reduce(
(count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count,
0
)
: queryResult.rowCount;
if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) {
rows = rows
.map(row => _.toPairs(row)
.reduce((acc, [key, value]) => {
const mapping = this.options.aliasesMapping.get(key);
acc[mapping || key] = value;
return acc;
}, {})
);
}
const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); complete();
const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN');
let rows = Array.isArray(queryResult)
? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), [])
: queryResult.rows;
const rowCount = Array.isArray(queryResult)
? queryResult.reduce(
(count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count,
0
)
: queryResult.rowCount;
if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) {
rows = rows
.map(row => _.toPairs(row)
.reduce((acc, [key, value]) => {
const mapping = this.options.aliasesMapping.get(key);
acc[mapping || key] = value;
return acc;
}, {})
);
}
if (isRelNameQuery) { const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables');
return rows.map(row => ({ const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN');
name: row.relname,
tableName: row.relname.split('_')[0]
}));
}
if (isTableNameQuery) {
return rows.map(row => _.values(row));
}
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { if (isRelNameQuery) {
if (rows[0].sequelize_caught_exception !== null) { return rows.map(row => ({
throw this.formatError({ name: row.relname,
code: '23505', tableName: row.relname.split('_')[0]
detail: rows[0].sequelize_caught_exception }));
}); }
} if (isTableNameQuery) {
for (const row of rows) { return rows.map(row => _.values(row));
delete row.sequelize_caught_exception; }
}
}
if (this.isShowIndexesQuery()) { if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
for (const row of rows) { if (rows[0].sequelize_caught_exception !== null) {
const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(','); throw this.formatError({
code: '23505',
// Map column index in table to column name detail: rows[0].sequelize_caught_exception
const columns = _.zipObject( });
row.column_indexes, }
this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.column_names) for (const row of rows) {
); delete row.sequelize_caught_exception;
delete row.column_indexes; }
delete row.column_names; }
let field; if (this.isShowIndexesQuery()) {
let attribute; for (const row of rows) {
const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(',');
// Indkey is the order of attributes in the index, specified by a string of attribute indexes
row.fields = row.indkey.split(' ').map((indKey, index) => { // Map column index in table to column name
field = columns[indKey]; const columns = _.zipObject(
// for functional indices indKey = 0 row.column_indexes,
if (!field) { this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.column_names)
return null; );
} delete row.column_indexes;
attribute = attributes[index]; delete row.column_names;
return {
attribute: field, let field;
collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined, let attribute;
order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined,
length: undefined // Indkey is the order of attributes in the index, specified by a string of attribute indexes
}; row.fields = row.indkey.split(' ').map((indKey, index) => {
}).filter(n => n !== null); field = columns[indKey];
delete row.columns; // for functional indices indKey = 0
if (!field) {
return null;
} }
return rows; attribute = attributes[index];
} return {
if (this.isForeignKeysQuery()) { attribute: field,
const result = []; collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
for (const row of rows) { order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined,
let defParts; length: undefined
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; }).filter(n => n !== null);
row.table = defParts[2]; delete row.columns;
row.from = defParts[1]; }
row.to = defParts[3]; return rows;
let i; }
for (i = 5; i <= 8; i += 3) { if (this.isForeignKeysQuery()) {
if (/(UPDATE|DELETE)/.test(defParts[i])) { const result = [];
row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1]; 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);
} }
return result;
} }
if (this.isSelectQuery()) { result.push(row);
let result = rows; }
// Postgres will treat tables as case-insensitive, so fix the case return result;
// of the returned values to match attributes }
if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { if (this.isSelectQuery()) {
const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => { let result = rows;
m[k.toLowerCase()] = k; // Postgres will treat tables as case-insensitive, so fix the case
return m; // of the returned values to match attributes
}, {}); if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
result = rows.map(row => { const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => {
return _.mapKeys(row, (value, key) => { m[k.toLowerCase()] = k;
const targetAttr = attrsMap[key]; return m;
if (typeof targetAttr === 'string' && targetAttr !== key) { }, {});
return targetAttr; result = rows.map(row => {
} return _.mapKeys(row, (value, key) => {
return key; const targetAttr = attrsMap[key];
}); if (typeof targetAttr === 'string' && targetAttr !== key) {
}); return targetAttr;
}
return key;
});
});
}
return this.handleSelectQuery(result);
}
if (QueryTypes.DESCRIBE === this.options.type) {
const result = {};
for (const row of rows) {
result[row.Field] = {
type: row.Type.toUpperCase(),
allowNull: row.Null === 'YES',
defaultValue: row.Default,
comment: row.Comment,
special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [],
primaryKey: row.Constraint === 'PRIMARY KEY'
};
if (result[row.Field].type === 'BOOLEAN') {
result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue];
if (result[row.Field].defaultValue === undefined) {
result[row.Field].defaultValue = null;
} }
return this.handleSelectQuery(result);
} }
if (QueryTypes.DESCRIBE === this.options.type) {
const result = {};
for (const row of rows) {
result[row.Field] = {
type: row.Type.toUpperCase(),
allowNull: row.Null === 'YES',
defaultValue: row.Default,
comment: row.Comment,
special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [],
primaryKey: row.Constraint === 'PRIMARY KEY'
};
if (result[row.Field].type === 'BOOLEAN') {
result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue];
if (result[row.Field].defaultValue === undefined) {
result[row.Field].defaultValue = null;
}
}
if (typeof result[row.Field].defaultValue === 'string') { if (typeof result[row.Field].defaultValue === 'string') {
result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, ''); result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, '');
if (result[row.Field].defaultValue.includes('::')) { if (result[row.Field].defaultValue.includes('::')) {
const split = result[row.Field].defaultValue.split('::'); const split = result[row.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') { if (split[1].toLowerCase() !== 'regclass)') {
result[row.Field].defaultValue = split[0]; result[row.Field].defaultValue = split[0];
}
}
} }
} }
return result;
}
if (this.isVersionQuery()) {
return rows[0].server_version;
}
if (this.isShowOrDescribeQuery()) {
return rows;
}
if (QueryTypes.BULKUPDATE === this.options.type) {
if (!this.options.returning) {
return parseInt(rowCount, 10);
}
return this.handleSelectQuery(rows);
}
if (QueryTypes.BULKDELETE === this.options.type) {
return parseInt(rowCount, 10);
}
if (this.isUpsertQuery()) {
return rows[0];
} }
if (this.isInsertQuery() || this.isUpdateQuery()) { }
if (this.instance && this.instance.dataValues) {
for (const key in rows[0]) { return result;
if (Object.prototype.hasOwnProperty.call(rows[0], key)) { }
const record = rows[0][key]; if (this.isVersionQuery()) {
return rows[0].server_version;
}
if (this.isShowOrDescribeQuery()) {
return rows;
}
if (QueryTypes.BULKUPDATE === this.options.type) {
if (!this.options.returning) {
return parseInt(rowCount, 10);
}
return this.handleSelectQuery(rows);
}
if (QueryTypes.BULKDELETE === this.options.type) {
return parseInt(rowCount, 10);
}
if (this.isUpsertQuery()) {
return rows[0];
}
if (this.isInsertQuery() || this.isUpdateQuery()) {
if (this.instance && this.instance.dataValues) {
for (const key in rows[0]) {
if (Object.prototype.hasOwnProperty.call(rows[0], key)) {
const record = rows[0][key];
const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key);
this.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,
rowCount
];
} }
if (this.isRawQuery()) { }
return [rows, queryResult];
} return [
return rows; this.instance || rows && (this.options.plain && rows[0] || rows) || undefined,
}); rowCount
];
}
if (this.isRawQuery()) {
return [rows, queryResult];
}
return rows;
} }
formatError(err) { formatError(err) {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!