Skip to content

Commit

Permalink
feat(postgres): change returning option to only return model attributes
Browse files Browse the repository at this point in the history
If an underlying table has columns that don't exist in the model,
when using `returning: true` the instance will be created with these
extra attributes. This change alters this behavior to only return the
fields defined in the model, provided a model definition is passed.

BREAKING CHANGE: setting `returning: true` will no longer create
attributes on the instance that are not defined in the model.

The old default behavior, if desired, can be used by changing the:

`returning: true`

to

`returning: ['*']`
  • Loading branch information
Americas committed Oct 9, 2019
1 parent 7a90df5 commit fd956cd
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 148 deletions.
153 changes: 78 additions & 75 deletions lib/dialects/abstract/query-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,39 +130,12 @@ class QueryGenerator {
}

if (this._dialect.supports.returnValues && options.returning) {
if (this._dialect.supports.returnValues.returning) {
valueQuery += ' RETURNING *';
emptyQuery += ' RETURNING *';
} else if (this._dialect.supports.returnValues.output) {
outputFragment = ' OUTPUT INSERTED.*';

//To capture output rows when there is a trigger on MSSQL DB
if (modelAttributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {

let tmpColumns = '';
let outputColumns = '';

for (const modelKey in modelAttributes) {
const attribute = modelAttributes[modelKey];
if (!(attribute.type instanceof DataTypes.VIRTUAL)) {
if (tmpColumns.length > 0) {
tmpColumns += ',';
outputColumns += ',';
}

tmpColumns += `${this.quoteIdentifier(attribute.field)} ${attribute.type.toSql()}`;
outputColumns += `INSERTED.${this.quoteIdentifier(attribute.field)}`;
}
}

tmpTable = `declare @tmp table (${tmpColumns});`;
outputFragment = ` OUTPUT ${outputColumns} into @tmp`;
const selectFromTmp = ';select * from @tmp';
const returnValues = this.generateReturnValues(modelAttributes, options);

valueQuery += selectFromTmp;
emptyQuery += selectFromTmp;
}
}
valueQuery += returnValues.query;
emptyQuery += returnValues.query;
tmpTable = returnValues.tmpTable || '';
outputFragment = returnValues.outputFragment || '';
}

if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {
Expand Down Expand Up @@ -316,11 +289,10 @@ class QueryGenerator {
const onConflictDoNothing = options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : '';
let returning = '';

if (this._dialect.supports.returnValues && Array.isArray(options.returning)) {
const fields = options.returning.map(field => this.quoteIdentifier(field)).join(',');
returning += ` RETURNING ${fields}`;
} else {
returning += this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : '';
if (this._dialect.supports.returnValues && options.returning) {
const returnValues = this.generateReturnValues(fieldMappedAttributes, options);

returning += returnValues.query;
}

return `INSERT${ignoreDuplicates} INTO ${this.quoteTable(tableName)} (${attributes}) VALUES ${tuples.join(',')}${onDuplicateKeyUpdate}${onConflictDoNothing}${returning};`;
Expand Down Expand Up @@ -348,7 +320,6 @@ class QueryGenerator {
const modelAttributeMap = {};
let outputFragment = '';
let tmpTable = ''; // tmpTable declaration for trigger
let selectFromTmp = ''; // Select statement for trigger
let suffix = '';

if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {
Expand All @@ -364,39 +335,16 @@ class QueryGenerator {
}
}

if (this._dialect.supports.returnValues) {
if (this._dialect.supports.returnValues.output) {
// we always need this for mssql
outputFragment = ' OUTPUT INSERTED.*';

//To capture output rows when there is a trigger on MSSQL DB
if (attributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
let tmpColumns = '';
let outputColumns = '';
if (this._dialect.supports.returnValues && (this._dialect.supports.returnValues.output || options.returning)) {
const returnValues = this.generateReturnValues(attributes, options);

for (const modelKey in attributes) {
const attribute = attributes[modelKey];
if (!(attribute.type instanceof DataTypes.VIRTUAL)) {
if (tmpColumns.length > 0) {
tmpColumns += ',';
outputColumns += ',';
}

tmpColumns += `${this.quoteIdentifier(attribute.field)} ${attribute.type.toSql()}`;
outputColumns += `INSERTED.${this.quoteIdentifier(attribute.field)}`;
}
}

tmpTable = `declare @tmp table (${tmpColumns}); `;
outputFragment = ` OUTPUT ${outputColumns} into @tmp`;
selectFromTmp = ';select * from @tmp';
suffix += returnValues.query;
tmpTable = returnValues.tmpTable || '';
outputFragment = returnValues.outputFragment || '';

suffix += selectFromTmp;
}
} else if (this._dialect.supports.returnValues && options.returning) {
// ensure that the return output is properly mapped to model fields.
// ensure that the return output is properly mapped to model fields.
if (!this._dialect.supports.returnValues.output && options.returning) {
options.mapToModel = true;
suffix += ' RETURNING *';
}
}

Expand Down Expand Up @@ -462,12 +410,10 @@ class QueryGenerator {
let returningFragment = '';

if (this._dialect.supports.returnValues && options.returning) {
if (this._dialect.supports.returnValues.returning) {
options.mapToModel = true;
returningFragment = 'RETURNING *';
} else if (this._dialect.supports.returnValues.output) {
outputFragment = ' OUTPUT INSERTED.*';
}
const returnValues = this.generateReturnValues(null, options);

outputFragment = returnValues.outputFragment;
returningFragment = returnValues.query;
}

for (const key in attrValueHash) {
Expand All @@ -481,7 +427,7 @@ class QueryGenerator {
values.push(`${this.quoteIdentifier(key)}=${this.escape(value)}`);
}

return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where)} ${returningFragment}`.trim();
return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where)}${returningFragment}`.trim();
}

/*
Expand Down Expand Up @@ -1726,6 +1672,63 @@ class QueryGenerator {
};
}

/**
* Returns the SQL fragments to handle returning the attributes from an insert/update query.
*
* @param {Object} modelAttributes An object with the model attributes.
* @param {Object} options An object with options.
*
* @private
*/
generateReturnValues(modelAttributes, options) {
const returnFields = [];
let query = '';
let outputFragment = '';
let tmpTable = '';

if (this._dialect.supports.returnValues.returning) {
if (Array.isArray(options.returning)) {
returnFields.push(...options.returning.map(field => this.quoteIdentifier(field)));
} else if (modelAttributes) {
_.each(modelAttributes, attribute => {
if (!(attribute.type instanceof DataTypes.VIRTUAL)) {
returnFields.push(this.quoteIdentifier(attribute.field));
}
});
}

if (_.isEmpty(returnFields)) {
query = ' RETURNING *';
} else {
query = ` RETURNING ${returnFields.join(',')}`;
}
} else if (this._dialect.supports.returnValues.output) {
// we always need this for mssql
outputFragment = ' OUTPUT INSERTED.*';

//To capture output rows when there is a trigger on MSSQL DB
if (modelAttributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {

const tmpColumns = [];
const outputColumns = [];

for (const modelKey in modelAttributes) {
const attribute = modelAttributes[modelKey];
if (!(attribute.type instanceof DataTypes.VIRTUAL)) {
tmpColumns.push(`${this.quoteIdentifier(attribute.field)} ${attribute.type.toSql()}`);
outputColumns.push(`INSERTED.${this.quoteIdentifier(attribute.field)}`);
}
}

tmpTable = `DECLARE @tmp TABLE (${tmpColumns.join(',')}); `;
outputFragment = ` OUTPUT ${outputColumns.join(',')} INTO @tmp`;
query = '; SELECT * FROM @tmp';
}
}

return { query, outputFragment, tmpTable };
}

generateThroughJoin(include, includeAs, parentTableName, topLevelInfo) {
const through = include.through;
const throughTable = through.model.getTableName();
Expand Down
2 changes: 1 addition & 1 deletion lib/dialects/postgres/query-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator {
upsertQuery(tableName, insertValues, updateValues, where, model, options) {
const primaryField = this.quoteIdentifier(model.primaryKeyField);

const upsertOptions = _.defaults({ bindParam: false }, options);
const upsertOptions = _.defaults({ bindParam: false, returning: ['*'] }, options);
const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions);
const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes);

Expand Down

0 comments on commit fd956cd

Please sign in to comment.