'use strict'; const chai = require('chai'); const expect = chai.expect; const Support = require('../../support'); const { DataTypes } = require('@sequelize/core'); const { _validateIncludedElements, } = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'); const { beforeAll2 } = require('../../support'); const expectsql = Support.expectsql; const current = Support.sequelize; const sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation describe('QueryGenerator#selectQuery with "order"', () => { function expectSelect(options, expectation) { const model = options.model; return expectsql( sql.selectQuery(options.table || (model && model.table), options, options.model), expectation, ); } const vars = beforeAll2(() => { // models const User = Support.sequelize.define( 'User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id', }, name: { type: DataTypes.STRING, field: 'name', allowNull: false, }, createdAt: { field: 'created_at', }, updatedAt: { field: 'updated_at', }, }, { tableName: 'user', timestamps: true, }, ); const Project = Support.sequelize.define( 'Project', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id', }, name: { type: DataTypes.STRING, field: 'name', allowNull: false, }, createdAt: { field: 'created_at', }, updatedAt: { field: 'updated_at', }, }, { tableName: 'project', timestamps: true, }, ); const ProjectUser = Support.sequelize.define( 'ProjectUser', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id', }, userId: { type: DataTypes.INTEGER, field: 'user_id', allowNull: false, }, projectId: { type: DataTypes.INTEGER, field: 'project_id', allowNull: false, }, createdAt: { field: 'created_at', }, updatedAt: { field: 'updated_at', }, }, { tableName: 'project_user', timestamps: true, }, ); const Task = Support.sequelize.define( 'Task', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id', }, name: { type: DataTypes.STRING, field: 'name', allowNull: false, }, projectId: { type: DataTypes.INTEGER, field: 'project_id', allowNull: false, }, createdAt: { field: 'created_at', }, updatedAt: { field: 'updated_at', }, }, { tableName: 'task', timestamps: true, }, ); const Subtask = Support.sequelize.define( 'Subtask', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id', }, name: { type: DataTypes.STRING, field: 'name', allowNull: false, }, taskId: { type: DataTypes.INTEGER, field: 'task_id', allowNull: false, }, createdAt: { field: 'created_at', }, updatedAt: { field: 'updated_at', }, }, { tableName: 'subtask', timestamps: true, }, ); // Relations User.belongsToMany(Project, { as: 'ProjectUserProjects', inverse: { as: 'ProjectUserUsers', }, through: ProjectUser, foreignKey: 'user_id', otherKey: 'project_id', }); Project.belongsToMany(User, { as: 'ProjectUserUsers', inverse: { as: 'ProjectUserProjects', }, through: ProjectUser, foreignKey: 'project_id', otherKey: 'user_id', }); Project.hasMany(Task, { as: 'Tasks', foreignKey: 'project_id', inverse: 'Project', }); Task.belongsTo(Project, { as: 'Project', foreignKey: 'project_id', }); Task.hasMany(Subtask, { as: 'Subtasks', foreignKey: 'task_id', inverse: 'Task', }); Subtask.belongsTo(Task, { as: 'Task', foreignKey: 'task_id', }); return { User, Project, ProjectUser, Task, Subtask }; }); it('supports "order"', () => { const { Project, Subtask, Task } = vars; expectSelect( { model: Subtask, attributes: ['id', 'name', 'createdAt'], include: _validateIncludedElements({ include: [ { association: Subtask.associations.Task, required: true, attributes: ['id', 'name', 'createdAt'], include: [ { association: Task.associations.Project, required: true, attributes: ['id', 'name', 'createdAt'], }, ], }, ], model: Subtask, }).include, order: [ // order with multiple simple association syntax with direction [ { model: Task, as: 'Task', }, { model: Project, as: 'Project', }, 'createdAt', 'ASC', ], // order with multiple simple association syntax without direction [ { model: Task, as: 'Task', }, { model: Project, as: 'Project', }, 'createdAt', ], // order with simple association syntax with direction [ { model: Task, as: 'Task', }, 'createdAt', 'ASC', ], // order with simple association syntax without direction [ { model: Task, as: 'Task', }, 'createdAt', ], // through model object as array with direction [Task, Project, 'createdAt', 'ASC'], // through model object as array without direction [Task, Project, 'createdAt'], // model object as array with direction [Task, 'createdAt', 'ASC'], // model object as array without direction [Task, 'createdAt'], // through association object as array with direction [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'ASC'], // through association object as array without direction [Subtask.associations.Task, Task.associations.Project, 'createdAt'], // association object as array with direction [Subtask.associations.Task, 'createdAt', 'ASC'], // association object as array without direction [Subtask.associations.Task, 'createdAt'], // through association name order as array with direction ['Task', 'Project', 'createdAt', 'ASC'], // through association name as array without direction ['Task', 'Project', 'createdAt'], // association name as array with direction ['Task', 'createdAt', 'ASC'], // association name as array without direction ['Task', 'createdAt'], // main order as array with direction ['createdAt', 'ASC'], // main order as array without direction ['createdAt'], // main order as string 'createdAt', ], }, { default: 'SELECT [Subtask].[id], [Subtask].[name], [Subtask].[createdAt], [Task].[id] AS [Task.id], [Task].[name] AS [Task.name], [Task].[created_at] AS [Task.createdAt], [Task->Project].[id] AS [Task.Project.id], [Task->Project].[name] AS [Task.Project.name], [Task->Project].[created_at] AS [Task.Project.createdAt] FROM [subtask] AS [Subtask] INNER JOIN [task] AS [Task] ON [Subtask].[task_id] = [Task].[id] INNER JOIN [project] AS [Task->Project] ON [Task].[project_id] = [Task->Project].[id] ORDER BY [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Subtask].[created_at] ASC, [Subtask].[created_at], [Subtask].[created_at];', postgres: 'SELECT "Subtask"."id", "Subtask"."name", "Subtask"."createdAt", "Task"."id" AS "Task.id", "Task"."name" AS "Task.name", "Task"."created_at" AS "Task.createdAt", "Task->Project"."id" AS "Task.Project.id", "Task->Project"."name" AS "Task.Project.name", "Task->Project"."created_at" AS "Task.Project.createdAt" FROM "subtask" AS "Subtask" INNER JOIN "task" AS "Task" ON "Subtask"."task_id" = "Task"."id" INNER JOIN "project" AS "Task->Project" ON "Task"."project_id" = "Task->Project"."id" ORDER BY "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Subtask"."created_at" ASC, "Subtask"."created_at", "Subtask"."created_at";', }, ); }); it('supports random ordering', () => { const { Subtask } = vars; expectSelect( { model: Subtask, attributes: ['id', 'name'], order: [Support.sequelize.random()], }, { ibmi: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RAND()', mssql: 'SELECT [id], [name] FROM [subtask] AS [Subtask] ORDER BY RAND();', db2: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RAND();', mariadb: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();', mysql: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();', postgres: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();', snowflake: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();', sqlite3: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RANDOM();', }, ); }); describe('Invalid', () => { it('Error on invalid association', () => { const { Project, Subtask } = vars; return expect( Subtask.findAll({ order: [[Project, 'createdAt', 'ASC']], }), ).to.eventually.be.rejectedWith( Error, 'Invalid Include received: no associations exist between "Subtask" and "Project"', ); }); it('Error on invalid structure', () => { const { Subtask, Task } = vars; return expect( Subtask.findAll({ order: [[Subtask.associations.Task, 'createdAt', Task.associations.Project, 'ASC']], }), ).to.eventually.be.rejectedWith(Error, 'Unknown structure passed to order / group: Project'); }); it('Error when the order is a string', () => { const { Subtask } = vars; return expect( Subtask.findAll({ order: 'i am a silly string', }), ).to.eventually.be.rejectedWith( Error, 'Order must be type of array or instance of a valid sequelize method.', ); }); it('Error when the order contains a `{raw: "..."}` object', () => { const { Subtask } = vars; return expect( Subtask.findAll({ order: [ { raw: 'this should throw an error', }, ], }), ).to.eventually.be.rejectedWith( Error, 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.', ); }); it('Error when the order contains a `{raw: "..."}` object wrapped in an array', () => { const { Subtask } = vars; return expect( Subtask.findAll({ order: [ [ { raw: 'this should throw an error', }, ], ], }), ).to.eventually.be.rejectedWith( Error, 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.', ); }); }); });