generateJoin.test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. 'use strict';
  2. const at = require('lodash/at');
  3. const { beforeAll2, expectsql, sequelize } = require('../../support');
  4. const { DataTypes, Model, Op } = require('@sequelize/core');
  5. const {
  6. _validateIncludedElements,
  7. } = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js');
  8. const sql = sequelize.queryGenerator;
  9. describe('QueryGenerator#generateJoin', () => {
  10. const expectJoin = function (path, options, expectation) {
  11. Model._conformIncludes(options, options.model);
  12. options = _validateIncludedElements(options);
  13. const include = at(options, path)[0];
  14. const join = sql.generateJoin(include, {
  15. options,
  16. subQuery:
  17. options.subQuery === undefined
  18. ? options.limit && options.hasMultiAssociation
  19. : options.subQuery,
  20. });
  21. return expectsql(`${join.join} ${join.body} ON ${join.condition}`, expectation);
  22. };
  23. const vars = beforeAll2(() => {
  24. const User = sequelize.define(
  25. 'User',
  26. {
  27. id: {
  28. type: DataTypes.INTEGER,
  29. primaryKey: true,
  30. autoIncrement: true,
  31. field: 'id_user',
  32. },
  33. companyId: {
  34. type: DataTypes.INTEGER,
  35. field: 'company_id',
  36. },
  37. },
  38. {
  39. tableName: 'user',
  40. },
  41. );
  42. const Task = sequelize.define(
  43. 'Task',
  44. {
  45. title: DataTypes.STRING,
  46. userId: {
  47. type: DataTypes.INTEGER,
  48. field: 'user_id',
  49. },
  50. },
  51. {
  52. tableName: 'task',
  53. },
  54. );
  55. const Company = sequelize.define(
  56. 'Company',
  57. {
  58. name: DataTypes.STRING,
  59. ownerId: {
  60. type: DataTypes.INTEGER,
  61. field: 'owner_id',
  62. },
  63. public: {
  64. type: DataTypes.BOOLEAN,
  65. },
  66. },
  67. {
  68. tableName: 'company',
  69. },
  70. );
  71. const Profession = sequelize.define(
  72. 'Profession',
  73. {
  74. name: DataTypes.STRING,
  75. },
  76. {
  77. tableName: 'profession',
  78. },
  79. );
  80. User.Tasks = User.hasMany(Task, { as: 'Tasks', foreignKey: 'userId', inverse: 'User' });
  81. User.Company = User.belongsTo(Company, { as: 'Company', foreignKey: 'companyId' });
  82. User.Profession = User.belongsTo(Profession, { as: 'Profession', foreignKey: 'professionId' });
  83. Profession.Professionals = Profession.hasMany(User, {
  84. as: 'Professionals',
  85. foreignKey: 'professionId',
  86. inverse: 'Profession',
  87. });
  88. Company.Employees = Company.hasMany(User, {
  89. as: 'Employees',
  90. foreignKey: 'companyId',
  91. inverse: 'Company',
  92. });
  93. Company.Owner = Company.belongsTo(User, { as: 'Owner', foreignKey: 'ownerId' });
  94. return { User, Task, Company, Profession };
  95. });
  96. /*
  97. * BelongsTo
  98. */
  99. it('Generates a join query for a belongsTo association', () => {
  100. const { User } = vars;
  101. expectJoin(
  102. 'include[0]',
  103. {
  104. model: User,
  105. include: [User.Company],
  106. },
  107. {
  108. default: 'LEFT OUTER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id]',
  109. },
  110. );
  111. });
  112. it('Generates a belongsTo join query with an extra OR "on" condition', () => {
  113. const { User } = vars;
  114. expectJoin(
  115. 'include[0]',
  116. {
  117. model: User,
  118. include: [
  119. {
  120. association: User.Company,
  121. where: { public: true },
  122. or: true,
  123. },
  124. ],
  125. },
  126. {
  127. default:
  128. 'INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = true',
  129. ibmi: 'INNER JOIN "company" AS "Company" ON "User"."company_id" = "Company"."id" OR "Company"."public" = 1',
  130. sqlite3:
  131. 'INNER JOIN `company` AS `Company` ON `User`.`company_id` = `Company`.`id` OR `Company`.`public` = 1',
  132. mssql:
  133. 'INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = 1',
  134. },
  135. );
  136. });
  137. it('Generates a nested belongsTo join query', () => {
  138. const { Profession, User } = vars;
  139. expectJoin(
  140. 'include[0].include[0]',
  141. {
  142. model: Profession,
  143. include: [
  144. {
  145. association: Profession.Professionals,
  146. limit: 3,
  147. include: [User.Company],
  148. },
  149. ],
  150. },
  151. {
  152. default:
  153. 'LEFT OUTER JOIN [company] AS [Professionals->Company] ON [Professionals].[company_id] = [Professionals->Company].[id]',
  154. },
  155. );
  156. });
  157. it('supports subQuery = true', () => {
  158. const { User } = vars;
  159. expectJoin(
  160. 'include[0]',
  161. {
  162. model: User,
  163. subQuery: true,
  164. include: [User.Company],
  165. },
  166. {
  167. default: 'LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]',
  168. },
  169. );
  170. });
  171. it('supports subQuery = true with required = false and nested WHERE', () => {
  172. const { User } = vars;
  173. expectJoin(
  174. 'include[0]',
  175. {
  176. model: User,
  177. subQuery: true,
  178. include: [
  179. {
  180. association: User.Company,
  181. required: false,
  182. where: { name: 'ABC' },
  183. },
  184. ],
  185. },
  186. {
  187. default:
  188. "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id] AND [Company].[name] = 'ABC'",
  189. mssql:
  190. "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id] AND [Company].[name] = N'ABC'",
  191. },
  192. );
  193. });
  194. it('supports "right = true"', () => {
  195. const { User } = vars;
  196. expectJoin(
  197. 'include[0]',
  198. {
  199. model: User,
  200. subQuery: true,
  201. include: [
  202. {
  203. association: User.Company,
  204. right: true,
  205. },
  206. ],
  207. },
  208. {
  209. default: `${sequelize.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]`,
  210. },
  211. );
  212. });
  213. it('supports nested includes with subQuery = true', () => {
  214. const { Company, User } = vars;
  215. expectJoin(
  216. 'include[0].include[0]',
  217. {
  218. subQuery: true,
  219. model: User,
  220. include: [
  221. {
  222. association: User.Company,
  223. include: [Company.Owner],
  224. },
  225. ],
  226. },
  227. {
  228. default:
  229. 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user]',
  230. },
  231. );
  232. });
  233. it('supports double nested includes', () => {
  234. const { Company, User } = vars;
  235. expectJoin(
  236. 'include[0].include[0].include[0]',
  237. {
  238. model: User,
  239. subQuery: true,
  240. include: [
  241. {
  242. association: User.Company,
  243. include: [
  244. {
  245. association: Company.Owner,
  246. include: [User.Profession],
  247. },
  248. ],
  249. },
  250. ],
  251. },
  252. {
  253. default:
  254. 'LEFT OUTER JOIN [profession] AS [Company->Owner->Profession] ON [Company->Owner].[professionId] = [Company->Owner->Profession].[id]',
  255. },
  256. );
  257. });
  258. it('supports nested includes with required = true', () => {
  259. const { Company, User } = vars;
  260. expectJoin(
  261. 'include[0].include[0]',
  262. {
  263. model: User,
  264. subQuery: true,
  265. include: [
  266. {
  267. association: User.Company,
  268. required: true,
  269. include: [Company.Owner],
  270. },
  271. ],
  272. },
  273. {
  274. default:
  275. 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user]',
  276. },
  277. );
  278. });
  279. it('supports required = true', () => {
  280. const { User } = vars;
  281. expectJoin(
  282. 'include[0]',
  283. {
  284. model: User,
  285. subQuery: true,
  286. include: [{ association: User.Company, required: true }],
  287. },
  288. {
  289. default: 'INNER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]',
  290. },
  291. );
  292. });
  293. // /*
  294. // * HasMany
  295. // */
  296. it('supports hasMany', () => {
  297. const { User } = vars;
  298. expectJoin(
  299. 'include[0]',
  300. {
  301. model: User,
  302. include: [User.Tasks],
  303. },
  304. { default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id_user] = [Tasks].[user_id]' },
  305. );
  306. });
  307. it('supports hasMany with subQuery = true', () => {
  308. const { User } = vars;
  309. expectJoin(
  310. 'include[0]',
  311. {
  312. model: User,
  313. subQuery: true,
  314. include: [User.Tasks],
  315. },
  316. {
  317. // The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of
  318. default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]',
  319. },
  320. );
  321. });
  322. it('supports hasMany with "on" condition', () => {
  323. const { User } = vars;
  324. expectJoin(
  325. 'include[0]',
  326. {
  327. model: User,
  328. include: [
  329. {
  330. association: User.Tasks,
  331. on: {
  332. [Op.or]: [
  333. { '$User.id_user$': { [Op.col]: 'Tasks.user_id' } },
  334. { '$Tasks.user_id$': 2 },
  335. ],
  336. },
  337. },
  338. ],
  339. },
  340. {
  341. default:
  342. 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id_user] = [Tasks].[user_id] OR [Tasks].[user_id] = 2',
  343. },
  344. );
  345. });
  346. it('supports hasMany with "on" condition (2)', () => {
  347. const { User } = vars;
  348. expectJoin(
  349. 'include[0]',
  350. {
  351. model: User,
  352. include: [
  353. {
  354. association: User.Tasks,
  355. on: { user_id: { [Op.col]: 'User.alternative_id' } },
  356. },
  357. ],
  358. },
  359. {
  360. default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [Tasks].[user_id] = [User].[alternative_id]',
  361. },
  362. );
  363. });
  364. it('supports nested hasMany', () => {
  365. const { Company, User } = vars;
  366. expectJoin(
  367. 'include[0].include[0]',
  368. {
  369. subQuery: true,
  370. model: User,
  371. include: [
  372. {
  373. association: User.Company,
  374. include: [
  375. {
  376. association: Company.Owner,
  377. on: {
  378. [Op.or]: [
  379. { '$Company.owner_id$': { [Op.col]: 'Company.Owner.id_user' } },
  380. { '$Company.Owner.id_user$': 2 },
  381. ],
  382. },
  383. },
  384. ],
  385. },
  386. ],
  387. },
  388. {
  389. default:
  390. 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user] OR [Company->Owner].[id_user] = 2',
  391. },
  392. );
  393. });
  394. });