diff --git a/src/dialects/abstract/query-generator.js b/src/dialects/abstract/query-generator.js index b2f3a0c795ab..bcacdd1b3d7e 100644 --- a/src/dialects/abstract/query-generator.js +++ b/src/dialects/abstract/query-generator.js @@ -91,7 +91,7 @@ class QueryGenerator { /** * Helper method for populating the returning into bind information * that is needed by some dialects (currently Oracle) - * + * * @private */ populateInsertQueryReturnIntoBinds() { @@ -1352,8 +1352,8 @@ class QueryGenerator { } else { // Ordering is handled by the subqueries, so ordering the UNION'ed result is not needed groupedLimitOrder = options.order; - - // For the Oracle dialect, the result of a select is a set, not a sequence, and so is the result of UNION. + + // For the Oracle dialect, the result of a select is a set, not a sequence, and so is the result of UNION. // So the top level ORDER BY is required if (!this._dialect.supports.topLevelOrderByRequired) { delete options.order; @@ -1612,7 +1612,7 @@ class QueryGenerator { } else if (/json_extract\(/.test(attr)) { prefix = attr.replace(/json_extract\(/i, `json_extract(${this.quoteIdentifier(includeAs.internalAs)}.`); } else if (/json_value\(/.test(attr)) { - prefix = attr.replace(/json_value\(/i, `json_value(${this.quoteIdentifier(includeAs.internalAs)}.`); + prefix = attr.replace(/json_value\(/i, `json_value(${this.quoteIdentifier(includeAs.internalAs)}.`); } else { prefix = `${this.quoteIdentifier(includeAs.internalAs)}.${this.quoteIdentifier(attr)}`; } @@ -2135,7 +2135,21 @@ class QueryGenerator { const field = model.rawAttributes[order[0]] ? model.rawAttributes[order[0]].field : order[0]; const subQueryAlias = this._getAliasForField(this.quoteIdentifier(model.name), field, options); - subQueryOrder.push(this.quote(subQueryAlias === null ? order : subQueryAlias, model, '->')); + let parent = null; + let orderToQuote = []; + + // we need to ensure that the parent is null if we use the subquery alias, else we'll get an exception since + // "model_name"."alias" doesn't exist - only "alias" does. we also need to ensure that we preserve order direction + // by pushing order[1] to the subQueryOrder as well - in case it doesn't exist, we want to push "ASC" + if (subQueryAlias === null) { + orderToQuote = order; + parent = model; + } else { + orderToQuote = [subQueryAlias, order.length > 1 ? order[1] : 'ASC']; + parent = null; + } + + subQueryOrder.push(this.quote(orderToQuote, parent, '->')); } // Handle case where renamed attributes are used to order by, diff --git a/test/integration/dialects/postgres/query.test.js b/test/integration/dialects/postgres/query.test.js index f4888d20fecf..40e5bf8c59bc 100644 --- a/test/integration/dialects/postgres/query.test.js +++ b/test/integration/dialects/postgres/query.test.js @@ -124,22 +124,35 @@ if (dialect.match(/^postgres/)) { await Foo.create({ name: 'record1' }); await Foo.create({ name: 'record2' }); - const thisWorks = (await Foo.findAll({ + const baseTest = (await Foo.findAll({ subQuery: false, order: sequelizeMinifyAliases.literal('"Foo".my_name') })).map(f => f.name); - expect(thisWorks[0]).to.equal('record1'); + expect(baseTest[0]).to.equal('record1'); - const thisShouldAlsoWork = (await Foo.findAll({ + const orderByAscSubquery = (await Foo.findAll({ attributes: { include: [ [sequelizeMinifyAliases.literal('"Foo".my_name'), 'customAttribute'] ] }, subQuery: true, - order: ['customAttribute'] + order: [['customAttribute']], + limit: 1 })).map(f => f.name); - expect(thisShouldAlsoWork[0]).to.equal('record1'); + expect(orderByAscSubquery[0]).to.equal('record1'); + + const orderByDescSubquery = (await Foo.findAll({ + attributes: { + include: [ + [sequelizeMinifyAliases.literal('"Foo".my_name'), 'customAttribute'] + ] + }, + subQuery: true, + order: [['customAttribute', 'DESC']], + limit: 1 + })).map(f => f.name); + expect(orderByDescSubquery[0]).to.equal('record2'); }); it('returns the minified aliased attributes', async () => {