From 7257a7b0f4841c2ef4812bac00897d4e74a7a610 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 18 Aug 2026 19:30:28 +0200 Subject: [PATCH] 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. --- src/lib/services/furni-import.ts | 36 ++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/lib/services/furni-import.ts b/src/lib/services/furni-import.ts index 6e667e344e..392f75dbd9 100644 --- a/src/lib/services/furni-import.ts +++ b/src/lib/services/furni-import.ts @@ -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 { + 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 { '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 {