Files
Aether/backend/pkg/db/migrations/000024_calendar_sync.up.sql
T
marcandClaude Opus 5 8fdde182b7 feat(calendar): Abgleich nur der Aenderungen (sync-collection, RFC 6578)
Bis hierher holte jeder Durchgang die volle Terminliste: ctag abfragen,
Aenderung sehen, alles neu laden. Eine verschobene Besprechung kostete
damit so viel wie ein Neuanfang -- bei einem Jahr Termine, alle fuenfzehn
Minuten, auf Akku und Datenvolumen des Telefons.

Kein zweiter Zaehler: Das ctag **ist** bereits eine Zahl je Kalender, die
bei jedem Schreiben um eins steigt. Jedes Objekt merkt sich in sync_seq den
Stand, bei dem es zuletzt angefasst wurde (Migration 24).

Grabsteine (cal_tombstones) halten geloeschte UIDs fest -- ohne sie bliebe
ein geloeschter Termin auf jedem Geraet stehen, das gerade nicht verbunden
war. Sie stehen 180 Tage; beim Raeumen wandert sync_floor mit, und ein
Token darunter bekommt 403 <valid-sync-token/> statt einer Antwort mit
fehlenden Loeschungen.

go-webdav kennt fuer REPORT nur calendar-query und calendar-multiget, und
weder sync-token noch supported-report-set. Beide gehen denselben Weg wie
getctag und calendar-color. Ohne supported-report-set probiert DAVx5
sync-collection gar nicht erst aus.

Zwei Fehler noch vor dem Ausliefern gefunden:

* Die Anfuehrungszeichen am ETag standen **innerhalb** der Maskierung --
  xml.EscapeText machte daraus &#34;. Gueltig, aber genug Clients
  vergleichen ETags roh und saehen bei jedem Durchgang eine Aenderung.
* pruneTombstones nahm die erste Zeile eines DELETE ... RETURNING als
  Maximum. Das hat keine Reihenfolge -- der sync_floor waere zu niedrig
  gesetzt worden, und ein Client dazwischen haette Loeschungen verpasst.

caldav-selbsttest.py: 62 -> 86 Pruefungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 00:03:08 +02:00

39 lines
2.0 KiB
SQL

-- Abgleich nur der Aenderungen (RFC 6578, sync-collection).
--
-- Bis hierher holte jeder Durchgang die **volle** Terminliste: Der Client
-- fragt das ctag ab, sieht es veraendert und laedt alles neu. Bei einem Jahr
-- Termine ist das jedes Mal dieselbe Menge Daten fuer eine Aenderung.
--
-- Was dafuer fehlt, ist eine Reihenfolge: "was hat sich seit Stand N getan".
-- Ein **zweiter** Zaehler waere dafuer nicht noetig -- das ctag ist bereits
-- genau das: eine Zahl je Kalender, die bei jedem Schreiben um eins steigt.
-- Sie wird deshalb hier zur Sync-Sequenz; jedes Objekt merkt sich den Stand,
-- bei dem es zuletzt angefasst wurde.
ALTER TABLE cal_objects ADD COLUMN IF NOT EXISTS sync_seq BIGINT NOT NULL DEFAULT 0;
-- Bestehende Objekte auf den heutigen Stand ihres Kalenders setzen. Ein
-- Client hat noch kein Token (die Eigenschaft gab es bis jetzt nicht), holt
-- also ohnehin einmal alles und bekommt dabei sein erstes Token.
UPDATE cal_objects o SET sync_seq = c.ctag
FROM cal_calendars c WHERE c.id = o.calendar_id;
CREATE INDEX IF NOT EXISTS cal_objects_sync ON cal_objects (calendar_id, sync_seq);
-- Grabsteine: Eine geloeschte Zeile kann nicht mehr erzaehlen, dass es sie
-- gab. Ohne sie bliebe ein geloeschter Termin auf jedem Geraet stehen, das
-- waehrend des Loeschens nicht verbunden war -- der teuerste Fehler dieser
-- Art, weil er unsichtbar ist.
CREATE TABLE IF NOT EXISTS cal_tombstones (
calendar_id UUID NOT NULL REFERENCES cal_calendars(id) ON DELETE CASCADE,
uid TEXT NOT NULL,
sync_seq BIGINT NOT NULL,
deleted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (calendar_id, uid)
);
CREATE INDEX IF NOT EXISTS cal_tombstones_sync ON cal_tombstones (calendar_id, sync_seq);
-- Bis zu diesem Stand sind Grabsteine weggeraeumt. Ein Client mit einem
-- aelteren Token bekommt eine klare Absage (<valid-sync-token/>) und macht
-- einen Vollabgleich -- besser als eine Antwort, in der Loeschungen fehlen.
ALTER TABLE cal_calendars ADD COLUMN IF NOT EXISTS sync_floor BIGINT NOT NULL DEFAULT 0;