From 66d84db6aa1bb47ba4c2bcf0caca62eb8f106093 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Thu, 19 May 2022 20:25:21 -0300 Subject: [PATCH 01/12] feat(6497): basic select no inner joins --- src/dialects/mysql/query-generator.js | 37 ++++++++++++ test/integration/model/findAll.test.js | 32 +++++++++- .../dialects/mysql/query-generator.test.js | 58 +++++++++---------- .../unit/query-generator/select-query.test.ts | 4 +- 4 files changed, 99 insertions(+), 32 deletions(-) diff --git a/src/dialects/mysql/query-generator.js b/src/dialects/mysql/query-generator.js index 8ed64a97dd53..3fca3e58d49f 100644 --- a/src/dialects/mysql/query-generator.js +++ b/src/dialects/mysql/query-generator.js @@ -239,6 +239,43 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { ]); } + _buildJsonObjectSql(tableName, attributes) { + return `json_object(${attributes.map(attr => `\`${attr}\`, (SELECT \`${tableName}\`.\`${attr}\` AS \`${attr}\`)`).join(', ')})`; + } + + selectQuery(tableName, options, model) { + const { attributes, where, order } = options; + + if (Array.isArray(attributes[0]) && attributes[0][1] === 'count') { + return `SELECT count(*) AS \`count\` FROM \`${tableName}\`;`; + } + + // TODO move up as constant + const rootSelectSql = 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root`'; + + let orderBySql = ''; + if (options.order) { + const orders = super.getQueryOrders(options, model); + if (orders.mainQueryOrder.length > 0) { + orderBySql = ` ORDER BY ${orders.mainQueryOrder.join(', ')}`; + } + } + + return ` + ${rootSelectSql} + FROM + (SELECT ${this._buildJsonObjectSql('_0_root.base', attributes)} AS \`root\` + FROM + (SELECT * + FROM \`${tableName}\` + ${super.whereQuery(where)} + ${orderBySql} + ${super.addLimitAndOffset(options, model)} + ) AS \`_0_root.base\` + ) AS \`_1_root\`; + `.replace(/\s\s+/g, ' ').trim(); + } + handleSequelizeMethod(smth, tableName, factory, options, prepend) { if (smth instanceof Utils.Json) { // Parse nested object diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index 993ac4cce58c..21b0602534f4 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -26,8 +26,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { aBool: DataTypes.BOOLEAN, binary: DataTypes.STRING(16, true), }); + this.Project = this.sequelize.define('Project', { + name: DataTypes.STRING, + }); + + this.User.hasMany(this.Project); + this.Project.belongsTo(this.User); - await this.User.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('findAll', () => { @@ -887,6 +893,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(async function () { const user = await this.User.create({ username: 'barfooz' }); this.user = user; + + const project = await this.Project.create({ + name: 'Investigate', + }); + + await user.setProjects([project]); }); it('should return a DAO when queryOptions are not set', async function () { @@ -910,6 +922,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(users[0]).to.be.instanceOf(Object); } }); + + it.skip('should return DAO data when json is false', async function () { + const users = await this.User.findAll({ where: { username: 'barfooz' }, json: false, include: [this.Project] }); + for (const user of users) { + expect(user).to.be.instanceOf(this.User); + expect(user.Projects[0]).to.be.instanceOf(this.Project); + } + }); + + it.skip('should return json data when json is true', async function () { + const users = await this.User.findAll({ where: { username: 'barfooz' }, json: true, include: [this.Project] }); + for (const user of users) { + expect(user).to.not.be.instanceOf(this.User); + expect(user).to.be.instanceOf(Object); + expect(user.Projects[0]).to.not.be.instanceOf(this.Project); + expect(user.Projects[0]).to.be.instanceOf(Object); + } + }); }); describe('include all', () => { diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index a99edc040d9d..7b53dac61eed 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -210,110 +210,110 @@ if (dialect === 'mysql') { selectQuery: [ { - arguments: ['myTable'], - expectation: 'SELECT * FROM `myTable`;', + arguments: ['myTable', { attributes: ['id'] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT `id`, `name` FROM `myTable`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { where: { id: 2 } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`), `name`, (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { where: { name: 'foo' } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`name` = \'foo\';', + arguments: ['myTable', { attributes: ['id'], where: { id: 2 } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `id` = 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { where: { name: 'foo\';DROP TABLE myTable;' } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`name` = \'foo\\\';DROP TABLE myTable;\';', + arguments: ['myTable', { attributes: ['id'], where: { name: 'foo' } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { where: 2 }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + arguments: ['myTable', { attributes: ['id'], where: { name: 'foo\';DROP TABLE myTable;' } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\\\';DROP TABLE myTable;\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['foo', { attributes: [['count(*)', 'count']] }], expectation: 'SELECT count(*) AS `count` FROM `foo`;', context: QueryGenerator, }, { - arguments: ['myTable', { order: ['id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', + arguments: ['myTable', { attributes: ['id'], order: ['id'] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { order: ['id', 'DESC'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', + arguments: ['myTable', { attributes: ['id'], order: ['id', 'DESC'] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id`, `DESC` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { order: ['myTable.id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', + arguments: ['myTable', { attributes: ['id'], order: ['myTable.id'] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', + arguments: ['myTable', { attributes: ['id'], order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { order: [['id', 'DESC']] }, function (sequelize) { + arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC']] }, function (sequelize) { return sequelize.define('myTable', {}); }], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { - arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function (sequelize) { + arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC'], ['name']] }, function (sequelize) { return sequelize.define('myTable', {}); }], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { title: 'functions can take functions as arguments', arguments: ['myTable', function (sequelize) { return { + attributes: ['id'], order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']], }; }], - expectation: 'SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { title: 'functions can take all types as arguments', arguments: ['myTable', function (sequelize) { return { + attributes: ['id'], order: [ [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'], ], }; }], - expectation: 'SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { title: 'sequelize.where with .fn as attribute and default comparator', arguments: ['myTable', function (sequelize) { return { + attributes: ['id'], where: sequelize.and( sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), { type: 1 }, ), }; }], - expectation: 'SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `myTable`.`type` = 1);', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { title: 'sequelize.where with .fn as attribute and LIKE comparator', arguments: ['myTable', function (sequelize) { return { + attributes: ['id'], where: sequelize.and( sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), { type: 1 }, ), }; }], - expectation: 'SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `myTable`.`type` = 1);', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { diff --git a/test/unit/query-generator/select-query.test.ts b/test/unit/query-generator/select-query.test.ts index 20b7051af687..162b97e05504 100644 --- a/test/unit/query-generator/select-query.test.ts +++ b/test/unit/query-generator/select-query.test.ts @@ -37,7 +37,7 @@ describe('QueryGenerator#selectQuery', () => { expectsql(sql, { postgres: `SELECT "id" FROM "Users" AS "User" OFFSET 1;`, - mysql: 'SELECT `id` FROM `Users` AS `User` LIMIT 18446744073709551615 OFFSET 1;', + mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` LIMIT 18446744073709551615 OFFSET 1) AS `_0_root.base`) AS `_1_root`;', mariadb: 'SELECT `id` FROM `Users` AS `User` LIMIT 18446744073709551615 OFFSET 1;', sqlite: 'SELECT `id` FROM `Users` AS `User` LIMIT -1 OFFSET 1;', snowflake: 'SELECT "id" FROM "Users" AS "User" LIMIT NULL OFFSET 1;', @@ -47,7 +47,7 @@ describe('QueryGenerator#selectQuery', () => { }); }); - describe('replacements', () => { + describe.skip('replacements', () => { it('parses named replacements in literals', async () => { // The goal of this test is to test that :replacements are parsed in literals in as many places as possible From a8dd373b0586334377db8be3a5c385a25154b3b1 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Fri, 20 May 2022 14:45:08 -0300 Subject: [PATCH 02/12] feat(6497): first group by test passing --- src/dialects/abstract/query-generator.js | 6 ++- src/dialects/mysql/query-generator.js | 22 ++++++++-- .../dialects/mysql/query-generator.test.js | 42 ++++++++++--------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/dialects/abstract/query-generator.js b/src/dialects/abstract/query-generator.js index e97f63335f24..502d5f292d2c 100644 --- a/src/dialects/abstract/query-generator.js +++ b/src/dialects/abstract/query-generator.js @@ -1397,7 +1397,7 @@ export class AbstractQueryGenerator { // Add GROUP BY to sub or main query if (options.group) { - options.group = Array.isArray(options.group) ? options.group.map(t => this.aliasGrouping(t, model, mainTable.as, options)).join(', ') : this.aliasGrouping(options.group, model, mainTable.as, options); + options.group = this.normalizeGrouping(model, mainTable, options); if (subQuery && options.group) { subQueryItems.push(` GROUP BY ${options.group}`); @@ -1473,6 +1473,10 @@ export class AbstractQueryGenerator { return `${query};`; } + normalizeGrouping(model, mainTable, options) { + return Array.isArray(options.group) ? options.group.map(t => this.aliasGrouping(t, model, mainTable.as, options)).join(', ') : this.aliasGrouping(options.group, model, mainTable.as, options); + } + aliasGrouping(field, model, tableName, options) { const src = Array.isArray(field) ? field[0] : field; diff --git a/src/dialects/mysql/query-generator.js b/src/dialects/mysql/query-generator.js index 3fca3e58d49f..ce0b3a544bd5 100644 --- a/src/dialects/mysql/query-generator.js +++ b/src/dialects/mysql/query-generator.js @@ -240,19 +240,34 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { } _buildJsonObjectSql(tableName, attributes) { - return `json_object(${attributes.map(attr => `\`${attr}\`, (SELECT \`${tableName}\`.\`${attr}\` AS \`${attr}\`)`).join(', ')})`; + return `json_object(${attributes.map(attr => `'${attr}', (SELECT \`${tableName}\`.\`${attr}\` AS \`${attr}\`)`).join(', ')})`; } selectQuery(tableName, options, model) { - const { attributes, where, order } = options; + const { attributes, where } = options; if (Array.isArray(attributes[0]) && attributes[0][1] === 'count') { return `SELECT count(*) AS \`count\` FROM \`${tableName}\`;`; } + const mainTable = { + name: tableName, + quotedName: null, + as: null, + model, + }; + // TODO move up as constant const rootSelectSql = 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root`'; + let groupBySql = ''; + if (options.group) { + options.group = super.normalizeGrouping(model, mainTable, options); + if (options.group) { + groupBySql = ` GROUP BY ${options.group}`; + } + } + let orderBySql = ''; if (options.order) { const orders = super.getQueryOrders(options, model); @@ -266,9 +281,10 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { FROM (SELECT ${this._buildJsonObjectSql('_0_root.base', attributes)} AS \`root\` FROM - (SELECT * + (SELECT ${groupBySql ? attributes.map(attr => `${attr} AS \`${attr}\``).join(', ') : '*'} FROM \`${tableName}\` ${super.whereQuery(where)} + ${groupBySql} ${orderBySql} ${super.addLimitAndOffset(options, model)} ) AS \`_0_root.base\` diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 7b53dac61eed..fe6195c32996 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -211,23 +211,23 @@ if (dialect === 'mysql') { selectQuery: [ { arguments: ['myTable', { attributes: ['id'] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`), `name`, (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`), \'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], where: { id: 2 } }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `id` = 2 ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `id` = 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], where: { name: 'foo' } }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\' ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], where: { name: 'foo\';DROP TABLE myTable;' } }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\\\';DROP TABLE myTable;\' ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\\\';DROP TABLE myTable;\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['foo', { attributes: [['count(*)', 'count']] }], @@ -235,32 +235,32 @@ if (dialect === 'mysql') { context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], order: ['id'] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id` ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], order: ['id', 'DESC'] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id`, `DESC` ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id`, `DESC` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], order: ['myTable.id'] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], order: [['myTable.id', 'DESC']] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC']] }, function (sequelize) { return sequelize.define('myTable', {}); }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC'], ['name']] }, function (sequelize) { return sequelize.define('myTable', {}); }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name` ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { @@ -271,7 +271,7 @@ if (dialect === 'mysql') { order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']], }; }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { @@ -285,7 +285,7 @@ if (dialect === 'mysql') { ], }; }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { @@ -299,7 +299,7 @@ if (dialect === 'mysql') { ), }; }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { @@ -313,26 +313,28 @@ if (dialect === 'mysql') { ), }; }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { title: 'single string argument should be quoted', - arguments: ['myTable', { group: 'name' }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + // a rather odd way to select distinct + arguments: ['myTable', { attributes: ['name'], group: 'name' }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { group: ['name'] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + arguments: ['myTable', { attributes: ['name'], group: ['name'] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'functions work for group by', arguments: ['myTable', function (sequelize) { return { + attributes: ['YEAR(createdAt)'], group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], }; }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)` FROM `myTable` GROUP BY YEAR(`createdAt`) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { From 356428a042b6d2e5278208ecd846e7761fdb3a0b Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Fri, 20 May 2022 17:08:10 -0300 Subject: [PATCH 03/12] feat(6497): group by with order by --- test/unit/dialects/mysql/query-generator.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index fe6195c32996..24b078bdcf5c 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -341,15 +341,16 @@ if (dialect === 'mysql') { title: 'It is possible to mix sequelize.fn and string arguments to group by', arguments: ['myTable', function (sequelize) { return { + attributes: ['YEAR(createdAt)', 'title'], group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], }; }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`), \'title\', (SELECT `_0_root.base`.`title` AS `title`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)`, title AS `title` FROM `myTable` GROUP BY YEAR(`createdAt`), `title` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { - arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', + arguments: ['myTable', { attributes: ['name'], group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ORDER BY `id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'HAVING clause works with where-like hash', From a5025f9a9280433d8cf99e721a158ae9f4052707 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Sat, 21 May 2022 19:04:25 -0300 Subject: [PATCH 04/12] feat(6497): implement having statement with attribute aliases --- src/dialects/abstract/query-generator.js | 23 ++++++++------ src/dialects/mysql/query-generator.js | 31 ++++++++++++++++--- .../dialects/mysql/query-generator.test.js | 4 +-- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/dialects/abstract/query-generator.js b/src/dialects/abstract/query-generator.js index 502d5f292d2c..832a84a105fa 100644 --- a/src/dialects/abstract/query-generator.js +++ b/src/dialects/abstract/query-generator.js @@ -1205,16 +1205,7 @@ export class AbstractQueryGenerator { options.includeAliases = new Map(); } - // resolve table name options - if (options.tableAs) { - mainTable.as = this.quoteIdentifier(options.tableAs); - } else if (!Array.isArray(mainTable.name) && mainTable.model) { - mainTable.as = this.quoteIdentifier(mainTable.model.name); - } - - mainTable.quotedName = !Array.isArray(mainTable.name) ? this.quoteTable(mainTable.name) : tableName.map(t => { - return Array.isArray(t) ? this.quoteTable(t[0], t[1]) : this.quoteTable(t, true); - }).join(', '); + this.resolveTableNameOptions(tableName, options, mainTable); if (subQuery && attributes.main) { for (const keyAtt of mainTable.model.primaryKeyAttributes) { @@ -1473,6 +1464,18 @@ export class AbstractQueryGenerator { return `${query};`; } + resolveTableNameOptions(tableName, options, mainTable) { + if (options.tableAs) { + mainTable.as = this.quoteIdentifier(options.tableAs); + } else if (!Array.isArray(mainTable.name) && mainTable.model) { + mainTable.as = this.quoteIdentifier(mainTable.model.name); + } + + mainTable.quotedName = !Array.isArray(mainTable.name) ? this.quoteTable(mainTable.name) : tableName.map(t => { + return Array.isArray(t) ? this.quoteTable(t[0], t[1]) : this.quoteTable(t, true); + }).join(', '); + } + normalizeGrouping(model, mainTable, options) { return Array.isArray(options.group) ? options.group.map(t => this.aliasGrouping(t, model, mainTable.as, options)).join(', ') : this.aliasGrouping(options.group, model, mainTable.as, options); } diff --git a/src/dialects/mysql/query-generator.js b/src/dialects/mysql/query-generator.js index ce0b3a544bd5..f22309d5951f 100644 --- a/src/dialects/mysql/query-generator.js +++ b/src/dialects/mysql/query-generator.js @@ -240,11 +240,23 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { } _buildJsonObjectSql(tableName, attributes) { - return `json_object(${attributes.map(attr => `'${attr}', (SELECT \`${tableName}\`.\`${attr}\` AS \`${attr}\`)`).join(', ')})`; + return `json_object(${attributes.map(([_, alias]) => `'${alias}', (SELECT \`${tableName}\`.\`${alias}\` AS \`${alias}\`)`).join(', ')})`; + } + + // returns an array of pair aliased attribute with alias. ie [['YEAR(`createdAt`)', 'creationYear']] + _getAttributesWithAliases(attributes, options, mainTable) { + const escapedAttributes = this.escapeAttributes(attributes, options, mainTable.as); + + // replace does this 'YEAR(`createdAt`) AS `creationYear`' -> ['YEAR(`createdAt`)', 'creationYear'] + return attributes.map((attr, i) => (typeof attr === 'string' ? [attr, attr] : escapedAttributes[i].replace(/(.*?) AS `(.*?)`/g, (_, b, c) => [b, c]).split(','))); } selectQuery(tableName, options, model) { - const { attributes, where } = options; + // TODO move up as constant + const rootSelectSql = 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root`'; + + const { where } = options; + let { attributes } = options; if (Array.isArray(attributes[0]) && attributes[0][1] === 'count') { return `SELECT count(*) AS \`count\` FROM \`${tableName}\`;`; @@ -257,8 +269,8 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { model, }; - // TODO move up as constant - const rootSelectSql = 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root`'; + super.resolveTableNameOptions(tableName, options, mainTable); + attributes = this._getAttributesWithAliases(attributes, options, mainTable); let groupBySql = ''; if (options.group) { @@ -276,15 +288,24 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { } } + let havingSql = ''; + if (options.having) { + const conditions = super.getWhereConditions(options.having, tableName, model, options, false); + if (conditions) { + havingSql = ` HAVING ${conditions}`; + } + } + return ` ${rootSelectSql} FROM (SELECT ${this._buildJsonObjectSql('_0_root.base', attributes)} AS \`root\` FROM - (SELECT ${groupBySql ? attributes.map(attr => `${attr} AS \`${attr}\``).join(', ') : '*'} + (SELECT ${groupBySql ? attributes.map(([attr, alias]) => `${attr} AS \`${alias}\``).join(', ') : '*'} FROM \`${tableName}\` ${super.whereQuery(where)} ${groupBySql} + ${havingSql} ${orderBySql} ${super.addLimitAndOffset(options, model)} ) AS \`_0_root.base\` diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 24b078bdcf5c..918690bf2593 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -356,12 +356,12 @@ if (dialect === 'mysql') { title: 'HAVING clause works with where-like hash', arguments: ['myTable', function (sequelize) { return { - attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], + attributes: ['title', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], group: ['creationYear', 'title'], having: { creationYear: { [Op.gt]: 2002 } }, }; }], - expectation: 'SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'title\', (SELECT `_0_root.base`.`title` AS `title`), \'creationYear\', (SELECT `_0_root.base`.`creationYear` AS `creationYear`)) AS `root` FROM (SELECT title AS `title`, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { From 803625cc87fb9e3de88dd759b0d4fe78f9d53c50 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Sun, 22 May 2022 21:25:27 -0300 Subject: [PATCH 05/12] feat(6497): all query generator tests passing --- .../dialects/mysql/query-generator.test.js | 85 ++++++++----------- .../unit/query-generator/select-query.test.ts | 2 +- 2 files changed, 36 insertions(+), 51 deletions(-) diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 918690bf2593..0e60414699c2 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -368,113 +368,98 @@ if (dialect === 'mysql') { title: 'Combination of sequelize.fn, sequelize.col and { in: ... }', arguments: ['myTable', function (sequelize) { return { + attributes: ['id'], where: sequelize.and( { archived: null }, sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }), ), }; }], - expectation: 'SELECT * FROM `myTable` WHERE (`myTable`.`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN (\'Lost\', \'Found\'));', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN (\'Lost\', \'Found\')) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { - arguments: ['myTable', { limit: 10 }], - expectation: 'SELECT * FROM `myTable` LIMIT 10;', + arguments: ['myTable', { attributes: ['id'], limit: 10 }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { limit: 10, offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 10 OFFSET 2;', + arguments: ['myTable', { attributes: ['id'], limit: 10, offset: 2 }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'uses default limit if only offset is specified', - arguments: ['myTable', { offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 18446744073709551615 OFFSET 2;', + arguments: ['myTable', { attributes: ['id'], offset: 2 }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 18446744073709551615 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'uses limit 0', - arguments: ['myTable', { limit: 0 }], - expectation: 'SELECT * FROM `myTable` LIMIT 0;', + arguments: ['myTable', { attributes: ['id'], limit: 0 }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 0 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'uses offset 0', - arguments: ['myTable', { offset: 0 }], - expectation: 'SELECT * FROM `myTable`;', + arguments: ['myTable', { attributes: ['id'], offset: 0 }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'multiple where arguments', - arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`boat` = \'canoe\' AND `myTable`.`weather` = \'cold\';', + arguments: ['myTable', { attributes: ['id'], where: { boat: 'canoe', weather: 'cold' } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `boat` = \'canoe\' AND `weather` = \'cold\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'no where arguments (object)', - arguments: ['myTable', { where: {} }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator, - }, { - title: 'no where arguments (string)', - arguments: ['myTable', { where: [''] }], - expectation: 'SELECT * FROM `myTable` WHERE 1=1;', + arguments: ['myTable', { attributes: ['id'], where: {} }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'no where arguments (null)', - arguments: ['myTable', { where: null }], - expectation: 'SELECT * FROM `myTable`;', + arguments: ['myTable', { attributes: ['id'], where: null }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'buffer as where argument', - arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` = X\'53657175656c697a65\';', + arguments: ['myTable', { attributes: ['id'], where: { field: Buffer.from('Sequelize') } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` = X\'53657175656c697a65\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use != if ne !== null', - arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 0;', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: 0 } } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 0 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use IS NOT if ne === null', - arguments: ['myTable', { where: { field: { [Op.ne]: null } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT NULL;', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: null } } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT NULL ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use IS NOT if not === BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: true } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT true;', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: true } } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT true ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use != if not !== BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 3;', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: 3 } } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 3 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'Regular Expression in where clause', - arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` REGEXP \'^[h|a|t]\';', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.regexp]: '^[h|a|t]' } } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'Regular Expression negation in where clause', - arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` NOT REGEXP \'^[h|a|t]\';', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` NOT REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'Empty having', arguments: ['myTable', function () { return { + attributes: ['id'], having: {}, }; }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'Having in subquery', - arguments: ['myTable', function () { - return { - subQuery: true, - tableAs: 'test', - having: { creationYear: { [Op.gt]: 2002 } }, - }; - }], - expectation: 'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `creationYear` > 2002) AS `test`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { @@ -487,7 +472,7 @@ if (dialect === 'mysql') { }, }, }], - expectation: 'SELECT `foo.bar.baz` FROM `myTable`;', + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'foo.bar.baz\', (SELECT `_0_root.base`.`foo.bar.baz` AS `foo.bar.baz`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, ], diff --git a/test/unit/query-generator/select-query.test.ts b/test/unit/query-generator/select-query.test.ts index 162b97e05504..2425c86ba059 100644 --- a/test/unit/query-generator/select-query.test.ts +++ b/test/unit/query-generator/select-query.test.ts @@ -37,7 +37,7 @@ describe('QueryGenerator#selectQuery', () => { expectsql(sql, { postgres: `SELECT "id" FROM "Users" AS "User" OFFSET 1;`, - mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(`id`, (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` LIMIT 18446744073709551615 OFFSET 1) AS `_0_root.base`) AS `_1_root`;', + mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` LIMIT 18446744073709551615 OFFSET 1) AS `_0_root.base`) AS `_1_root`;', mariadb: 'SELECT `id` FROM `Users` AS `User` LIMIT 18446744073709551615 OFFSET 1;', sqlite: 'SELECT `id` FROM `Users` AS `User` LIMIT -1 OFFSET 1;', snowflake: 'SELECT "id" FROM "Users" AS "User" LIMIT NULL OFFSET 1;', From f7b1c9855d26051ad589d67ee26743ae2eec7036 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Sun, 22 May 2022 21:30:59 -0300 Subject: [PATCH 06/12] feat(6497): fix rawSelect --- test/unit/query-interface/raw-select.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/query-interface/raw-select.test.ts b/test/unit/query-interface/raw-select.test.ts index 982ec4d38514..21c4976c4d9b 100644 --- a/test/unit/query-interface/raw-select.test.ts +++ b/test/unit/query-interface/raw-select.test.ts @@ -32,6 +32,7 @@ describe('QueryInterface#rawSelect', () => { expectsql(firstCall.args[0] as string, { default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'some :data';`, mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'some :data';`, + mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` WHERE `username` = \'some :data\') AS `_0_root.base`) AS `_1_root`;', }); }); }); From 9fc6bd08a4bf05790525a40af45a6e4f738cdf33 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Sun, 22 May 2022 21:42:41 -0300 Subject: [PATCH 07/12] feat(6497): fix query interface select --- test/unit/query-interface/select.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/query-interface/select.test.ts b/test/unit/query-interface/select.test.ts index 8dc140fb3765..41547c69a9a5 100644 --- a/test/unit/query-interface/select.test.ts +++ b/test/unit/query-interface/select.test.ts @@ -32,6 +32,7 @@ describe('QueryInterface#select', () => { expectsql(firstCall.args[0] as string, { default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'some :data';`, mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'some :data';`, + mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` WHERE `username` = \'some :data\') AS `_0_root.base`) AS `_1_root`;', }); }); }); From 9303f7876f8a4bc2d8dbb8c93cc59d96b5c955b0 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Sun, 22 May 2022 22:03:09 -0300 Subject: [PATCH 08/12] feat(6497): fix sql group test --- test/unit/sql/group.test.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/test/unit/sql/group.test.js b/test/unit/sql/group.test.js index cfb3fb35a579..f3e743e8d278 100644 --- a/test/unit/sql/group.test.js +++ b/test/unit/sql/group.test.js @@ -35,26 +35,30 @@ describe(Support.getTestDialectTeaser('SQL'), () => { testsql({ model: User, + attributes: ['name'], group: ['name'], }, { - default: 'SELECT * FROM `Users` AS `User` GROUP BY `name`;', - postgres: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', - db2: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', - ibmi: 'SELECT * FROM "Users" AS "User" GROUP BY "name"', - mssql: 'SELECT * FROM [Users] AS [User] GROUP BY [name];', - snowflake: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + default: 'SELECT `name` FROM `Users` AS `User` GROUP BY `name`;', + postgres: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name";', + db2: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name";', + ibmi: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name"', + mssql: 'SELECT [name] FROM [Users] AS [User] GROUP BY [name];', + snowflake: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name";', + mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `Users` GROUP BY `name`) AS `_0_root.base`) AS `_1_root`;', }); testsql({ model: User, + attributes: ['name'], group: [], }, { - default: 'SELECT * FROM `Users` AS `User`;', - postgres: 'SELECT * FROM "Users" AS "User";', - db2: 'SELECT * FROM "Users" AS "User";', - ibmi: 'SELECT * FROM "Users" AS "User"', - mssql: 'SELECT * FROM [Users] AS [User];', - snowflake: 'SELECT * FROM "Users" AS "User";', + default: 'SELECT `name` FROM `Users` AS `User`;', + postgres: 'SELECT "name" FROM "Users" AS "User";', + db2: 'SELECT "name" FROM "Users" AS "User";', + ibmi: 'SELECT "name" FROM "Users" AS "User"', + mssql: 'SELECT [name] FROM [Users] AS [User];', + snowflake: 'SELECT "name" FROM "Users" AS "User";', + mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `Users` ) AS `_0_root.base`) AS `_1_root`;', }); }); }); From 4449a6d7eb9b962e3a26b65af0bc9b8a53eca814 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Mon, 23 May 2022 15:43:37 -0300 Subject: [PATCH 09/12] feat(6497): bring back no json tests --- src/dialects/mysql/query-generator.js | 5 + .../dialects/mysql/query-generator.test.js | 353 ++++++++++++++++-- 2 files changed, 327 insertions(+), 31 deletions(-) diff --git a/src/dialects/mysql/query-generator.js b/src/dialects/mysql/query-generator.js index f22309d5951f..854798cf4644 100644 --- a/src/dialects/mysql/query-generator.js +++ b/src/dialects/mysql/query-generator.js @@ -252,6 +252,11 @@ export class MySqlQueryGenerator extends AbstractQueryGenerator { } selectQuery(tableName, options, model) { + + if (!options?.json === true) { + return super.selectQuery(tableName, options, model); + } + // TODO move up as constant const rootSelectSql = 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root`'; diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 0e60414699c2..d395aa2a42fe 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -210,54 +210,335 @@ if (dialect === 'mysql') { selectQuery: [ { - arguments: ['myTable', { attributes: ['id'] }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + arguments: ['myTable'], + expectation: 'SELECT * FROM `myTable`;', context: QueryGenerator, }, { arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT `id`, `name` FROM `myTable`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + context: QueryGenerator, + }, { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`name` = \'foo\';', + context: QueryGenerator, + }, { + arguments: ['myTable', { where: { name: 'foo\';DROP TABLE myTable;' } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`name` = \'foo\\\';DROP TABLE myTable;\';', + context: QueryGenerator, + }, { + arguments: ['myTable', { where: 2 }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + context: QueryGenerator, + }, { + arguments: ['foo', { attributes: [['count(*)', 'count']] }], + expectation: 'SELECT count(*) AS `count` FROM `foo`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', + context: QueryGenerator, + }, { + arguments: ['myTable', { order: [['id', 'DESC']] }, function (sequelize) { + return sequelize.define('myTable', {}); + }], + expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', + context: QueryGenerator, + needsSequelize: true, + }, { + arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function (sequelize) { + return sequelize.define('myTable', {}); + }], + expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'functions can take functions as arguments', + arguments: ['myTable', function (sequelize) { + return { + order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']], + }; + }], + expectation: 'SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'functions can take all types as arguments', + arguments: ['myTable', function (sequelize) { + return { + order: [ + [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], + [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'], + ], + }; + }], + expectation: 'SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'sequelize.where with .fn as attribute and default comparator', + arguments: ['myTable', function (sequelize) { + return { + where: sequelize.and( + sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), + { type: 1 }, + ), + }; + }], + expectation: 'SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `myTable`.`type` = 1);', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'sequelize.where with .fn as attribute and LIKE comparator', + arguments: ['myTable', function (sequelize) { + return { + where: sequelize.and( + sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), + { type: 1 }, + ), + }; + }], + expectation: 'SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `myTable`.`type` = 1);', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: QueryGenerator, + }, { + title: 'functions work for group by', + arguments: ['myTable', function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: ['myTable', function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', + context: QueryGenerator, + needsSequelize: true, + }, { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', + context: QueryGenerator, + }, { + title: 'HAVING clause works with where-like hash', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], + group: ['creationYear', 'title'], + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }], + expectation: 'SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'Combination of sequelize.fn, sequelize.col and { in: ... }', + arguments: ['myTable', function (sequelize) { + return { + where: sequelize.and( + { archived: null }, + sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }), + ), + }; + }], + expectation: 'SELECT * FROM `myTable` WHERE (`myTable`.`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN (\'Lost\', \'Found\'));', + context: QueryGenerator, + needsSequelize: true, + }, { + arguments: ['myTable', { limit: 10 }], + expectation: 'SELECT * FROM `myTable` LIMIT 10;', + context: QueryGenerator, + }, { + arguments: ['myTable', { limit: 10, offset: 2 }], + expectation: 'SELECT * FROM `myTable` LIMIT 10 OFFSET 2;', + context: QueryGenerator, + }, { + title: 'uses default limit if only offset is specified', + arguments: ['myTable', { offset: 2 }], + expectation: 'SELECT * FROM `myTable` LIMIT 18446744073709551615 OFFSET 2;', + context: QueryGenerator, + }, { + title: 'uses limit 0', + arguments: ['myTable', { limit: 0 }], + expectation: 'SELECT * FROM `myTable` LIMIT 0;', + context: QueryGenerator, + }, { + title: 'uses offset 0', + arguments: ['myTable', { offset: 0 }], + expectation: 'SELECT * FROM `myTable`;', + context: QueryGenerator, + }, { + title: 'multiple where arguments', + arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`boat` = \'canoe\' AND `myTable`.`weather` = \'cold\';', + context: QueryGenerator, + }, { + title: 'no where arguments (object)', + arguments: ['myTable', { where: {} }], + expectation: 'SELECT * FROM `myTable`;', + context: QueryGenerator, + }, { + title: 'no where arguments (string)', + arguments: ['myTable', { where: [''] }], + expectation: 'SELECT * FROM `myTable` WHERE 1=1;', + context: QueryGenerator, + }, { + title: 'no where arguments (null)', + arguments: ['myTable', { where: null }], + expectation: 'SELECT * FROM `myTable`;', + context: QueryGenerator, + }, { + title: 'buffer as where argument', + arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` = X\'53657175656c697a65\';', + context: QueryGenerator, + }, { + title: 'use != if ne !== null', + arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 0;', + context: QueryGenerator, + }, { + title: 'use IS NOT if ne === null', + arguments: ['myTable', { where: { field: { [Op.ne]: null } } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT NULL;', + context: QueryGenerator, + }, { + title: 'use IS NOT if not === BOOLEAN', + arguments: ['myTable', { where: { field: { [Op.not]: true } } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT true;', + context: QueryGenerator, + }, { + title: 'use != if not !== BOOLEAN', + arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 3;', + context: QueryGenerator, + }, { + title: 'Regular Expression in where clause', + arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` REGEXP \'^[h|a|t]\';', + context: QueryGenerator, + }, { + title: 'Regular Expression negation in where clause', + arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` NOT REGEXP \'^[h|a|t]\';', + context: QueryGenerator, + }, { + title: 'Empty having', + arguments: ['myTable', function () { + return { + having: {}, + }; + }], + expectation: 'SELECT * FROM `myTable`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'Having in subquery', + arguments: ['myTable', function () { + return { + subQuery: true, + tableAs: 'test', + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }], + expectation: 'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `creationYear` > 2002) AS `test`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'Contains fields with "." characters.', + arguments: ['myTable', { + attributes: ['foo.bar.baz'], + model: { + rawAttributes: { + 'foo.bar.baz': {}, + }, + }, + }], + expectation: 'SELECT `foo.bar.baz` FROM `myTable`;', + context: QueryGenerator, + }, + ], + + selectJSONQuery: [ + { + arguments: ['myTable', { attributes: ['id'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id', 'name'], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`), \'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], where: { id: 2 } }], + arguments: ['myTable', { attributes: ['id'], where: { id: 2 }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `id` = 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], where: { name: 'foo' } }], + arguments: ['myTable', { attributes: ['id'], where: { name: 'foo' }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], where: { name: 'foo\';DROP TABLE myTable;' } }], + arguments: ['myTable', { attributes: ['id'], where: { name: 'foo\';DROP TABLE myTable;' }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\\\';DROP TABLE myTable;\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['foo', { attributes: [['count(*)', 'count']] }], + arguments: ['foo', { attributes: [['count(*)', 'count']], json: true }], expectation: 'SELECT count(*) AS `count` FROM `foo`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], order: ['id'] }], + arguments: ['myTable', { attributes: ['id'], order: ['id'], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], order: ['id', 'DESC'] }], + arguments: ['myTable', { attributes: ['id'], order: ['id', 'DESC'], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id`, `DESC` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], order: ['myTable.id'] }], + arguments: ['myTable', { attributes: ['id'], order: ['myTable.id'], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], order: [['myTable.id', 'DESC']] }], + arguments: ['myTable', { attributes: ['id'], order: [['myTable.id', 'DESC']], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC']] }, function (sequelize) { + arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC']], json: true }, function (sequelize) { return sequelize.define('myTable', {}); }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { - arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC'], ['name']] }, function (sequelize) { + arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC'], ['name']], json: true }, function (sequelize) { return sequelize.define('myTable', {}); }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name` ) AS `_0_root.base` ) AS `_1_root`;', @@ -269,6 +550,7 @@ if (dialect === 'mysql') { return { attributes: ['id'], order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']], + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC ) AS `_0_root.base` ) AS `_1_root`;', @@ -283,6 +565,7 @@ if (dialect === 'mysql') { [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'], ], + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC ) AS `_0_root.base` ) AS `_1_root`;', @@ -297,6 +580,7 @@ if (dialect === 'mysql') { sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), { type: 1 }, ), + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', @@ -311,6 +595,7 @@ if (dialect === 'mysql') { sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), { type: 1 }, ), + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', @@ -319,11 +604,11 @@ if (dialect === 'mysql') { }, { title: 'single string argument should be quoted', // a rather odd way to select distinct - arguments: ['myTable', { attributes: ['name'], group: 'name' }], + arguments: ['myTable', { attributes: ['name'], group: 'name', json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['name'], group: ['name'] }], + arguments: ['myTable', { attributes: ['name'], group: ['name'], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { @@ -332,6 +617,7 @@ if (dialect === 'mysql') { return { attributes: ['YEAR(createdAt)'], group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)` FROM `myTable` GROUP BY YEAR(`createdAt`) ) AS `_0_root.base` ) AS `_1_root`;', @@ -343,13 +629,14 @@ if (dialect === 'mysql') { return { attributes: ['YEAR(createdAt)', 'title'], group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`), \'title\', (SELECT `_0_root.base`.`title` AS `title`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)`, title AS `title` FROM `myTable` GROUP BY YEAR(`createdAt`), `title` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { - arguments: ['myTable', { attributes: ['name'], group: 'name', order: [['id', 'DESC']] }], + arguments: ['myTable', { attributes: ['name'], group: 'name', order: [['id', 'DESC']], json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ORDER BY `id` DESC ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { @@ -359,6 +646,7 @@ if (dialect === 'mysql') { attributes: ['title', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], group: ['creationYear', 'title'], having: { creationYear: { [Op.gt]: 2002 } }, + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'title\', (SELECT `_0_root.base`.`title` AS `title`), \'creationYear\', (SELECT `_0_root.base`.`creationYear` AS `creationYear`)) AS `root` FROM (SELECT title AS `title`, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002 ) AS `_0_root.base` ) AS `_1_root`;', @@ -373,82 +661,83 @@ if (dialect === 'mysql') { { archived: null }, sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }), ), + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN (\'Lost\', \'Found\')) ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, needsSequelize: true, }, { - arguments: ['myTable', { attributes: ['id'], limit: 10 }], + arguments: ['myTable', { attributes: ['id'], limit: 10, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { - arguments: ['myTable', { attributes: ['id'], limit: 10, offset: 2 }], + arguments: ['myTable', { attributes: ['id'], limit: 10, offset: 2, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'uses default limit if only offset is specified', - arguments: ['myTable', { attributes: ['id'], offset: 2 }], + arguments: ['myTable', { attributes: ['id'], offset: 2, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 18446744073709551615 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'uses limit 0', - arguments: ['myTable', { attributes: ['id'], limit: 0 }], + arguments: ['myTable', { attributes: ['id'], limit: 0, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 0 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'uses offset 0', - arguments: ['myTable', { attributes: ['id'], offset: 0 }], + arguments: ['myTable', { attributes: ['id'], offset: 0, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'multiple where arguments', - arguments: ['myTable', { attributes: ['id'], where: { boat: 'canoe', weather: 'cold' } }], + arguments: ['myTable', { attributes: ['id'], where: { boat: 'canoe', weather: 'cold' }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `boat` = \'canoe\' AND `weather` = \'cold\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'no where arguments (object)', - arguments: ['myTable', { attributes: ['id'], where: {} }], + arguments: ['myTable', { attributes: ['id'], where: {}, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'no where arguments (null)', - arguments: ['myTable', { attributes: ['id'], where: null }], + arguments: ['myTable', { attributes: ['id'], where: null, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'buffer as where argument', - arguments: ['myTable', { attributes: ['id'], where: { field: Buffer.from('Sequelize') } }], + arguments: ['myTable', { attributes: ['id'], where: { field: Buffer.from('Sequelize') }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` = X\'53657175656c697a65\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use != if ne !== null', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: 0 } } }], + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: 0 } }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 0 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use IS NOT if ne === null', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: null } } }], + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: null } }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT NULL ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use IS NOT if not === BOOLEAN', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: true } } }], + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: true } }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT true ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'use != if not !== BOOLEAN', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: 3 } } }], + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: 3 } }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 3 ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'Regular Expression in where clause', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.regexp]: '^[h|a|t]' } } }], + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.regexp]: '^[h|a|t]' } }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { title: 'Regular Expression negation in where clause', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.notRegexp]: '^[h|a|t]' } }, json: true }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` NOT REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, }, { @@ -457,6 +746,7 @@ if (dialect === 'mysql') { return { attributes: ['id'], having: {}, + json: true, }; }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', @@ -471,6 +761,7 @@ if (dialect === 'mysql') { 'foo.bar.baz': {}, }, }, + json: true, }], expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'foo.bar.baz\', (SELECT `_0_root.base`.`foo.bar.baz` AS `foo.bar.baz`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', context: QueryGenerator, @@ -785,7 +1076,7 @@ if (dialect === 'mysql') { // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; - const conditions = this.queryGenerator[suiteTitle](...test.arguments); + const conditions = this.queryGenerator[suiteTitle === 'selectJSONQuery' ? 'selectQuery' : suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); }); } From 1d3c3eba92e0c21e2129792d19dab12f3c2fcf3b Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Mon, 23 May 2022 15:52:35 -0300 Subject: [PATCH 10/12] feat(6497): change findAll test --- test/integration/model/findAll.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index 21b0602534f4..0056e9cc95ae 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -923,8 +923,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - it.skip('should return DAO data when json is false', async function () { - const users = await this.User.findAll({ where: { username: 'barfooz' }, json: false, include: [this.Project] }); + it('should return DAO data when json is not specified', async function () { + const users = await this.User.findAll({ where: { username: 'barfooz' }, include: [this.Project] }); for (const user of users) { expect(user).to.be.instanceOf(this.User); expect(user.Projects[0]).to.be.instanceOf(this.Project); From e398f2d4f7f40a69cbc244fab144d1ad3c48a509 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Mon, 23 May 2022 16:07:09 -0300 Subject: [PATCH 11/12] feat(6497): added todo --- test/unit/query-generator/select-query.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/query-generator/select-query.test.ts b/test/unit/query-generator/select-query.test.ts index 2425c86ba059..00624191fd30 100644 --- a/test/unit/query-generator/select-query.test.ts +++ b/test/unit/query-generator/select-query.test.ts @@ -37,7 +37,7 @@ describe('QueryGenerator#selectQuery', () => { expectsql(sql, { postgres: `SELECT "id" FROM "Users" AS "User" OFFSET 1;`, - mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` LIMIT 18446744073709551615 OFFSET 1) AS `_0_root.base`) AS `_1_root`;', + mysql: 'SELECT `id` FROM `Users` AS `User` LIMIT 18446744073709551615 OFFSET 1;', mariadb: 'SELECT `id` FROM `Users` AS `User` LIMIT 18446744073709551615 OFFSET 1;', sqlite: 'SELECT `id` FROM `Users` AS `User` LIMIT -1 OFFSET 1;', snowflake: 'SELECT "id" FROM "Users" AS "User" LIMIT NULL OFFSET 1;', @@ -47,7 +47,9 @@ describe('QueryGenerator#selectQuery', () => { }); }); - describe.skip('replacements', () => { + // TODO add replacements for MySQL in JSON mode + + describe('replacements', () => { it('parses named replacements in literals', async () => { // The goal of this test is to test that :replacements are parsed in literals in as many places as possible From b8cd84ad46ba359c2eaf90a5fe349e2f26e323d0 Mon Sep 17 00:00:00 2001 From: Nico Gallinal Date: Mon, 23 May 2022 16:39:41 -0300 Subject: [PATCH 12/12] feat(6497): move all json tests to a single file --- .../dialects/mysql/query-generator.test.js | 281 +-------------- .../dialects/mysql/sql/select-json.test.js | 330 ++++++++++++++++++ .../unit/query-generator/select-query.test.ts | 2 - test/unit/query-interface/raw-select.test.ts | 1 - test/unit/query-interface/select.test.ts | 1 - test/unit/sql/group.test.js | 28 +- 6 files changed, 343 insertions(+), 300 deletions(-) create mode 100644 test/unit/dialects/mysql/sql/select-json.test.js diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index d395aa2a42fe..a99edc040d9d 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -489,285 +489,6 @@ if (dialect === 'mysql') { }, ], - selectJSONQuery: [ - { - arguments: ['myTable', { attributes: ['id'], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id', 'name'], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`), \'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], where: { id: 2 }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `id` = 2 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], where: { name: 'foo' }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\' ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], where: { name: 'foo\';DROP TABLE myTable;' }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\\\';DROP TABLE myTable;\' ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['foo', { attributes: [['count(*)', 'count']], json: true }], - expectation: 'SELECT count(*) AS `count` FROM `foo`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], order: ['id'], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], order: ['id', 'DESC'], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id`, `DESC` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], order: ['myTable.id'], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], order: [['myTable.id', 'DESC']], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC']], json: true }, function (sequelize) { - return sequelize.define('myTable', {}); - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC'], ['name']], json: true }, function (sequelize) { - return sequelize.define('myTable', {}); - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'functions can take functions as arguments', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['id'], - order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']], - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'functions can take all types as arguments', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['id'], - order: [ - [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], - [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'], - ], - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'sequelize.where with .fn as attribute and default comparator', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['id'], - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), - { type: 1 }, - ), - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'sequelize.where with .fn as attribute and LIKE comparator', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['id'], - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), - { type: 1 }, - ), - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'single string argument should be quoted', - // a rather odd way to select distinct - arguments: ['myTable', { attributes: ['name'], group: 'name', json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['name'], group: ['name'], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'functions work for group by', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['YEAR(createdAt)'], - group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)` FROM `myTable` GROUP BY YEAR(`createdAt`) ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'It is possible to mix sequelize.fn and string arguments to group by', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['YEAR(createdAt)', 'title'], - group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`), \'title\', (SELECT `_0_root.base`.`title` AS `title`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)`, title AS `title` FROM `myTable` GROUP BY YEAR(`createdAt`), `title` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - arguments: ['myTable', { attributes: ['name'], group: 'name', order: [['id', 'DESC']], json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ORDER BY `id` DESC ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'HAVING clause works with where-like hash', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['title', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], - group: ['creationYear', 'title'], - having: { creationYear: { [Op.gt]: 2002 } }, - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'title\', (SELECT `_0_root.base`.`title` AS `title`), \'creationYear\', (SELECT `_0_root.base`.`creationYear` AS `creationYear`)) AS `root` FROM (SELECT title AS `title`, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'Combination of sequelize.fn, sequelize.col and { in: ... }', - arguments: ['myTable', function (sequelize) { - return { - attributes: ['id'], - where: sequelize.and( - { archived: null }, - sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }), - ), - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN (\'Lost\', \'Found\')) ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - arguments: ['myTable', { attributes: ['id'], limit: 10, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - arguments: ['myTable', { attributes: ['id'], limit: 10, offset: 2, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'uses default limit if only offset is specified', - arguments: ['myTable', { attributes: ['id'], offset: 2, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 18446744073709551615 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'uses limit 0', - arguments: ['myTable', { attributes: ['id'], limit: 0, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 0 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'uses offset 0', - arguments: ['myTable', { attributes: ['id'], offset: 0, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'multiple where arguments', - arguments: ['myTable', { attributes: ['id'], where: { boat: 'canoe', weather: 'cold' }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `boat` = \'canoe\' AND `weather` = \'cold\' ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'no where arguments (object)', - arguments: ['myTable', { attributes: ['id'], where: {}, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'no where arguments (null)', - arguments: ['myTable', { attributes: ['id'], where: null, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'buffer as where argument', - arguments: ['myTable', { attributes: ['id'], where: { field: Buffer.from('Sequelize') }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` = X\'53657175656c697a65\' ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'use != if ne !== null', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: 0 } }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 0 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'use IS NOT if ne === null', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: null } }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT NULL ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'use IS NOT if not === BOOLEAN', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: true } }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT true ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'use != if not !== BOOLEAN', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: 3 } }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 3 ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'Regular Expression in where clause', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.regexp]: '^[h|a|t]' } }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'Regular Expression negation in where clause', - arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.notRegexp]: '^[h|a|t]' } }, json: true }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` NOT REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, { - title: 'Empty having', - arguments: ['myTable', function () { - return { - attributes: ['id'], - having: {}, - json: true, - }; - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - needsSequelize: true, - }, { - title: 'Contains fields with "." characters.', - arguments: ['myTable', { - attributes: ['foo.bar.baz'], - model: { - rawAttributes: { - 'foo.bar.baz': {}, - }, - }, - json: true, - }], - expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'foo.bar.baz\', (SELECT `_0_root.base`.`foo.bar.baz` AS `foo.bar.baz`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', - context: QueryGenerator, - }, - ], - insertQuery: [ { arguments: ['myTable', { name: 'foo' }], @@ -1076,7 +797,7 @@ if (dialect === 'mysql') { // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; - const conditions = this.queryGenerator[suiteTitle === 'selectJSONQuery' ? 'selectQuery' : suiteTitle](...test.arguments); + const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); }); } diff --git a/test/unit/dialects/mysql/sql/select-json.test.js b/test/unit/dialects/mysql/sql/select-json.test.js new file mode 100644 index 000000000000..088cfd4631e6 --- /dev/null +++ b/test/unit/dialects/mysql/sql/select-json.test.js @@ -0,0 +1,330 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const _ = require('lodash'); +const { Op, IndexHints } = require('@sequelize/core'); +const { MySqlQueryGenerator: QueryGenerator } = require('@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/mysql/query-generator.js'); + +if (dialect === 'mysql') { + describe('[MYSQL Specific] select data as json', () => { + const suites = { + + selectQuery: [ + { + arguments: ['myTable', { attributes: ['id'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id', 'name'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`), \'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], where: { id: 2 }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `id` = 2 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], where: { name: 'foo' }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\' ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], where: { name: 'foo\';DROP TABLE myTable;' }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `name` = \'foo\\\';DROP TABLE myTable;\' ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['foo', { attributes: [['count(*)', 'count']], json: true }], + expectation: 'SELECT count(*) AS `count` FROM `foo`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], order: ['id'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], order: ['id', 'DESC'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `id`, `DESC` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], order: ['myTable.id'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], order: [['myTable.id', 'DESC']], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC']], json: true }, function (sequelize) { + return sequelize.define('myTable', {}); + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + arguments: ['myTable', { attributes: ['id'], order: [['id', 'DESC'], ['name']], json: true }, function (sequelize) { + return sequelize.define('myTable', {}); + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'functions can take functions as arguments', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['id'], + order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']], + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'functions can take all types as arguments', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['id'], + order: [ + [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], + [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'], + ], + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55\') ASC ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'sequelize.where with .fn as attribute and default comparator', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['id'], + where: sequelize.and( + sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), + { type: 1 }, + ), + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = \'jan\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'sequelize.where with .fn as attribute and LIKE comparator', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['id'], + where: sequelize.and( + sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), + { type: 1 }, + ), + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE \'%t%\' AND `type` = 1) ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'single string argument should be quoted', + // a rather odd way to select distinct + arguments: ['myTable', { attributes: ['name'], group: 'name', json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['name'], group: ['name'], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'functions work for group by', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['YEAR(createdAt)'], + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)` FROM `myTable` GROUP BY YEAR(`createdAt`) ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['YEAR(createdAt)', 'title'], + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'YEAR(createdAt)\', (SELECT `_0_root.base`.`YEAR(createdAt)` AS `YEAR(createdAt)`), \'title\', (SELECT `_0_root.base`.`title` AS `title`)) AS `root` FROM (SELECT YEAR(createdAt) AS `YEAR(createdAt)`, title AS `title` FROM `myTable` GROUP BY YEAR(`createdAt`), `title` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + arguments: ['myTable', { attributes: ['name'], group: 'name', order: [['id', 'DESC']], json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `myTable` GROUP BY `name` ORDER BY `id` DESC ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'HAVING clause works with where-like hash', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['title', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], + group: ['creationYear', 'title'], + having: { creationYear: { [Op.gt]: 2002 } }, + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'title\', (SELECT `_0_root.base`.`title` AS `title`), \'creationYear\', (SELECT `_0_root.base`.`creationYear` AS `creationYear`)) AS `root` FROM (SELECT title AS `title`, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'Combination of sequelize.fn, sequelize.col and { in: ... }', + arguments: ['myTable', function (sequelize) { + return { + attributes: ['id'], + where: sequelize.and( + { archived: null }, + sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }), + ), + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE (`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN (\'Lost\', \'Found\')) ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + arguments: ['myTable', { attributes: ['id'], limit: 10, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + arguments: ['myTable', { attributes: ['id'], limit: 10, offset: 2, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 10 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'uses default limit if only offset is specified', + arguments: ['myTable', { attributes: ['id'], offset: 2, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 18446744073709551615 OFFSET 2 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'uses limit 0', + arguments: ['myTable', { attributes: ['id'], limit: 0, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` LIMIT 0 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'uses offset 0', + arguments: ['myTable', { attributes: ['id'], offset: 0, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'multiple where arguments', + arguments: ['myTable', { attributes: ['id'], where: { boat: 'canoe', weather: 'cold' }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `boat` = \'canoe\' AND `weather` = \'cold\' ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'no where arguments (object)', + arguments: ['myTable', { attributes: ['id'], where: {}, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'no where arguments (null)', + arguments: ['myTable', { attributes: ['id'], where: null, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'buffer as where argument', + arguments: ['myTable', { attributes: ['id'], where: { field: Buffer.from('Sequelize') }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` = X\'53657175656c697a65\' ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'use != if ne !== null', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: 0 } }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 0 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'use IS NOT if ne === null', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.ne]: null } }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT NULL ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'use IS NOT if not === BOOLEAN', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: true } }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` IS NOT true ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'use != if not !== BOOLEAN', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.not]: 3 } }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` != 3 ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'Regular Expression in where clause', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.regexp]: '^[h|a|t]' } }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'Regular Expression negation in where clause', + arguments: ['myTable', { attributes: ['id'], where: { field: { [Op.notRegexp]: '^[h|a|t]' } }, json: true }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` WHERE `field` NOT REGEXP \'^[h|a|t]\' ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, { + title: 'Empty having', + arguments: ['myTable', function () { + return { + attributes: ['id'], + having: {}, + json: true, + }; + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + needsSequelize: true, + }, { + title: 'Contains fields with "." characters.', + arguments: ['myTable', { + attributes: ['foo.bar.baz'], + model: { + rawAttributes: { + 'foo.bar.baz': {}, + }, + }, + json: true, + }], + expectation: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'foo.bar.baz\', (SELECT `_0_root.base`.`foo.bar.baz` AS `foo.bar.baz`)) AS `root` FROM (SELECT * FROM `myTable` ) AS `_0_root.base` ) AS `_1_root`;', + context: QueryGenerator, + }, + ], + }; + + _.each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + beforeEach(function () { + this.queryGenerator = new QueryGenerator({ + sequelize: this.sequelize, + _dialect: this.sequelize.dialect, + }); + }); + + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = test.title || `MySQL correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, function () { + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](this.sequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](this.sequelize); + } + } + + // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; + + const conditions = this.queryGenerator.selectQuery(...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + }); +} diff --git a/test/unit/query-generator/select-query.test.ts b/test/unit/query-generator/select-query.test.ts index 00624191fd30..20b7051af687 100644 --- a/test/unit/query-generator/select-query.test.ts +++ b/test/unit/query-generator/select-query.test.ts @@ -47,8 +47,6 @@ describe('QueryGenerator#selectQuery', () => { }); }); - // TODO add replacements for MySQL in JSON mode - describe('replacements', () => { it('parses named replacements in literals', async () => { // The goal of this test is to test that :replacements are parsed in literals in as many places as possible diff --git a/test/unit/query-interface/raw-select.test.ts b/test/unit/query-interface/raw-select.test.ts index 21c4976c4d9b..982ec4d38514 100644 --- a/test/unit/query-interface/raw-select.test.ts +++ b/test/unit/query-interface/raw-select.test.ts @@ -32,7 +32,6 @@ describe('QueryInterface#rawSelect', () => { expectsql(firstCall.args[0] as string, { default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'some :data';`, mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'some :data';`, - mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` WHERE `username` = \'some :data\') AS `_0_root.base`) AS `_1_root`;', }); }); }); diff --git a/test/unit/query-interface/select.test.ts b/test/unit/query-interface/select.test.ts index 41547c69a9a5..8dc140fb3765 100644 --- a/test/unit/query-interface/select.test.ts +++ b/test/unit/query-interface/select.test.ts @@ -32,7 +32,6 @@ describe('QueryInterface#select', () => { expectsql(firstCall.args[0] as string, { default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'some :data';`, mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'some :data';`, - mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'id\', (SELECT `_0_root.base`.`id` AS `id`)) AS `root` FROM (SELECT * FROM `Users` WHERE `username` = \'some :data\') AS `_0_root.base`) AS `_1_root`;', }); }); }); diff --git a/test/unit/sql/group.test.js b/test/unit/sql/group.test.js index f3e743e8d278..cfb3fb35a579 100644 --- a/test/unit/sql/group.test.js +++ b/test/unit/sql/group.test.js @@ -35,30 +35,26 @@ describe(Support.getTestDialectTeaser('SQL'), () => { testsql({ model: User, - attributes: ['name'], group: ['name'], }, { - default: 'SELECT `name` FROM `Users` AS `User` GROUP BY `name`;', - postgres: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name";', - db2: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name";', - ibmi: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name"', - mssql: 'SELECT [name] FROM [Users] AS [User] GROUP BY [name];', - snowflake: 'SELECT "name" FROM "Users" AS "User" GROUP BY "name";', - mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT name AS `name` FROM `Users` GROUP BY `name`) AS `_0_root.base`) AS `_1_root`;', + default: 'SELECT * FROM `Users` AS `User` GROUP BY `name`;', + postgres: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + db2: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + ibmi: 'SELECT * FROM "Users" AS "User" GROUP BY "name"', + mssql: 'SELECT * FROM [Users] AS [User] GROUP BY [name];', + snowflake: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', }); testsql({ model: User, - attributes: ['name'], group: [], }, { - default: 'SELECT `name` FROM `Users` AS `User`;', - postgres: 'SELECT "name" FROM "Users" AS "User";', - db2: 'SELECT "name" FROM "Users" AS "User";', - ibmi: 'SELECT "name" FROM "Users" AS "User"', - mssql: 'SELECT [name] FROM [Users] AS [User];', - snowflake: 'SELECT "name" FROM "Users" AS "User";', - mysql: 'SELECT coalesce(JSON_ARRAYAGG(`root`), json_array()) AS `root` FROM (SELECT json_object(\'name\', (SELECT `_0_root.base`.`name` AS `name`)) AS `root` FROM (SELECT * FROM `Users` ) AS `_0_root.base`) AS `_1_root`;', + default: 'SELECT * FROM `Users` AS `User`;', + postgres: 'SELECT * FROM "Users" AS "User";', + db2: 'SELECT * FROM "Users" AS "User";', + ibmi: 'SELECT * FROM "Users" AS "User"', + mssql: 'SELECT * FROM [Users] AS [User];', + snowflake: 'SELECT * FROM "Users" AS "User";', }); }); });