From 705f9c38970626bb012c8b80aff2126d8735b1aa Mon Sep 17 00:00:00 2001 From: Daan Meijer Date: Tue, 20 Oct 2026 18:08:13 +0200 Subject: [PATCH] Release keys numbered after the highest number, not the count Merges delete releases, so the count of a key's releases fell behind its numbers (83 releases up to key:129): the 20 numbers tried were all taken and the range's batch failed for good. Spot releases (spot:key) are not numbered. Co-Authored-By: Claude Opus 5.5 --- app/services/batch_store.ts | 12 ++++++++++-- tests/functional/batch_store.spec.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/services/batch_store.ts b/app/services/batch_store.ts index de0cae5..f4ebd8a 100644 --- a/app/services/batch_store.ts +++ b/app/services/batch_store.ts @@ -246,8 +246,16 @@ class BatchStore { } const fresh = part.filter((_, idx) => !joined.has(idx)) if (fresh.length > 0) { - // The first upload of a key keeps the key; later ones are key:2, key:3, … - const { rows: counts } = await trx.rawQuery(`SELECT identity_key, count(*) AS n FROM releases WHERE identity_key = ANY(?) GROUP BY 1`, [[...new Set(fresh.map((r) => r.key))]]) + // The first upload of a key keeps the key; later ones are key:2, key:3, … after the highest number: merges + // delete releases, and a count fell behind it (83 releases of a key up to key:129: 20 numbers tried, all + // taken, the range's batch failed for good; LOG.md 2026-10-20 (89)). Spot releases (spot:key) aren't numbered. + const { rows: counts } = await trx.rawQuery( + `SELECT identity_key, max(CASE WHEN release_key = identity_key THEN 1 + WHEN left(release_key, length(identity_key) + 1) = identity_key || ':' AND substr(release_key, length(identity_key) + 2) ~ '^[0-9]{1,9}$' + THEN substr(release_key, length(identity_key) + 2)::int END) AS n + FROM releases WHERE identity_key = ANY(?) GROUP BY 1`, + [[...new Set(fresh.map((r) => r.key))]] + ) const taken = new Map(counts.map((c: { identity_key: string; n: string }) => [c.identity_key, Number(c.n)])) const numbered = (r: ReleaseChunk) => { const n = (taken.get(r.key) ?? 0) + 1 diff --git a/tests/functional/batch_store.spec.ts b/tests/functional/batch_store.spec.ts index 0954d9c..3b7bf46 100644 --- a/tests/functional/batch_store.spec.ts +++ b/tests/functional/batch_store.spec.ts @@ -159,6 +159,18 @@ test.group('BatchStore', (group) => { assert.deepEqual(keys, [first.identity_key, `${first.identity_key}:3`]) }) + test('a re-upload is numbered after the highest number, not into the holes merges left', async ({ assert }) => { + for (let day = 0; day < 4; day++) { + await storeIn(GROUP, 1 + day * 100_000, upload(10 + day * 100_000, 'Some.Show.S01E01.1080p.mkv', 2, 'Some.Show.S01E01.1080p [1/1]', `2026-10-0${day * 2 + 1}T10:00:00Z`)) + } + const [first] = await releasesOf() + // Merged away: key:2 and key:3 gone, key and key:4 left. + await db.from('releases').whereIn('release_key', [`${first.identity_key}:2`, `${first.identity_key}:3`]).delete() + await storeIn(GROUP, 400_001, upload(400_010, 'Some.Show.S01E01.1080p.mkv', 2, 'Some.Show.S01E01.1080p [1/1]', '2026-10-09T10:00:00Z')) + const keys = (await releasesOf()).map((r) => r.release_key.split(':')[1] ?? '1') + assert.deepEqual(keys, ['1', '4', '5']) + }) + test('a small release of sidecars only is marked for the sidecar fold', async ({ assert }) => { await storeIn(GROUP, 1_000_000, [ ...upload(1_000_000, 'Murdoch Mysteries S8D1.nzb', 1, '[00/80]', '2026-10-05T10:00:00Z'),