fix: reorder catalog missing-entry scan after structural repair
runCatalogAudit computed missingCatalogEntries from the pre-repair snapshot, so generated catalog SQL could reference sprite ids deleted by the duplicate-classname merge and create new orphaned catalog_items rows. Re-derive entries after repairStructure and add a defensive guard in generateCatalogSql that skips entries whose item id no longer exists in items_base. Also fix the catalog_pages INSERT template (18 columns vs 17 values) which made page creation always fail with 'Column count doesn't match value count'. Add a sandbox-guarded live repair rehearsal test covering the full pipeline.
This commit is contained in:
1 parent
60b6d0856c
commit
3a63152ee3
3 files changed
+318
-2
No files matched your search
@@ -24,6 +24,7 @@ export interface CatalogSqlResult {
|
||||
statements: string[];
|
||||
sql: string;
|
||||
count: number;
|
||||
skipped: number;
|
||||
}
|
||||
|
||||
function escSqlLiteral(value: string): string {
|
||||
@@ -341,12 +342,25 @@ export async function generateCatalogSql(
|
||||
`)) as unknown as [Array<{ maxId: number | bigint | null }>, unknown];
|
||||
let nextId = Number(maxIdResult[0]?.maxId ?? 0) + 1;
|
||||
|
||||
// Skip entries whose sprite id no longer exists in items_base (e.g. because
|
||||
// a duplicate-classname merge ran between the audit snapshot and this
|
||||
// generation). Inserting them would create orphaned catalog_items rows.
|
||||
const [validIds] = (await db.execute(sql`
|
||||
SELECT id FROM items_base
|
||||
`)) as unknown as [Array<{ id: number }>, unknown];
|
||||
const validItemIds = new Set(validIds.map((r) => Number(r.id)));
|
||||
let skipped = 0;
|
||||
|
||||
// Build a map of caption_save → pageId so multiple items in the same
|
||||
// category share one page (and we emit the page INSERT only once).
|
||||
const pageMap = new Map<string, number>();
|
||||
|
||||
let generated = 0;
|
||||
for (const entry of entries) {
|
||||
if (!validItemIds.has(entry.itemId)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
let pageId: number;
|
||||
if (organizeSql && parentPageId) {
|
||||
const catKey = classifyFurni(entry.classname, entry.itemType);
|
||||
@@ -367,7 +381,7 @@ export async function generateCatalogSql(
|
||||
const newId = nextPageId++;
|
||||
pageMap.set(captionSave, newId);
|
||||
statements.push(
|
||||
`INSERT INTO catalog_pages (id, caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser) VALUES (${newId}, '${escSqlLiteral(captionSave)}', '${escSqlLiteral(caption)}', 'default_3x3', ${parentPageId}, '1', ${catInfo.order}, ${catInfo.icon}, '1', '1', '', '', '', '', '', '', '');`,
|
||||
`INSERT INTO catalog_pages (id, caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser) VALUES (${newId}, '${escSqlLiteral(captionSave)}', '${escSqlLiteral(caption)}', 'default_3x3', ${parentPageId}, '1', ${catInfo.order}, ${catInfo.icon}, '1', '1', '', '', '', '', '', '', '', '');`,
|
||||
);
|
||||
}
|
||||
existing = pageMap.get(captionSave);
|
||||
@@ -394,7 +408,7 @@ export async function generateCatalogSql(
|
||||
}
|
||||
|
||||
statements.push("COMMIT;");
|
||||
return { statements, sql: statements.join("\n"), count: generated };
|
||||
return { statements, sql: statements.join("\n"), count: generated, skipped };
|
||||
}
|
||||
|
||||
/** Execute generated INSERT statements directly against the database. */
|
||||
|
||||
Reference in new issue
Block a user