Skip to content

Commit 6251909

Browse files
committed
fix: wrap packagist metadata writes in a transaction
Signed-off-by: anilb <epipav@gmail.com>
1 parent 91c6edc commit 6251909

2 files changed

Lines changed: 72 additions & 62 deletions

File tree

services/apps/packages_worker/src/packagist/__tests__/persistMetadata.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ const mockVersions = vi.mocked(upsertPackagistVersions)
2323
const mockIds = vi.mocked(getPackagistPackageIdsByNames)
2424
const mockDeps = vi.mocked(reconcileVersionDependencies)
2525

26-
const qx = {} as QueryExecutor
26+
const qx = {
27+
tx: vi.fn((cb: (t: QueryExecutor) => Promise<void>) => cb(qx)),
28+
} as unknown as QueryExecutor
2729
const PURL = 'pkg:composer/monolog/monolog'
2830

2931
const expanded: PackagistExpandedVersion[] = [

services/apps/packages_worker/src/packagist/upsertMetadata.ts

Lines changed: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -28,77 +28,85 @@ export async function persistPackagistMetadata(
2828
const { versionRows, latestVersion, firstReleaseAt, latestReleaseAt, licenses, homepage } =
2929
buildPackagistVersionRows(expanded)
3030

31-
const agg = await updatePackagistVersionAggregates(qx, purl, {
32-
versionsCount: versionRows.length,
33-
latestVersion,
34-
firstReleaseAt,
35-
latestReleaseAt,
36-
licenses,
37-
homepage,
38-
})
31+
let found = false
32+
const changedFields: string[] = []
33+
let unresolvedDependencyTargets = 0
3934

40-
if (!agg) {
41-
return { found: false, changedFields: [], unresolvedDependencyTargets: 0 }
42-
}
35+
// One transaction for the whole p2 write path: the aggregates update, version
36+
// upsert + is_latest cleanup, and dependency reconcile (delete stale + upsert
37+
// current) are each multi-statement on their own — sharing one tx means a failure
38+
// partway through can never leave versions/dependencies half-refreshed.
39+
await qx.tx(async (t) => {
40+
const agg = await updatePackagistVersionAggregates(t, purl, {
41+
versionsCount: versionRows.length,
42+
latestVersion,
43+
firstReleaseAt,
44+
latestReleaseAt,
45+
licenses,
46+
homepage,
47+
})
4348

44-
const changedFields = [...agg.changedFields]
49+
if (!agg) return
4550

46-
let versionIds: Array<{ number: string; id: string }> = []
47-
if (versionRows.length > 0) {
48-
const versionResult = await upsertPackagistVersions(qx, agg.id, versionRows, latestVersion)
49-
changedFields.push(...versionResult.changedFields)
50-
versionIds = versionResult.versionIds
51-
}
51+
found = true
52+
changedFields.push(...agg.changedFields)
5253

53-
// One pass over the tagged versions collects target names and provisional edges;
54-
// target ids are then resolved in a single batch query.
55-
const versionMap = new Map(versionIds.map((v) => [v.number, v.id]))
56-
const targetNames = new Set<string>()
57-
const pending: Array<{ versionId: string; dep: PackagistDependency }> = []
54+
let versionIds: Array<{ number: string; id: string }> = []
55+
if (versionRows.length > 0) {
56+
const versionResult = await upsertPackagistVersions(t, agg.id, versionRows, latestVersion)
57+
changedFields.push(...versionResult.changedFields)
58+
versionIds = versionResult.versionIds
59+
}
5860

59-
for (const v of expanded) {
60-
if (isPackagistDevVersion(v.version, v.version_normalized)) continue
61-
const versionId = versionMap.get(v.version)
62-
if (!versionId) continue
61+
// One pass over the tagged versions collects target names and provisional edges;
62+
// target ids are then resolved in a single batch query.
63+
const versionMap = new Map(versionIds.map((v) => [v.number, v.id]))
64+
const targetNames = new Set<string>()
65+
const pending: Array<{ versionId: string; dep: PackagistDependency }> = []
6366

64-
for (const dep of extractVersionDependencies(v)) {
65-
targetNames.add(dep.name)
66-
pending.push({ versionId, dep })
67+
for (const v of expanded) {
68+
if (isPackagistDevVersion(v.version, v.version_normalized)) continue
69+
const versionId = versionMap.get(v.version)
70+
if (!versionId) continue
71+
72+
for (const dep of extractVersionDependencies(v)) {
73+
targetNames.add(dep.name)
74+
pending.push({ versionId, dep })
75+
}
6776
}
68-
}
6977

70-
// Reconciled for every version being refreshed (not just ones with a resolved
71-
// dependency this pass) — a version whose manifest drops a requirement, or all of
72-
// them, must have its stale package_dependencies rows removed, not just the newly
73-
// declared ones upserted.
74-
let unresolvedDependencyTargets = 0
75-
if (versionIds.length > 0) {
76-
const edges: VersionDependencyEdge[] = []
77-
if (pending.length > 0) {
78-
const idMap = await getPackagistPackageIdsByNames(qx, Array.from(targetNames))
79-
for (const { versionId, dep } of pending) {
80-
const dependsOnId = idMap.get(dep.name)
81-
if (!dependsOnId) {
82-
unresolvedDependencyTargets++
83-
continue
78+
// Reconciled for every version being refreshed (not just ones with a resolved
79+
// dependency this pass) — a version whose manifest drops a requirement, or all of
80+
// them, must have its stale package_dependencies rows removed, not just the newly
81+
// declared ones upserted.
82+
if (versionIds.length > 0) {
83+
const edges: VersionDependencyEdge[] = []
84+
if (pending.length > 0) {
85+
const idMap = await getPackagistPackageIdsByNames(t, Array.from(targetNames))
86+
for (const { versionId, dep } of pending) {
87+
const dependsOnId = idMap.get(dep.name)
88+
if (!dependsOnId) {
89+
unresolvedDependencyTargets++
90+
continue
91+
}
92+
edges.push({
93+
packageId: agg.id,
94+
versionId,
95+
dependsOnId,
96+
constraint: dep.constraint,
97+
kind: dep.kind,
98+
})
8499
}
85-
edges.push({
86-
packageId: agg.id,
87-
versionId,
88-
dependsOnId,
89-
constraint: dep.constraint,
90-
kind: dep.kind,
91-
})
92100
}
93-
}
94101

95-
const depChanges = await reconcileVersionDependencies(
96-
qx,
97-
versionIds.map((v) => v.id),
98-
edges,
99-
)
100-
changedFields.push(...depChanges)
101-
}
102+
const depChanges = await reconcileVersionDependencies(
103+
t,
104+
versionIds.map((v) => v.id),
105+
edges,
106+
)
107+
changedFields.push(...depChanges)
108+
}
109+
})
102110

103-
return { found: true, changedFields, unresolvedDependencyTargets }
111+
return { found, changedFields, unresolvedDependencyTargets }
104112
}

0 commit comments

Comments
 (0)