destroy.test.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import type {
  2. CreationOptional,
  3. InferAttributes,
  4. InferCreationAttributes,
  5. NonAttribute,
  6. } from '@sequelize/core';
  7. import { DataTypes, ManualOnDelete, Model } from '@sequelize/core';
  8. import { Attribute, BelongsTo, NotNull } from '@sequelize/core/decorators-legacy';
  9. import sinon from 'sinon';
  10. import { beforeAll2, expectPerDialect, sequelize, toMatchSql } from '../../support';
  11. import { setResetMode } from '../support';
  12. describe('ModelRepository#_UNSTABLE_destroy', () => {
  13. setResetMode('destroy');
  14. const vars = beforeAll2(async () => {
  15. class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
  16. declare id: CreationOptional<number>;
  17. }
  18. class Project extends Model<InferAttributes<Project>, InferCreationAttributes<Project>> {
  19. declare id: CreationOptional<number>;
  20. @NotNull
  21. @Attribute(DataTypes.INTEGER)
  22. declare ownerId: number;
  23. @BelongsTo(() => User, 'ownerId')
  24. declare owner: NonAttribute<User>;
  25. }
  26. class Task extends Model<InferAttributes<Task>, InferCreationAttributes<Task>> {
  27. declare id: CreationOptional<number>;
  28. @NotNull
  29. @Attribute(DataTypes.INTEGER)
  30. declare projectId: number;
  31. @BelongsTo(() => Project, 'projectId')
  32. declare project: NonAttribute<Project>;
  33. }
  34. sequelize.addModels([User, Project, Task]);
  35. await sequelize.sync({ force: true });
  36. return { User, Project, Task };
  37. });
  38. afterEach(() => {
  39. sinon.restore();
  40. });
  41. describe('with "manualOnDelete" = "all"', () => {
  42. it('cascade deletes in JavaScript', async () => {
  43. const { User, Project, Task } = vars;
  44. const user = await User.create({ id: 1 });
  45. const project = await Project.create({ id: 1, ownerId: user.id });
  46. await Task.create({ id: 1, projectId: project.id });
  47. const spy = sinon.spy(sequelize, 'queryRaw');
  48. await User.modelRepository._UNSTABLE_destroy(user, { manualOnDelete: ManualOnDelete.all });
  49. const calls = spy.getCalls().map(call => call.args[0]);
  50. expectPerDialect(() => calls, {
  51. default: toMatchSql([
  52. 'START TRANSACTION',
  53. 'SELECT [id], [ownerId], [createdAt], [updatedAt] FROM [Projects] AS [Project] WHERE [Project].[ownerId] IN (1);',
  54. 'SELECT [id], [projectId], [createdAt], [updatedAt] FROM [Tasks] AS [Task] WHERE [Task].[projectId] IN (1);',
  55. 'DELETE FROM [Tasks] WHERE [id] = 1',
  56. 'DELETE FROM [Projects] WHERE [id] = 1',
  57. 'DELETE FROM [Users] WHERE [id] = 1',
  58. 'COMMIT',
  59. ]),
  60. mssql: toMatchSql([
  61. // mssql transactions don't go through .queryRaw, they are called on the connection object
  62. // 'BEGIN TRANSACTION;',
  63. 'SELECT [id], [ownerId], [createdAt], [updatedAt] FROM [Projects] AS [Project] WHERE [Project].[ownerId] IN (1);',
  64. 'SELECT [id], [projectId], [createdAt], [updatedAt] FROM [Tasks] AS [Task] WHERE [Task].[projectId] IN (1);',
  65. 'DELETE FROM [Tasks] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
  66. 'DELETE FROM [Projects] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
  67. 'DELETE FROM [Users] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
  68. // 'COMMIT TRANSACTION;',
  69. ]),
  70. db2: toMatchSql(
  71. [
  72. // db2 transactions don't go through .queryRaw, they are called on the connection object
  73. // 'BEGIN TRANSACTION;',
  74. 'SELECT [id], [ownerId], [createdAt], [updatedAt] FROM [Projects] AS [Project] WHERE [Project].[ownerId] IN (1);',
  75. 'SELECT [id], [projectId], [createdAt], [updatedAt] FROM [Tasks] AS [Task] WHERE [Task].[projectId] IN (1);',
  76. 'DELETE FROM [Tasks] WHERE [id] = 1',
  77. 'DELETE FROM [Projects] WHERE [id] = 1',
  78. 'DELETE FROM [Users] WHERE [id] = 1',
  79. // 'COMMIT TRANSACTION;',
  80. ],
  81. { genericQuotes: true },
  82. ),
  83. sqlite3: toMatchSql(
  84. [
  85. 'BEGIN DEFERRED TRANSACTION',
  86. 'SELECT [id], [ownerId], [createdAt], [updatedAt] FROM [Projects] AS [Project] WHERE [Project].[ownerId] IN (1);',
  87. 'SELECT [id], [projectId], [createdAt], [updatedAt] FROM [Tasks] AS [Task] WHERE [Task].[projectId] IN (1);',
  88. 'DELETE FROM [Tasks] WHERE [id] = 1',
  89. 'DELETE FROM [Projects] WHERE [id] = 1',
  90. 'DELETE FROM [Users] WHERE [id] = 1',
  91. 'COMMIT',
  92. ],
  93. { genericQuotes: true },
  94. ),
  95. });
  96. });
  97. it('does not start a transaction if one is already started', async () => {
  98. const { User, Project, Task } = vars;
  99. const user = await User.create({ id: 1 });
  100. const project = await Project.create({ id: 1, ownerId: user.id });
  101. await Task.create({ id: 1, projectId: project.id });
  102. const calls = await sequelize.transaction(async transaction => {
  103. const spy = sinon.spy(sequelize, 'queryRaw');
  104. await User.modelRepository._UNSTABLE_destroy(user, {
  105. manualOnDelete: ManualOnDelete.all,
  106. transaction,
  107. });
  108. return spy.getCalls().map(call => call.args[0]);
  109. });
  110. expectPerDialect(() => calls, {
  111. default: toMatchSql([
  112. 'SELECT [id], [ownerId], [createdAt], [updatedAt] FROM [Projects] AS [Project] WHERE [Project].[ownerId] IN (1);',
  113. 'SELECT [id], [projectId], [createdAt], [updatedAt] FROM [Tasks] AS [Task] WHERE [Task].[projectId] IN (1);',
  114. 'DELETE FROM [Tasks] WHERE [id] = 1',
  115. 'DELETE FROM [Projects] WHERE [id] = 1',
  116. 'DELETE FROM [Users] WHERE [id] = 1',
  117. ]),
  118. mssql: toMatchSql([
  119. 'SELECT [id], [ownerId], [createdAt], [updatedAt] FROM [Projects] AS [Project] WHERE [Project].[ownerId] IN (1);',
  120. 'SELECT [id], [projectId], [createdAt], [updatedAt] FROM [Tasks] AS [Task] WHERE [Task].[projectId] IN (1);',
  121. 'DELETE FROM [Tasks] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
  122. 'DELETE FROM [Projects] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
  123. 'DELETE FROM [Users] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
  124. ]),
  125. });
  126. });
  127. });
  128. });