Skip to content

Commit 8e0d609

Browse files
[TECH] Eviter de créer des transactions nestées lorsqu'on utilise la fonction batchInsert de knex dans le cadre d'une DomainTransaction
#11435
2 parents 9526d82 + d05f9b4 commit 8e0d609

File tree

15 files changed

+95
-20
lines changed

15 files changed

+95
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { knex } from '../../../db/knex-database-connection.js';
12
import { DomainTransaction } from '../../../src/shared/domain/DomainTransaction.js';
23

34
const batchAddTargetProfilesToOrganization = async function (organizationTargetProfiles) {
45
const knexConn = DomainTransaction.getConnection();
5-
await knexConn.batchInsert('target-profile-shares', organizationTargetProfiles);
6+
await knex
7+
.batchInsert('target-profile-shares', organizationTargetProfiles)
8+
.transacting(knexConn.isTransaction ? knexConn : null);
69
};
710

811
export { batchAddTargetProfilesToOrganization };

Diff for: api/scripts/certification/import-certification-cpf-cities.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ async function main(filePath) {
412412
logger.info('Inserting cities in database... ');
413413
trx = await knex.transaction();
414414
await trx('certification-cpf-cities').del();
415-
const batchInfo = await trx.batchInsert('certification-cpf-cities', cities);
415+
const batchInfo = await knex.batchInsert('certification-cpf-cities', cities).transacting(trx);
416416
const insertedLines = _getInsertedLineNumber(batchInfo);
417417
logger.info('✅ ');
418418
await trx.commit();

Diff for: api/scripts/certification/import-certification-cpf-countries.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async function main(filePath) {
9797
console.log('Emptying existing countries in database... ');
9898
await trx('certification-cpf-countries').del();
9999
console.log('Inserting countries in database... ');
100-
await trx.batchInsert('certification-cpf-countries', countries);
100+
await knex.batchInsert('certification-cpf-countries', countries).transacting(trx);
101101
await trx.commit();
102102
console.log('ok');
103103

Diff for: api/scripts/certification/next-gen/import-complementary-alone-feature-pilot-certification-centers-from-csv.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ async function main(filePath) {
9292
.leftJoin('features', 'features.id', 'certification-center-features.featureId')
9393
.where('features.key', CERTIFICATION_FEATURES.CAN_REGISTER_FOR_A_COMPLEMENTARY_CERTIFICATION_ALONE.key)
9494
.del();
95-
const batchInfo = await trx.batchInsert('certification-center-features', certificationCentersPilotsList);
95+
const batchInfo = await knex
96+
.batchInsert('certification-center-features', certificationCentersPilotsList)
97+
.transacting(trx);
9698
const insertedLines = _getInsertedLineNumber(batchInfo);
9799
logger.info('✅ ');
98100
await trx.commit();

Diff for: api/scripts/data-generation/generate-campaign-with-participants.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ async function _createUsers({ count, uniqId, trx }) {
268268
});
269269
}
270270
const chunkSize = _getChunkSize(userData[0]);
271-
const users = await trx.batchInsert('users', userData.flat(), chunkSize).returning('id');
271+
const users = await knex.batchInsert('users', userData.flat(), chunkSize).transacting(trx).returning('id');
272272
return users.map((user) => user.id);
273273
}
274274

@@ -296,8 +296,9 @@ async function _createOrganizationLearners({ userIds, organizationId, uniqId, tr
296296
organizationLearnerData.push(organizationLearnerSpecificBuilder({ userId, organizationId, identifier }));
297297
}
298298
const chunkSize = _getChunkSize(organizationLearnerData[0]);
299-
return trx
299+
return knex
300300
.batchInsert('organization-learners', organizationLearnerData.flat(), chunkSize)
301+
.transacting(trx)
301302
.returning(['id', 'userId']);
302303
}
303304

@@ -355,7 +356,7 @@ async function _createAssessments({ userAndCampaignParticipationIds, trx }) {
355356
});
356357
}
357358
const chunkSize = _getChunkSize(assessmentData[0]);
358-
return trx.batchInsert('assessments', assessmentData.flat(), chunkSize).returning(['id', 'userId']);
359+
return knex.batchInsert('assessments', assessmentData.flat(), chunkSize).transacting(trx).returning(['id', 'userId']);
359360
}
360361

361362
async function _createCampaignParticipations({ campaignId, trx, organizationLearnerAndUserIds }) {
@@ -377,7 +378,10 @@ async function _createCampaignParticipations({ campaignId, trx, organizationLear
377378
});
378379
}
379380
const chunkSize = _getChunkSize(participationData[0]);
380-
return trx.batchInsert('campaign-participations', participationData.flat(), chunkSize).returning(['id', 'userId']);
381+
return knex
382+
.batchInsert('campaign-participations', participationData.flat(), chunkSize)
383+
.transacting(trx)
384+
.returning(['id', 'userId']);
381385
}
382386

383387
async function _createAnswersAndKnowledgeElements({ campaignId, userAndAssessmentIds, trx }) {
@@ -391,8 +395,9 @@ async function _createAnswersAndKnowledgeElements({ campaignId, userAndAssessmen
391395
});
392396
}
393397
const chunkSize = _getChunkSize(answerData[0]);
394-
const answerRecordedData = await trx
398+
const answerRecordedData = await knex
395399
.batchInsert('answers', answerData.flat(), chunkSize)
400+
.transacting(trx)
396401
.returning(['id', 'assessmentId']);
397402
_log('\tOK');
398403

@@ -467,7 +472,7 @@ async function _createBadgeAcquisitions({ targetProfile, userAndCampaignParticip
467472
}
468473
}
469474
const chunkSize = _getChunkSize(badgeAcquisitionData[0]);
470-
await trx.batchInsert('badge-acquisitions', badgeAcquisitionData.flat(), chunkSize);
475+
await knex.batchInsert('badge-acquisitions', badgeAcquisitionData.flat(), chunkSize).transacting(trx);
471476
_log(`\t${badgeAcquisitionData.flat().length} acquisitions de badge créées`);
472477
}
473478

Diff for: api/scripts/prod/target-profile-migrations/common.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import _ from 'lodash';
22

3+
import { knex } from '../../../db/knex-database-connection.js';
34
import * as skillRepository from '../../../src/shared/infrastructure/repositories/skill-repository.js';
45
import * as tubeRepository from '../../../src/shared/infrastructure/repositories/tube-repository.js';
56

@@ -37,7 +38,7 @@ const autoMigrateTargetProfile = async function (id, trx) {
3738
return { ...tube, targetProfileId: id };
3839
});
3940
await trx('target-profiles').update({ migration_status: 'AUTO' }).where({ id });
40-
await trx.batchInsert('target-profile_tubes', completeTubes);
41+
await knex.batchInsert('target-profile_tubes', completeTubes).transacting(trx);
4142
};
4243

4344
export { autoMigrateTargetProfile };

Diff for: api/src/certification/enrolment/infrastructure/repositories/sco-certification-candidate-repository.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const addNonEnrolledCandidatesToSession = async function ({ sessionId, scoCertif
2929
allSubscriptionsDTO.push(subscriptionDTO);
3030
}
3131
}
32-
await trx.batchInsert('certification-subscriptions', allSubscriptionsDTO);
32+
await knex.batchInsert('certification-subscriptions', allSubscriptionsDTO).transacting(trx);
3333
});
3434
};
3535

Diff for: api/src/organizational-entities/infrastructure/repositories/data-protection-officer.repository.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { knex } from '../../../../db/knex-database-connection.js';
12
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
23
import { DataProtectionOfficer } from '../../domain/models/DataProtectionOfficer.js';
34

45
const DATA_PROTECTION_OFFICERS_TABLE_NAME = 'data-protection-officers';
56

67
async function batchAddDataProtectionOfficerToOrganization(organizationDataProtectionOfficer) {
78
const knexConn = DomainTransaction.getConnection();
8-
await knexConn.batchInsert(DATA_PROTECTION_OFFICERS_TABLE_NAME, organizationDataProtectionOfficer);
9+
await knex
10+
.batchInsert(DATA_PROTECTION_OFFICERS_TABLE_NAME, organizationDataProtectionOfficer)
11+
.transacting(knexConn.isTransaction ? knexConn : null);
912
}
1013

1114
async function get({ organizationId = null, certificationCenterId = null }) {

Diff for: api/src/organizational-entities/infrastructure/repositories/organization-tag.repository.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const create = async function (organizationTag) {
2828
const batchCreate = async function (organizationsTags) {
2929
const knexConn = DomainTransaction.getConnection();
3030

31-
return knexConn.batchInsert('organization-tags', organizationsTags);
31+
return knex.batchInsert('organization-tags', organizationsTags).transacting(knexConn.isTransaction ? knexConn : null);
3232
};
3333

3434
const isExistingByOrganizationIdAndTagId = async function ({ organizationId, tagId }) {

Diff for: api/src/prescription/campaign/infrastructure/repositories/campaign-administration-repository.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const save = async function (campaigns, dependencies = { skillRepository }) {
113113
...rightLevelSkills.map((skill) => ({ skillId: skill.id, campaignId: latestCreatedCampaign.id })),
114114
);
115115
}
116-
await trx.batchInsert('campaign_skills', skillData);
116+
await knex.batchInsert('campaign_skills', skillData).transacting(trx);
117117
}
118118
}
119119
await trx.commit();

Diff for: api/src/prescription/target-profile/infrastructure/repositories/target-profile-administration-repository.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const create = async function ({ targetProfileForCreation }) {
8585
tubeId: tube.id,
8686
level: tube.level,
8787
}));
88-
await knexConn.batchInsert('target-profile_tubes', tubesData);
88+
await knex.batchInsert('target-profile_tubes', tubesData).transacting(knexConn.isTransaction ? knexConn : null);
8989

9090
return targetProfileId;
9191
};

Diff for: api/src/shared/infrastructure/repositories/jobs/job-repository.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Joi from 'joi';
22

3+
import { knex } from '../../../../../db/knex-database-connection.js';
34
import { DomainTransaction } from '../../../domain/DomainTransaction.js';
45
import { EntityValidationError } from '../../../domain/errors.js';
56

@@ -59,7 +60,7 @@ export class JobRepository {
5960
async #send(jobs) {
6061
const knexConn = DomainTransaction.getConnection();
6162

62-
const results = await knexConn.batchInsert('pgboss.job', jobs);
63+
const results = await knex.batchInsert('pgboss.job', jobs).transacting(knexConn.isTransaction ? knexConn : null);
6364

6465
const rowCount = results.reduce((total, batchResult) => total + (batchResult.rowCount || 0), 0);
6566

Diff for: api/src/shared/infrastructure/repositories/knowledge-element-repository.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ const findUniqByUserIds = function (userIds) {
4444
const batchSave = async function ({ knowledgeElements }) {
4545
const knexConn = DomainTransaction.getConnection();
4646
const knowledgeElementsToSave = knowledgeElements.map((ke) => _.omit(ke, ['id', 'createdAt']));
47-
const savedKnowledgeElements = await knexConn.batchInsert(tableName, knowledgeElementsToSave).returning('*');
47+
const savedKnowledgeElements = await knex
48+
.batchInsert(tableName, knowledgeElementsToSave)
49+
.transacting(knexConn.isTransaction ? knexConn : null)
50+
.returning('*');
4851
return savedKnowledgeElements.map((ke) => new KnowledgeElement(ke));
4952
};
5053

Diff for: api/tests/evaluation/unit/infrastructure/repositories/answer-job-repository_test.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { AnswerJobRepository } from '../../../../../src/evaluation/infrastructure/repositories/answer-job-repository.js';
22
import { config } from '../../../../../src/shared/config.js';
33
import { DomainTransaction } from '../../../../../src/shared/domain/DomainTransaction.js';
4-
import { expect, sinon } from '../../../../test-helper.js';
4+
import { expect, knex, sinon } from '../../../../test-helper.js';
55

66
describe('Evaluation | Unit | Infrastructure | Repositories | AnswerJobRepository', function () {
77
beforeEach(function () {
88
sinon.stub(config, 'featureToggles');
9+
sinon.stub(knex, 'batchInsert').callsFake(() => ({
10+
transacting: sinon.stub().resolves([{ rowCount: 1 }]),
11+
}));
912
config.featureToggles.isQuestEnabled = true;
1013
config.featureToggles.isAsyncQuestRewardingCalculationEnabled = true;
1114
});

Diff for: api/tests/shared/integration/infrastructure/repositories/jobs/job-repository_test.js

+55-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { DomainTransaction } from '../../../../../../src/shared/domain/DomainTransaction.js';
12
import { EntityValidationError } from '../../../../../../src/shared/domain/errors.js';
23
import {
34
JobExpireIn,
45
JobPriority,
56
JobRepository,
67
JobRetry,
78
} from '../../../../../../src/shared/infrastructure/repositories/jobs/job-repository.js';
8-
import { catchErrSync, expect } from '../../../../../test-helper.js';
9+
import { catchErr, catchErrSync, expect, knex } from '../../../../../test-helper.js';
910

1011
describe('Integration | Infrastructure | Repositories | Jobs | job-repository', function () {
1112
it('create one job db with given config', async function () {
@@ -63,6 +64,59 @@ describe('Integration | Infrastructure | Repositories | Jobs | job-repository',
6364
expect(jobsInserted.rowCount).to.equal(2);
6465
});
6566

67+
context('transaction', function () {
68+
context('when no transaction ongoing', function () {
69+
it("should not insert any jobs if one of them is invalid and can't be inserted", async function () {
70+
// given
71+
const name = 'JobTest';
72+
// Knex doc : default chunk for batchInsert is 1000
73+
const defaultChunkValidJobs = [...Array(1000).keys()].map((i) => ({ jobParam: i }));
74+
const invalidJob = '>';
75+
const priority = JobPriority.HIGH;
76+
const job = new JobRepository({ name, priority });
77+
78+
// when
79+
const expectedError = await catchErr(job.performAsync, job)(...defaultChunkValidJobs, invalidJob);
80+
81+
// then
82+
expect(expectedError.detail).to.equal('Token ">" is invalid.');
83+
const { count } = await knex('pgboss.job').count('id').first();
84+
expect(count).to.equal(0);
85+
});
86+
});
87+
88+
context('when a transaction ongoing in DomainTransaction', function () {
89+
it('should use the same existing transaction', async function () {
90+
// given
91+
const name = 'JobTest';
92+
const jobs = [{ jobParam: 1 }, { jobParam: 2 }];
93+
const priority = JobPriority.HIGH;
94+
const job = new JobRepository({ name, priority });
95+
96+
// when
97+
let knexConn;
98+
const callback = async () => {
99+
knexConn = DomainTransaction.getConnection();
100+
await knexConn('features').insert({ key: 'someRandomFeature' });
101+
await job.performAsync(...jobs);
102+
const { count: countFeaturesBefore } = await knexConn('features').count('id').first();
103+
expect(countFeaturesBefore).to.equal(1);
104+
const { count: countJobsBefore } = await knexConn('pgboss.job').count('id').first();
105+
expect(countJobsBefore).to.equal(2);
106+
throw new Error('I want to rollback');
107+
};
108+
const expectedError = await catchErr(DomainTransaction.execute)(callback);
109+
110+
// then
111+
expect(expectedError.message).to.equal('I want to rollback');
112+
const { count: countFeaturesAfter } = await knex('features').count('id').first();
113+
expect(countFeaturesAfter).to.equal(0);
114+
const { count: countJobsAfter } = await knex('pgboss.job').count('id').first();
115+
expect(countJobsAfter).to.equal(0);
116+
});
117+
});
118+
});
119+
66120
describe('JobExpireIn', function () {
67121
it('reject unexpected expiredIn value', async function () {
68122
// given

0 commit comments

Comments
 (0)