Fix duplicate catalog pages from import race condition
A concurrent import could create the same catalog sub-page twice (getOrCreateCategoryPage / getOrCreateImportedParentPage do a SELECT-then-INSERT with no unique constraint). Deduplicate right after insert by keeping the lowest-id page and removing the duplicate, while never deleting a page that already received catalog items.
This commit is contained in:
1 parent
8a6c596727
commit
7257a7b0f4
1 file changed
+32
-4
@@ -126,6 +126,29 @@ export function buildItemsBaseEnrichmentUpdate(params: {
|
||||
`;
|
||||
}
|
||||
|
||||
/** Keep the lowest-id row for a catalog page and delete any duplicate created
|
||||
* concurrently by a racing import. Returns the surviving page id. */
|
||||
async function dedupeCatalogPage(
|
||||
captionSave: string,
|
||||
parentId: number,
|
||||
justCreatedId: number,
|
||||
): Promise<number> {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT MIN(id) AS id FROM catalog_pages
|
||||
WHERE caption_save = ${captionSave} AND parent_id = ${parentId}
|
||||
`)) as unknown as [Array<{ id: number }>, unknown];
|
||||
const winner = rows[0]?.id ?? justCreatedId;
|
||||
if (winner !== justCreatedId) {
|
||||
// Never delete a page that already received catalog items.
|
||||
await db.execute(sql`
|
||||
DELETE FROM catalog_pages
|
||||
WHERE id = ${justCreatedId}
|
||||
AND NOT EXISTS (SELECT 1 FROM catalog_items WHERE page_id = ${justCreatedId})
|
||||
`);
|
||||
}
|
||||
return winner;
|
||||
}
|
||||
|
||||
/** Get or create the root "Imported Furniture" parent page (no items directly on this page).
|
||||
* Caches the page ID in-memory for the process lifetime to avoid redundant DB reads
|
||||
* during batch imports */
|
||||
@@ -158,8 +181,12 @@ export async function getOrCreateImportedParentPage(): Promise<number> {
|
||||
'Failed to create "Imported Furniture" parent catalog page',
|
||||
);
|
||||
}
|
||||
parentIdCache = created.id;
|
||||
return created.id;
|
||||
parentIdCache = await dedupeCatalogPage(
|
||||
IMPORTED_PAGE_CAPTION_SAVE,
|
||||
-1,
|
||||
created.id,
|
||||
);
|
||||
return parentIdCache;
|
||||
}
|
||||
|
||||
/** Determine the catalog category for a furniture item based on its classname and type. */
|
||||
@@ -246,8 +273,9 @@ export async function getOrCreateCategoryPage(
|
||||
);
|
||||
}
|
||||
if (!categoryPageCache) categoryPageCache = new Map();
|
||||
categoryPageCache.set(captionSave, created.id);
|
||||
return created.id;
|
||||
const winner = await dedupeCatalogPage(captionSave, parentId, created.id);
|
||||
categoryPageCache.set(captionSave, winner);
|
||||
return winner;
|
||||
}
|
||||
|
||||
export function resetCatalogPageCache(): void {
|
||||
|
||||
Reference in new issue
Block a user