order.test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const Support = require('../../support');
  5. const { DataTypes } = require('@sequelize/core');
  6. const {
  7. _validateIncludedElements,
  8. } = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js');
  9. const { beforeAll2 } = require('../../support');
  10. const expectsql = Support.expectsql;
  11. const current = Support.sequelize;
  12. const sql = current.dialect.queryGenerator;
  13. // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation
  14. describe('QueryGenerator#selectQuery with "order"', () => {
  15. function expectSelect(options, expectation) {
  16. const model = options.model;
  17. return expectsql(
  18. sql.selectQuery(options.table || (model && model.table), options, options.model),
  19. expectation,
  20. );
  21. }
  22. const vars = beforeAll2(() => {
  23. // models
  24. const User = Support.sequelize.define(
  25. 'User',
  26. {
  27. id: {
  28. type: DataTypes.INTEGER,
  29. primaryKey: true,
  30. autoIncrement: true,
  31. field: 'id',
  32. },
  33. name: {
  34. type: DataTypes.STRING,
  35. field: 'name',
  36. allowNull: false,
  37. },
  38. createdAt: {
  39. field: 'created_at',
  40. },
  41. updatedAt: {
  42. field: 'updated_at',
  43. },
  44. },
  45. {
  46. tableName: 'user',
  47. timestamps: true,
  48. },
  49. );
  50. const Project = Support.sequelize.define(
  51. 'Project',
  52. {
  53. id: {
  54. type: DataTypes.INTEGER,
  55. primaryKey: true,
  56. autoIncrement: true,
  57. field: 'id',
  58. },
  59. name: {
  60. type: DataTypes.STRING,
  61. field: 'name',
  62. allowNull: false,
  63. },
  64. createdAt: {
  65. field: 'created_at',
  66. },
  67. updatedAt: {
  68. field: 'updated_at',
  69. },
  70. },
  71. {
  72. tableName: 'project',
  73. timestamps: true,
  74. },
  75. );
  76. const ProjectUser = Support.sequelize.define(
  77. 'ProjectUser',
  78. {
  79. id: {
  80. type: DataTypes.INTEGER,
  81. primaryKey: true,
  82. autoIncrement: true,
  83. field: 'id',
  84. },
  85. userId: {
  86. type: DataTypes.INTEGER,
  87. field: 'user_id',
  88. allowNull: false,
  89. },
  90. projectId: {
  91. type: DataTypes.INTEGER,
  92. field: 'project_id',
  93. allowNull: false,
  94. },
  95. createdAt: {
  96. field: 'created_at',
  97. },
  98. updatedAt: {
  99. field: 'updated_at',
  100. },
  101. },
  102. {
  103. tableName: 'project_user',
  104. timestamps: true,
  105. },
  106. );
  107. const Task = Support.sequelize.define(
  108. 'Task',
  109. {
  110. id: {
  111. type: DataTypes.INTEGER,
  112. primaryKey: true,
  113. autoIncrement: true,
  114. field: 'id',
  115. },
  116. name: {
  117. type: DataTypes.STRING,
  118. field: 'name',
  119. allowNull: false,
  120. },
  121. projectId: {
  122. type: DataTypes.INTEGER,
  123. field: 'project_id',
  124. allowNull: false,
  125. },
  126. createdAt: {
  127. field: 'created_at',
  128. },
  129. updatedAt: {
  130. field: 'updated_at',
  131. },
  132. },
  133. {
  134. tableName: 'task',
  135. timestamps: true,
  136. },
  137. );
  138. const Subtask = Support.sequelize.define(
  139. 'Subtask',
  140. {
  141. id: {
  142. type: DataTypes.INTEGER,
  143. primaryKey: true,
  144. autoIncrement: true,
  145. field: 'id',
  146. },
  147. name: {
  148. type: DataTypes.STRING,
  149. field: 'name',
  150. allowNull: false,
  151. },
  152. taskId: {
  153. type: DataTypes.INTEGER,
  154. field: 'task_id',
  155. allowNull: false,
  156. },
  157. createdAt: {
  158. field: 'created_at',
  159. },
  160. updatedAt: {
  161. field: 'updated_at',
  162. },
  163. },
  164. {
  165. tableName: 'subtask',
  166. timestamps: true,
  167. },
  168. );
  169. // Relations
  170. User.belongsToMany(Project, {
  171. as: 'ProjectUserProjects',
  172. inverse: {
  173. as: 'ProjectUserUsers',
  174. },
  175. through: ProjectUser,
  176. foreignKey: 'user_id',
  177. otherKey: 'project_id',
  178. });
  179. Project.belongsToMany(User, {
  180. as: 'ProjectUserUsers',
  181. inverse: {
  182. as: 'ProjectUserProjects',
  183. },
  184. through: ProjectUser,
  185. foreignKey: 'project_id',
  186. otherKey: 'user_id',
  187. });
  188. Project.hasMany(Task, {
  189. as: 'Tasks',
  190. foreignKey: 'project_id',
  191. inverse: 'Project',
  192. });
  193. Task.belongsTo(Project, {
  194. as: 'Project',
  195. foreignKey: 'project_id',
  196. });
  197. Task.hasMany(Subtask, {
  198. as: 'Subtasks',
  199. foreignKey: 'task_id',
  200. inverse: 'Task',
  201. });
  202. Subtask.belongsTo(Task, {
  203. as: 'Task',
  204. foreignKey: 'task_id',
  205. });
  206. return { User, Project, ProjectUser, Task, Subtask };
  207. });
  208. it('supports "order"', () => {
  209. const { Project, Subtask, Task } = vars;
  210. expectSelect(
  211. {
  212. model: Subtask,
  213. attributes: ['id', 'name', 'createdAt'],
  214. include: _validateIncludedElements({
  215. include: [
  216. {
  217. association: Subtask.associations.Task,
  218. required: true,
  219. attributes: ['id', 'name', 'createdAt'],
  220. include: [
  221. {
  222. association: Task.associations.Project,
  223. required: true,
  224. attributes: ['id', 'name', 'createdAt'],
  225. },
  226. ],
  227. },
  228. ],
  229. model: Subtask,
  230. }).include,
  231. order: [
  232. // order with multiple simple association syntax with direction
  233. [
  234. {
  235. model: Task,
  236. as: 'Task',
  237. },
  238. {
  239. model: Project,
  240. as: 'Project',
  241. },
  242. 'createdAt',
  243. 'ASC',
  244. ],
  245. // order with multiple simple association syntax without direction
  246. [
  247. {
  248. model: Task,
  249. as: 'Task',
  250. },
  251. {
  252. model: Project,
  253. as: 'Project',
  254. },
  255. 'createdAt',
  256. ],
  257. // order with simple association syntax with direction
  258. [
  259. {
  260. model: Task,
  261. as: 'Task',
  262. },
  263. 'createdAt',
  264. 'ASC',
  265. ],
  266. // order with simple association syntax without direction
  267. [
  268. {
  269. model: Task,
  270. as: 'Task',
  271. },
  272. 'createdAt',
  273. ],
  274. // through model object as array with direction
  275. [Task, Project, 'createdAt', 'ASC'],
  276. // through model object as array without direction
  277. [Task, Project, 'createdAt'],
  278. // model object as array with direction
  279. [Task, 'createdAt', 'ASC'],
  280. // model object as array without direction
  281. [Task, 'createdAt'],
  282. // through association object as array with direction
  283. [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'ASC'],
  284. // through association object as array without direction
  285. [Subtask.associations.Task, Task.associations.Project, 'createdAt'],
  286. // association object as array with direction
  287. [Subtask.associations.Task, 'createdAt', 'ASC'],
  288. // association object as array without direction
  289. [Subtask.associations.Task, 'createdAt'],
  290. // through association name order as array with direction
  291. ['Task', 'Project', 'createdAt', 'ASC'],
  292. // through association name as array without direction
  293. ['Task', 'Project', 'createdAt'],
  294. // association name as array with direction
  295. ['Task', 'createdAt', 'ASC'],
  296. // association name as array without direction
  297. ['Task', 'createdAt'],
  298. // main order as array with direction
  299. ['createdAt', 'ASC'],
  300. // main order as array without direction
  301. ['createdAt'],
  302. // main order as string
  303. 'createdAt',
  304. ],
  305. },
  306. {
  307. default:
  308. '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];',
  309. postgres:
  310. '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";',
  311. },
  312. );
  313. });
  314. it('supports random ordering', () => {
  315. const { Subtask } = vars;
  316. expectSelect(
  317. {
  318. model: Subtask,
  319. attributes: ['id', 'name'],
  320. order: [Support.sequelize.random()],
  321. },
  322. {
  323. ibmi: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RAND()',
  324. mssql: 'SELECT [id], [name] FROM [subtask] AS [Subtask] ORDER BY RAND();',
  325. db2: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RAND();',
  326. mariadb: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();',
  327. mysql: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();',
  328. postgres: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();',
  329. snowflake: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();',
  330. sqlite3: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RANDOM();',
  331. },
  332. );
  333. });
  334. describe('Invalid', () => {
  335. it('Error on invalid association', () => {
  336. const { Project, Subtask } = vars;
  337. return expect(
  338. Subtask.findAll({
  339. order: [[Project, 'createdAt', 'ASC']],
  340. }),
  341. ).to.eventually.be.rejectedWith(
  342. Error,
  343. 'Invalid Include received: no associations exist between "Subtask" and "Project"',
  344. );
  345. });
  346. it('Error on invalid structure', () => {
  347. const { Subtask, Task } = vars;
  348. return expect(
  349. Subtask.findAll({
  350. order: [[Subtask.associations.Task, 'createdAt', Task.associations.Project, 'ASC']],
  351. }),
  352. ).to.eventually.be.rejectedWith(Error, 'Unknown structure passed to order / group: Project');
  353. });
  354. it('Error when the order is a string', () => {
  355. const { Subtask } = vars;
  356. return expect(
  357. Subtask.findAll({
  358. order: 'i am a silly string',
  359. }),
  360. ).to.eventually.be.rejectedWith(
  361. Error,
  362. 'Order must be type of array or instance of a valid sequelize method.',
  363. );
  364. });
  365. it('Error when the order contains a `{raw: "..."}` object', () => {
  366. const { Subtask } = vars;
  367. return expect(
  368. Subtask.findAll({
  369. order: [
  370. {
  371. raw: 'this should throw an error',
  372. },
  373. ],
  374. }),
  375. ).to.eventually.be.rejectedWith(
  376. Error,
  377. 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.',
  378. );
  379. });
  380. it('Error when the order contains a `{raw: "..."}` object wrapped in an array', () => {
  381. const { Subtask } = vars;
  382. return expect(
  383. Subtask.findAll({
  384. order: [
  385. [
  386. {
  387. raw: 'this should throw an error',
  388. },
  389. ],
  390. ],
  391. }),
  392. ).to.eventually.be.rejectedWith(
  393. Error,
  394. 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.',
  395. );
  396. });
  397. });
  398. });