One-time cleanup for anyone already hit by the reimport-duplication bug: groups existing rows by (source, raw_uid) -- backfilling a content-based key first for rows that had none -- keeps the row a meeting is attached to (or the first survivor otherwise), repoints any meeting pointing at a row about to be removed, then deletes the rest. The unique index is dropped and recreated around this since the backfill step can momentarily produce rows that collide before they're deduped.
69 lines
2.9 KiB
SQL
69 lines
2.9 KiB
SQL
-- Bug fix: prior imports could accumulate many duplicate rows for the same
|
|
-- real calendar event on every re-import -- either because the (source,
|
|
-- raw_uid) unique index (added in 0005) never retroactively deduped rows
|
|
-- that existed before it, or because an event with no UID in its source data
|
|
-- got `raw_uid = NULL`, which that index's `WHERE raw_uid IS NOT NULL` clause
|
|
-- explicitly exempts from uniqueness, so it duplicated on every single
|
|
-- re-import forever. This collapses whatever's already in the table, then
|
|
-- backfills a stable content-based key for anything still missing a UID so
|
|
-- future re-imports resolve to the same row instead of minting a new one
|
|
-- (see calendar::content_uid in src/calendar/mod.rs, which produces the
|
|
-- identical 'content:subject|organizer|starts_at|ends_at' format used here).
|
|
PRAGMA foreign_keys = ON;
|
|
|
|
-- Drop the index first: the backfill below can momentarily produce rows that
|
|
-- share a (source, raw_uid) pair before they're deduped a few statements
|
|
-- later, which the index would reject mid-UPDATE.
|
|
DROP INDEX IF EXISTS idx_calendar_events_source_uid;
|
|
|
|
UPDATE calendar_events
|
|
SET raw_uid = 'content:' || COALESCE(subject, '') || '|' || COALESCE(organizer, '')
|
|
|| '|' || COALESCE(starts_at, 0) || '|' || COALESCE(ends_at, 0)
|
|
WHERE raw_uid IS NULL;
|
|
|
|
-- One survivor per (source, raw_uid) group: whichever row a meeting is
|
|
-- already attached to (so `attach_meeting_to_event` links don't break), else
|
|
-- the lexicographically-first id (arbitrary but deterministic).
|
|
CREATE TEMP TABLE calendar_event_survivors AS
|
|
SELECT source, raw_uid, MIN(id) AS keep_id
|
|
FROM calendar_events
|
|
GROUP BY source, raw_uid;
|
|
|
|
UPDATE calendar_event_survivors
|
|
SET keep_id = (
|
|
SELECT m.calendar_event_id FROM meetings m
|
|
JOIN calendar_events ce ON ce.id = m.calendar_event_id
|
|
WHERE ce.source = calendar_event_survivors.source
|
|
AND ce.raw_uid = calendar_event_survivors.raw_uid
|
|
LIMIT 1
|
|
)
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM meetings m
|
|
JOIN calendar_events ce ON ce.id = m.calendar_event_id
|
|
WHERE ce.source = calendar_event_survivors.source
|
|
AND ce.raw_uid = calendar_event_survivors.raw_uid
|
|
);
|
|
|
|
-- Repoint any meeting attached to a duplicate that's about to be deleted
|
|
-- onto the group's survivor instead.
|
|
UPDATE meetings
|
|
SET calendar_event_id = (
|
|
SELECT s.keep_id FROM calendar_event_survivors s
|
|
JOIN calendar_events ce ON ce.source = s.source AND ce.raw_uid = s.raw_uid
|
|
WHERE ce.id = meetings.calendar_event_id
|
|
)
|
|
WHERE calendar_event_id IN (
|
|
SELECT ce.id FROM calendar_events ce
|
|
JOIN calendar_event_survivors s ON ce.source = s.source AND ce.raw_uid = s.raw_uid
|
|
WHERE ce.id != s.keep_id
|
|
);
|
|
|
|
-- Drop the duplicates (cascades to calendar_event_participants).
|
|
DELETE FROM calendar_events
|
|
WHERE id NOT IN (SELECT keep_id FROM calendar_event_survivors);
|
|
|
|
DROP TABLE calendar_event_survivors;
|
|
|
|
CREATE UNIQUE INDEX idx_calendar_events_source_uid ON calendar_events(source, raw_uid)
|
|
WHERE raw_uid IS NOT NULL;
|