belongs-to.test.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. 'use strict';
  2. const chai = require('chai');
  3. const sinon = require('sinon');
  4. const expect = chai.expect;
  5. const Support = require('../support');
  6. const { DataTypes, Sequelize } = require('@sequelize/core');
  7. const assert = require('node:assert');
  8. const current = Support.sequelize;
  9. const dialect = Support.getTestDialect();
  10. describe(Support.getTestDialectTeaser('BelongsTo'), () => {
  11. describe('Model.associations', () => {
  12. it('should store all associations when associating to the same table multiple times', function () {
  13. const User = this.sequelize.define('User', {});
  14. const Group = this.sequelize.define('Group', {});
  15. Group.belongsTo(User);
  16. Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' });
  17. Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' });
  18. expect(Object.keys(Group.associations)).to.deep.equal([
  19. 'user',
  20. 'primaryUsers',
  21. 'secondaryUsers',
  22. ]);
  23. });
  24. });
  25. describe('get', () => {
  26. describe('multiple', () => {
  27. it('should fetch associations for multiple instances', async function () {
  28. const User = this.sequelize.define('User', {});
  29. const Task = this.sequelize.define('Task', {});
  30. Task.User = Task.belongsTo(User, { as: 'user' });
  31. await this.sequelize.sync({ force: true });
  32. const tasks = await Promise.all([
  33. Task.create(
  34. {
  35. id: 1,
  36. user: { id: 1 },
  37. },
  38. {
  39. include: [Task.User],
  40. },
  41. ),
  42. Task.create(
  43. {
  44. id: 2,
  45. user: { id: 2 },
  46. },
  47. {
  48. include: [Task.User],
  49. },
  50. ),
  51. Task.create({
  52. id: 3,
  53. }),
  54. ]);
  55. const result = await Task.User.get(tasks);
  56. expect(result.get(tasks[0].id).id).to.equal(tasks[0].user.id);
  57. expect(result.get(tasks[1].id).id).to.equal(tasks[1].user.id);
  58. expect(result.get(tasks[2].id)).to.be.undefined;
  59. });
  60. });
  61. });
  62. describe('getAssociation', () => {
  63. if (current.dialect.supports.transactions) {
  64. it('supports transactions', async function () {
  65. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  66. this.sequelize,
  67. );
  68. const User = sequelize.define('User', { username: DataTypes.STRING });
  69. const Group = sequelize.define('Group', { name: DataTypes.STRING });
  70. Group.belongsTo(User);
  71. await sequelize.sync({ force: true });
  72. const user = await User.create({ username: 'foo' });
  73. const group = await Group.create({ name: 'bar' });
  74. const t = await sequelize.startUnmanagedTransaction();
  75. await group.setUser(user, { transaction: t });
  76. const groups = await Group.findAll();
  77. const associatedUser = await groups[0].getUser();
  78. expect(associatedUser).to.be.null;
  79. const groups0 = await Group.findAll({ transaction: t });
  80. const associatedUser0 = await groups0[0].getUser({ transaction: t });
  81. expect(associatedUser0).to.be.not.null;
  82. await t.rollback();
  83. });
  84. }
  85. it("should be able to handle a where object that's a first class citizen.", async function () {
  86. const User = this.sequelize.define('UserXYZ', {
  87. username: DataTypes.STRING,
  88. gender: DataTypes.STRING,
  89. });
  90. const Task = this.sequelize.define('TaskXYZ', {
  91. title: DataTypes.STRING,
  92. status: DataTypes.STRING,
  93. });
  94. Task.belongsTo(User);
  95. await User.sync({ force: true });
  96. // Can't use Promise.all cause of foreign key references
  97. await Task.sync({ force: true });
  98. const [userA, , task] = await Promise.all([
  99. User.create({ username: 'foo', gender: 'male' }),
  100. User.create({ username: 'bar', gender: 'female' }),
  101. Task.create({ title: 'task', status: 'inactive' }),
  102. ]);
  103. await task.setUserXYZ(userA);
  104. const user = await task.getUserXYZ({ where: { gender: 'female' } });
  105. expect(user).to.be.null;
  106. });
  107. if (current.dialect.supports.schemas) {
  108. it('supports schemas', async function () {
  109. const User = this.sequelize
  110. .define('UserXYZ', { username: DataTypes.STRING, gender: DataTypes.STRING })
  111. .withSchema('archive');
  112. const Task = this.sequelize
  113. .define('TaskXYZ', { title: DataTypes.STRING, status: DataTypes.STRING })
  114. .withSchema('archive');
  115. Task.belongsTo(User);
  116. await this.sequelize.createSchema('archive');
  117. await User.sync({ force: true });
  118. await Task.sync({ force: true });
  119. const [user0, task] = await Promise.all([
  120. User.create({ username: 'foo', gender: 'male' }),
  121. Task.create({ title: 'task', status: 'inactive' }),
  122. ]);
  123. await task.setUserXYZ(user0);
  124. const user = await task.getUserXYZ();
  125. expect(user).to.be.ok;
  126. await this.sequelize.queryInterface.dropAllTables({ schema: 'archive' });
  127. await this.sequelize.dropSchema('archive');
  128. const schemas = await this.sequelize.queryInterface.listSchemas();
  129. expect(schemas).to.not.include('archive');
  130. });
  131. it('supports schemas when defining custom foreign key attribute #9029', async function () {
  132. const User = this.sequelize
  133. .define('UserXYZ', {
  134. uid: {
  135. type: DataTypes.INTEGER,
  136. primaryKey: true,
  137. autoIncrement: true,
  138. allowNull: false,
  139. },
  140. })
  141. .withSchema('archive');
  142. const Task = this.sequelize
  143. .define('TaskXYZ', {
  144. user_id: {
  145. type: DataTypes.INTEGER,
  146. references: { model: User, key: 'uid' },
  147. },
  148. })
  149. .withSchema('archive');
  150. Task.belongsTo(User, { foreignKey: 'user_id' });
  151. await this.sequelize.createSchema('archive');
  152. await User.sync({ force: true });
  153. await Task.sync({ force: true });
  154. const user0 = await User.create({});
  155. const task = await Task.create({});
  156. await task.setUserXYZ(user0);
  157. const user = await task.getUserXYZ();
  158. expect(user).to.be.ok;
  159. });
  160. }
  161. });
  162. describe('setAssociation', () => {
  163. if (current.dialect.supports.transactions) {
  164. it('supports transactions', async function () {
  165. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  166. this.sequelize,
  167. );
  168. const User = sequelize.define('User', { username: DataTypes.STRING });
  169. const Group = sequelize.define('Group', { name: DataTypes.STRING });
  170. Group.belongsTo(User);
  171. await sequelize.sync({ force: true });
  172. const user = await User.create({ username: 'foo' });
  173. const group = await Group.create({ name: 'bar' });
  174. const t = await sequelize.startUnmanagedTransaction();
  175. await group.setUser(user, { transaction: t });
  176. const groups = await Group.findAll();
  177. const associatedUser = await groups[0].getUser();
  178. expect(associatedUser).to.be.null;
  179. await t.rollback();
  180. });
  181. }
  182. it('can set the association with declared primary keys...', async function () {
  183. const User = this.sequelize.define('UserXYZ', {
  184. user_id: { type: DataTypes.INTEGER, primaryKey: true },
  185. username: DataTypes.STRING,
  186. });
  187. const Task = this.sequelize.define('TaskXYZ', {
  188. task_id: { type: DataTypes.INTEGER, primaryKey: true },
  189. title: DataTypes.STRING,
  190. });
  191. Task.belongsTo(User, { foreignKey: 'user_id' });
  192. await this.sequelize.sync({ force: true });
  193. const user = await User.create({ user_id: 1, username: 'foo' });
  194. const task = await Task.create({ task_id: 1, title: 'task' });
  195. await task.setUserXYZ(user);
  196. const user1 = await task.getUserXYZ();
  197. expect(user1).not.to.be.null;
  198. await task.setUserXYZ(null);
  199. const user0 = await task.getUserXYZ();
  200. expect(user0).to.be.null;
  201. });
  202. it('clears the association if null is passed', async function () {
  203. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  204. const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
  205. Task.belongsTo(User);
  206. await this.sequelize.sync({ force: true });
  207. const user = await User.create({ username: 'foo' });
  208. const task = await Task.create({ title: 'task' });
  209. await task.setUserXYZ(user);
  210. const user1 = await task.getUserXYZ();
  211. expect(user1).not.to.be.null;
  212. await task.setUserXYZ(null);
  213. const user0 = await task.getUserXYZ();
  214. expect(user0).to.be.null;
  215. });
  216. it('should throw a ForeignKeyConstraintError if the associated record does not exist', async function () {
  217. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  218. const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
  219. Task.belongsTo(User);
  220. await this.sequelize.sync({ force: true });
  221. await expect(Task.create({ title: 'task', userXYZId: 5 })).to.be.rejectedWith(
  222. Sequelize.ForeignKeyConstraintError,
  223. );
  224. const task = await Task.create({ title: 'task' });
  225. await expect(
  226. Task.update({ title: 'taskUpdate', userXYZId: 5 }, { where: { id: task.id } }),
  227. ).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
  228. });
  229. it('supports passing the primary key instead of an object', async function () {
  230. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  231. const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
  232. Task.belongsTo(User);
  233. await this.sequelize.sync({ force: true });
  234. const user = await User.create({ id: 15, username: 'jansemand' });
  235. const task = await Task.create({});
  236. await task.setUserXYZ(user.id);
  237. const user0 = await task.getUserXYZ();
  238. expect(user0.username).to.equal('jansemand');
  239. });
  240. it('should support logging', async function () {
  241. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  242. const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
  243. const spy = sinon.spy();
  244. Task.belongsTo(User);
  245. await this.sequelize.sync({ force: true });
  246. const user = await User.create();
  247. const task = await Task.create({});
  248. await task.setUserXYZ(user, { logging: spy });
  249. expect(spy.called).to.be.ok;
  250. });
  251. it('should not clobber atributes', async function () {
  252. const Comment = this.sequelize.define('comment', {
  253. text: DataTypes.STRING,
  254. });
  255. const Post = this.sequelize.define('post', {
  256. title: DataTypes.STRING,
  257. });
  258. Post.hasOne(Comment);
  259. Comment.belongsTo(Post);
  260. await this.sequelize.sync();
  261. const post = await Post.create({
  262. title: 'Post title',
  263. });
  264. const comment = await Comment.create({
  265. text: 'OLD VALUE',
  266. });
  267. comment.text = 'UPDATED VALUE';
  268. await comment.setPost(post);
  269. expect(comment.text).to.equal('UPDATED VALUE');
  270. });
  271. it('should set the foreign key value without saving when using save: false', async function () {
  272. const Comment = this.sequelize.define('comment', {
  273. text: DataTypes.STRING,
  274. });
  275. const Post = this.sequelize.define('post', {
  276. title: DataTypes.STRING,
  277. });
  278. Post.hasMany(Comment, { foreignKey: 'post_id' });
  279. Comment.belongsTo(Post, { foreignKey: 'post_id' });
  280. await this.sequelize.sync({ force: true });
  281. const [post, comment] = await Promise.all([Post.create(), Comment.create()]);
  282. expect(comment.get('post_id')).not.to.be.ok;
  283. const setter = await comment.setPost(post, { save: false });
  284. expect(setter).to.be.undefined;
  285. expect(comment.get('post_id')).to.equal(post.get('id'));
  286. expect(comment.changed('post_id')).to.be.true;
  287. });
  288. it('supports setting same association twice', async function () {
  289. const Home = this.sequelize.define('home', {});
  290. const User = this.sequelize.define('user');
  291. Home.belongsTo(User);
  292. await this.sequelize.sync({ force: true });
  293. const [home, user] = await Promise.all([Home.create(), User.create()]);
  294. await home.setUser(user);
  295. expect(await home.getUser()).to.have.property('id', user.id);
  296. });
  297. });
  298. describe('createAssociation', () => {
  299. it('creates an associated model instance', async function () {
  300. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  301. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  302. Task.belongsTo(User);
  303. await this.sequelize.sync({ force: true });
  304. const task = await Task.create({ title: 'task' });
  305. const user = await task.createUser({ username: 'bob' });
  306. expect(user).not.to.be.null;
  307. expect(user.username).to.equal('bob');
  308. });
  309. if (current.dialect.supports.transactions) {
  310. it('supports transactions', async function () {
  311. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  312. this.sequelize,
  313. );
  314. const User = sequelize.define('User', { username: DataTypes.STRING });
  315. const Group = sequelize.define('Group', { name: DataTypes.STRING });
  316. Group.belongsTo(User);
  317. await sequelize.sync({ force: true });
  318. const group = await Group.create({ name: 'bar' });
  319. const t = await sequelize.startUnmanagedTransaction();
  320. await group.createUser({ username: 'foo' }, { transaction: t });
  321. const user = await group.getUser();
  322. expect(user).to.be.null;
  323. const user0 = await group.getUser({ transaction: t });
  324. expect(user0).not.to.be.null;
  325. await t.rollback();
  326. });
  327. }
  328. });
  329. describe('foreign key', () => {
  330. it('should setup underscored field with foreign keys when using underscored', function () {
  331. const User = this.sequelize.define(
  332. 'User',
  333. { username: DataTypes.STRING },
  334. { underscored: true },
  335. );
  336. const Account = this.sequelize.define(
  337. 'Account',
  338. { name: DataTypes.STRING },
  339. { underscored: true },
  340. );
  341. User.belongsTo(Account);
  342. expect(User.getAttributes().accountId).to.exist;
  343. expect(User.getAttributes().accountId.field).to.equal('account_id');
  344. });
  345. it('should use model name when using camelcase', function () {
  346. const User = this.sequelize.define(
  347. 'User',
  348. { username: DataTypes.STRING },
  349. { underscored: false },
  350. );
  351. const Account = this.sequelize.define(
  352. 'Account',
  353. { name: DataTypes.STRING },
  354. { underscored: false },
  355. );
  356. User.belongsTo(Account);
  357. expect(User.getAttributes().accountId).to.exist;
  358. expect(User.getAttributes().accountId.field).to.equal('accountId');
  359. });
  360. it('should support specifying the field of a foreign key', async function () {
  361. const User = this.sequelize.define(
  362. 'User',
  363. { username: DataTypes.STRING },
  364. { underscored: false },
  365. );
  366. const Account = this.sequelize.define(
  367. 'Account',
  368. { title: DataTypes.STRING },
  369. { underscored: false },
  370. );
  371. User.belongsTo(Account, {
  372. foreignKey: {
  373. name: 'AccountId',
  374. field: 'account_id',
  375. },
  376. });
  377. expect(User.getAttributes().AccountId).to.exist;
  378. expect(User.getAttributes().AccountId.field).to.equal('account_id');
  379. await Account.sync({ force: true });
  380. // Can't use Promise.all cause of foreign key references
  381. await User.sync({ force: true });
  382. const [user1, account] = await Promise.all([
  383. User.create({ username: 'foo' }),
  384. Account.create({ title: 'pepsico' }),
  385. ]);
  386. await user1.setAccount(account);
  387. const user0 = await user1.getAccount();
  388. expect(user0).to.not.be.null;
  389. const user = await User.findOne({
  390. where: { username: 'foo' },
  391. include: [Account],
  392. });
  393. // the sql query should correctly look at account_id instead of AccountId
  394. expect(user.account).to.exist;
  395. });
  396. it('should set foreignKey on foreign table', async function () {
  397. const Mail = this.sequelize.define('mail', {}, { timestamps: false });
  398. const Entry = this.sequelize.define('entry', {}, { timestamps: false });
  399. const User = this.sequelize.define('user', {}, { timestamps: false });
  400. Entry.belongsTo(User, {
  401. as: 'owner',
  402. foreignKey: {
  403. name: 'ownerId',
  404. allowNull: false,
  405. },
  406. });
  407. Entry.belongsTo(Mail, {
  408. as: 'mail',
  409. foreignKey: {
  410. name: 'mailId',
  411. allowNull: false,
  412. },
  413. });
  414. Mail.belongsToMany(User, {
  415. as: 'recipients',
  416. through: {
  417. model: 'MailRecipients',
  418. timestamps: false,
  419. },
  420. otherKey: {
  421. name: 'recipientId',
  422. allowNull: false,
  423. },
  424. foreignKey: {
  425. name: 'mailId',
  426. allowNull: false,
  427. },
  428. });
  429. Mail.hasMany(Entry, {
  430. as: 'entries',
  431. foreignKey: {
  432. name: 'mailId',
  433. allowNull: false,
  434. },
  435. });
  436. User.hasMany(Entry, {
  437. as: 'entries',
  438. foreignKey: {
  439. name: 'ownerId',
  440. allowNull: false,
  441. },
  442. });
  443. await this.sequelize.sync({ force: true });
  444. await User.create(dialect === 'db2' ? { id: 1 } : {});
  445. const mail = await Mail.create(dialect === 'db2' ? { id: 1 } : {});
  446. await Entry.create({ mailId: mail.id, ownerId: 1 });
  447. await Entry.create({ mailId: mail.id, ownerId: 1 });
  448. // set recipients
  449. await mail.setRecipients([1]);
  450. const result = await Entry.findAndCountAll({
  451. offset: 0,
  452. limit: 10,
  453. order: [['id', 'DESC']],
  454. include: [
  455. {
  456. association: Entry.associations.mail,
  457. include: [
  458. {
  459. association: Mail.associations.recipients,
  460. through: {
  461. where: {
  462. recipientId: 1,
  463. },
  464. },
  465. required: true,
  466. },
  467. ],
  468. required: true,
  469. },
  470. ],
  471. });
  472. expect(result.count).to.equal(2);
  473. expect(result.rows[0].get({ plain: true })).to.deep.equal({
  474. id: 2,
  475. ownerId: 1,
  476. mailId: 1,
  477. mail: {
  478. id: 1,
  479. recipients: [
  480. {
  481. id: 1,
  482. MailRecipients: {
  483. mailId: 1,
  484. recipientId: 1,
  485. },
  486. },
  487. ],
  488. },
  489. });
  490. });
  491. });
  492. describe('foreign key constraints', () => {
  493. it('are enabled by default', async function () {
  494. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  495. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  496. Task.belongsTo(User); // defaults to SET NULL
  497. await this.sequelize.sync({ force: true });
  498. const user = await User.create({ username: 'foo' });
  499. const task = await Task.create({ title: 'task' });
  500. await task.setUser(user);
  501. await user.destroy();
  502. await task.reload();
  503. expect(task.userId).to.equal(null);
  504. });
  505. it('should be possible to disable them', async function () {
  506. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  507. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  508. Task.belongsTo(User, { foreignKeyConstraints: false });
  509. await this.sequelize.sync({ force: true });
  510. const user = await User.create({ username: 'foo' });
  511. const task = await Task.create({ title: 'task' });
  512. await task.setUser(user);
  513. await user.destroy();
  514. await task.reload();
  515. expect(task.userId).to.equal(user.id);
  516. });
  517. it('can cascade deletes', async function () {
  518. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  519. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  520. Task.belongsTo(User, { foreignKey: { onDelete: 'cascade' } });
  521. await this.sequelize.sync({ force: true });
  522. const user = await User.create({ username: 'foo' });
  523. const task = await Task.create({ title: 'task' });
  524. await task.setUser(user);
  525. await user.destroy();
  526. const tasks = await Task.findAll();
  527. expect(tasks).to.have.length(0);
  528. });
  529. if (current.dialect.supports.constraints.restrict) {
  530. it('can restrict deletes', async function () {
  531. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  532. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  533. Task.belongsTo(User, { foreignKey: { onDelete: 'restrict' } });
  534. await this.sequelize.sync({ force: true });
  535. const user = await User.create({ username: 'foo' });
  536. const task = await Task.create({ title: 'task' });
  537. await task.setUser(user);
  538. await expect(user.destroy()).to.eventually.be.rejectedWith(
  539. Sequelize.ForeignKeyConstraintError,
  540. );
  541. const tasks = await Task.findAll();
  542. expect(tasks).to.have.length(1);
  543. });
  544. it('can restrict updates', async function () {
  545. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  546. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  547. Task.belongsTo(User, { foreignKey: { onUpdate: 'restrict' } });
  548. await this.sequelize.sync({ force: true });
  549. const user = await User.create({ username: 'foo' });
  550. const task = await Task.create({ title: 'task' });
  551. await task.setUser(user);
  552. // Changing the id of a DAO requires a little dance since
  553. // the `UPDATE` query generated by `save()` uses `id` in the
  554. // `WHERE` clause
  555. const tableName = User.table;
  556. await expect(
  557. user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id }),
  558. ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
  559. // Should fail due to FK restriction
  560. const tasks = await Task.findAll();
  561. expect(tasks).to.have.length(1);
  562. });
  563. }
  564. // NOTE: mssql does not support changing an autoincrement primary key
  565. if (!['mssql', 'db2', 'ibmi'].includes(dialect)) {
  566. it('can cascade updates', async function () {
  567. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  568. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  569. Task.belongsTo(User, { foreignKey: { onUpdate: 'cascade' } });
  570. await this.sequelize.sync({ force: true });
  571. const user = await User.create({ username: 'foo' });
  572. const task = await Task.create({ title: 'task' });
  573. await task.setUser(user);
  574. // Changing the id of a DAO requires a little dance since
  575. // the `UPDATE` query generated by `save()` uses `id` in the
  576. // `WHERE` clause
  577. const tableName = User.table;
  578. await user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id });
  579. const tasks = await Task.findAll();
  580. expect(tasks).to.have.length(1);
  581. expect(tasks[0].userId).to.equal(999);
  582. });
  583. }
  584. });
  585. describe('association column', () => {
  586. it('has correct type and name for non-id primary keys with non-integer type', async function () {
  587. const User = this.sequelize.define('UserPKBT', {
  588. username: {
  589. type: DataTypes.STRING,
  590. },
  591. });
  592. const Group = this.sequelize.define('GroupPKBT', {
  593. name: {
  594. type: DataTypes.STRING,
  595. primaryKey: true,
  596. },
  597. });
  598. User.belongsTo(Group);
  599. await this.sequelize.sync({ force: true });
  600. expect(User.getAttributes().groupPKBTName.type).to.an.instanceof(DataTypes.STRING);
  601. });
  602. it('should support a non-primary key as the association column on a target without a primary key', async function () {
  603. const User = this.sequelize.define('User', {
  604. username: { type: DataTypes.STRING, unique: true },
  605. });
  606. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  607. User.removeAttribute('id');
  608. Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
  609. await this.sequelize.sync({ force: true });
  610. const newUser = await User.create({ username: 'bob' });
  611. const newTask = await Task.create({ title: 'some task' });
  612. await newTask.setUser(newUser);
  613. const foundTask = await Task.findOne({ where: { title: 'some task' } });
  614. const foundUser = await foundTask.getUser();
  615. await expect(foundUser.username).to.equal('bob');
  616. const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
  617. constraintType: 'FOREIGN KEY',
  618. });
  619. expect(foreignKeysDescriptions[0]).to.deep.include({
  620. referencedColumnNames: ['username'],
  621. referencedTableName: 'Users',
  622. columnNames: ['user_name'],
  623. });
  624. });
  625. it('should support a non-primary unique key as the association column', async function () {
  626. const User = this.sequelize.define('User', {
  627. username: {
  628. type: DataTypes.STRING,
  629. field: 'user_name',
  630. unique: true,
  631. },
  632. });
  633. const Task = this.sequelize.define('Task', {
  634. title: DataTypes.STRING,
  635. });
  636. Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
  637. await this.sequelize.sync({ force: true });
  638. const newUser = await User.create({ username: 'bob' });
  639. const newTask = await Task.create({ title: 'some task' });
  640. await newTask.setUser(newUser);
  641. const foundTask = await Task.findOne({ where: { title: 'some task' } });
  642. const foundUser = await foundTask.getUser();
  643. await expect(foundUser.username).to.equal('bob');
  644. const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
  645. constraintType: 'FOREIGN KEY',
  646. });
  647. expect(foreignKeysDescriptions[0]).to.deep.include({
  648. referencedColumnNames: ['user_name'],
  649. referencedTableName: 'Users',
  650. columnNames: ['user_name'],
  651. });
  652. });
  653. it('should support a non-primary key as the association column with a field option', async function () {
  654. const User = this.sequelize.define('User', {
  655. username: {
  656. type: DataTypes.STRING,
  657. field: 'the_user_name_field',
  658. unique: true,
  659. },
  660. });
  661. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  662. User.removeAttribute('id');
  663. Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
  664. await this.sequelize.sync({ force: true });
  665. const newUser = await User.create({ username: 'bob' });
  666. const newTask = await Task.create({ title: 'some task' });
  667. await newTask.setUser(newUser);
  668. const foundTask = await Task.findOne({ where: { title: 'some task' } });
  669. const foundUser = await foundTask.getUser();
  670. await expect(foundUser.username).to.equal('bob');
  671. const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
  672. constraintType: 'FOREIGN KEY',
  673. });
  674. expect(foreignKeysDescriptions[0]).to.deep.include({
  675. referencedColumnNames: ['the_user_name_field'],
  676. referencedTableName: 'Users',
  677. columnNames: ['user_name'],
  678. });
  679. });
  680. it('should support a non-primary key as the association column in a table with a composite primary key', async function () {
  681. const User = this.sequelize.define('User', {
  682. username: {
  683. type: DataTypes.STRING,
  684. field: 'the_user_name_field',
  685. unique: true,
  686. },
  687. age: {
  688. type: DataTypes.INTEGER,
  689. field: 'the_user_age_field',
  690. primaryKey: true,
  691. },
  692. weight: {
  693. type: DataTypes.INTEGER,
  694. field: 'the_user_weight_field',
  695. primaryKey: true,
  696. },
  697. });
  698. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  699. Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
  700. await this.sequelize.sync({ force: true });
  701. const newUser = await User.create({ username: 'bob', age: 18, weight: 40 });
  702. const newTask = await Task.create({ title: 'some task' });
  703. await newTask.setUser(newUser);
  704. const foundTask = await Task.findOne({ where: { title: 'some task' } });
  705. const foundUser = await foundTask.getUser();
  706. await expect(foundUser.username).to.equal('bob');
  707. const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
  708. constraintType: 'FOREIGN KEY',
  709. });
  710. expect(foreignKeysDescriptions[0]).to.deep.include({
  711. referencedColumnNames: ['the_user_name_field'],
  712. referencedTableName: 'Users',
  713. columnNames: ['user_name'],
  714. });
  715. });
  716. });
  717. describe('association options', () => {
  718. it('can specify data type for auto-generated relational keys', async function () {
  719. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  720. const dataTypes = [DataTypes.INTEGER, DataTypes.STRING];
  721. const Tasks = {};
  722. if (current.dialect.supports.dataTypes.BIGINT) {
  723. dataTypes.push(DataTypes.BIGINT);
  724. }
  725. for (const dataType of dataTypes) {
  726. const tableName = `TaskXYZ_${dataType.getDataTypeId()}`;
  727. Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING });
  728. Tasks[dataType].belongsTo(User, {
  729. foreignKey: { name: 'userId', type: dataType },
  730. foreignKeyConstraints: false,
  731. });
  732. }
  733. await this.sequelize.sync({ force: true });
  734. for (const dataType of dataTypes) {
  735. expect(Tasks[dataType].getAttributes().userId.type).to.be.an.instanceof(dataType);
  736. }
  737. });
  738. describe('allows the user to provide an attribute definition object as foreignKey', () => {
  739. it('works with a column that hasnt been defined before', function () {
  740. const Task = this.sequelize.define('task', {});
  741. const User = this.sequelize.define('user', {});
  742. Task.belongsTo(User, {
  743. foreignKey: {
  744. allowNull: false,
  745. name: 'uid',
  746. },
  747. });
  748. expect(Task.getAttributes().uid).to.be.ok;
  749. expect(Task.getAttributes().uid.allowNull).to.be.false;
  750. const targetTable = Task.getAttributes().uid.references.table;
  751. assert(typeof targetTable === 'object');
  752. expect(targetTable).to.deep.equal(User.table);
  753. expect(Task.getAttributes().uid.references.key).to.equal('id');
  754. });
  755. it('works when taking a column directly from the object', function () {
  756. const User = this.sequelize.define('user', {
  757. uid: {
  758. type: DataTypes.INTEGER,
  759. primaryKey: true,
  760. },
  761. });
  762. const Profile = this.sequelize.define('project', {
  763. user_id: {
  764. type: DataTypes.INTEGER,
  765. allowNull: false,
  766. },
  767. });
  768. Profile.belongsTo(User, { foreignKey: Profile.getAttributes().user_id });
  769. expect(Profile.getAttributes().user_id).to.be.ok;
  770. const targetTable = Profile.getAttributes().user_id.references.table;
  771. assert(typeof targetTable === 'object');
  772. expect(targetTable).to.deep.equal(User.table);
  773. expect(Profile.getAttributes().user_id.references.key).to.equal('uid');
  774. expect(Profile.getAttributes().user_id.allowNull).to.be.false;
  775. });
  776. it('works when merging with an existing definition', function () {
  777. const Task = this.sequelize.define('task', {
  778. projectId: {
  779. defaultValue: 42,
  780. type: DataTypes.INTEGER,
  781. },
  782. });
  783. const Project = this.sequelize.define('project', {});
  784. Task.belongsTo(Project, { foreignKey: { allowNull: true } });
  785. expect(Task.getAttributes().projectId).to.be.ok;
  786. expect(Task.getAttributes().projectId.defaultValue).to.equal(42);
  787. expect(Task.getAttributes().projectId.allowNull).to.be.ok;
  788. });
  789. });
  790. });
  791. describe('Eager loading', () => {
  792. beforeEach(function () {
  793. this.Individual = this.sequelize.define('individual', {
  794. name: DataTypes.STRING,
  795. });
  796. this.Hat = this.sequelize.define('hat', {
  797. name: DataTypes.STRING,
  798. });
  799. this.Individual.belongsTo(this.Hat, {
  800. as: 'personwearinghat',
  801. });
  802. });
  803. it('should load with an alias', async function () {
  804. await this.sequelize.sync({ force: true });
  805. const [individual1, hat] = await Promise.all([
  806. this.Individual.create({ name: 'Foo Bar' }),
  807. this.Hat.create({ name: 'Baz' }),
  808. ]);
  809. await individual1.setPersonwearinghat(hat);
  810. const individual0 = await this.Individual.findOne({
  811. where: { name: 'Foo Bar' },
  812. include: [{ model: this.Hat, as: 'personwearinghat' }],
  813. });
  814. expect(individual0.name).to.equal('Foo Bar');
  815. expect(individual0.personwearinghat.name).to.equal('Baz');
  816. const individual = await this.Individual.findOne({
  817. where: { name: 'Foo Bar' },
  818. include: [
  819. {
  820. model: this.Hat,
  821. as: 'personwearinghat',
  822. },
  823. ],
  824. });
  825. expect(individual.name).to.equal('Foo Bar');
  826. expect(individual.personwearinghat.name).to.equal('Baz');
  827. });
  828. it('should load all', async function () {
  829. await this.sequelize.sync({ force: true });
  830. const [individual0, hat] = await Promise.all([
  831. this.Individual.create({ name: 'Foo Bar' }),
  832. this.Hat.create({ name: 'Baz' }),
  833. ]);
  834. await individual0.setPersonwearinghat(hat);
  835. const individual = await this.Individual.findOne({
  836. where: { name: 'Foo Bar' },
  837. include: [{ all: true }],
  838. });
  839. expect(individual.name).to.equal('Foo Bar');
  840. expect(individual.personwearinghat.name).to.equal('Baz');
  841. });
  842. });
  843. });