refactor(sync): no mass backfill — identities are stamped on what is touched

Correcting an over-design of my own. I had contacts given an identity in bulk
the first time synchronisation was switched on, and a full dedupe-key map of the
logbook rebuilt on every pass, both sized for 123 000 contacts.

Neither is needed, because the change log starts EMPTY. Contacts logged before
synchronisation was switched on are never in anyone's log and so are never
exchanged; an identity is stamped only on a contact that is actually logged,
edited or deleted from then on. Seeding a second machine with the existing
history is a one-time copy of the database or an ADIF import — not something
synchronisation should be doing, and not something it can do from a file that
starts empty.

One case survives, and it is why the dedupe key stays. Two machines can already
hold the SAME old contact, the second seeded by that copy or import, with
different row ids and no identity on either. The day one of them edits it, it
stamps an identity and sends a change naming it; the other has never seen that
identity and would insert a duplicate. Matching on the contact itself —
callsign, minute, band, mode, the importer's own key — recognises it.

So that became a targeted lookup through idx_qso_callsign, run only when an
identity is unknown, instead of a whole-table map rebuilt each pass. Rare work,
priced as rare work.
This commit is contained in:
2026-08-17 00:42:59 +02:00
parent 747c2b9105
commit c48294b0ca
3 changed files with 61 additions and 146 deletions
+25 -50
View File
@@ -108,63 +108,38 @@ func TestIDBySyncUID(t *testing.T) {
}
}
// Backfill: batched, and it must never overwrite an identity already there.
func TestAssignSyncUIDsOnlyFillsTheEmptyOnes(t *testing.T) {
r := openRepo(t)
ctx := context.Background()
base := time.Date(2026, 8, 16, 14, 0, 0, 0, time.UTC)
a := addQSO(t, r, "M0AAA", base)
b := addQSO(t, r, "M0BBB", base.Add(time.Minute))
_ = r.SetSyncUID(ctx, a, "already")
n, err := r.CountWithoutSyncUID(ctx)
if err != nil || n != 1 {
t.Fatalf("CountWithoutSyncUID = %d (%v), want 1", n, err)
}
ids, err := r.NeedSyncUID(ctx, 100)
if err != nil || len(ids) != 1 || ids[0] != b {
t.Fatalf("NeedSyncUID = %v (%v), want [%d]", ids, err, b)
}
// Try to overwrite BOTH; only the empty one may take.
if err := r.AssignSyncUIDs(ctx, map[int64]string{a: "stomp", b: "fresh"}); err != nil {
t.Fatalf("AssignSyncUIDs: %v", err)
}
qa, _ := r.GetByID(ctx, a)
qb, _ := r.GetByID(ctx, b)
if qa.SyncUID != "already" {
t.Errorf("an existing identity was overwritten: %q", qa.SyncUID)
}
if qb.SyncUID != "fresh" {
t.Errorf("the empty one was not filled: %q", qb.SyncUID)
}
if n, _ := r.CountWithoutSyncUID(ctx); n != 0 {
t.Errorf("%d contacts still without an identity", n)
}
}
// Two machines holding the SAME imported log must not copy it to each other.
// An incoming contact with an unknown identity but a matching dedupe key is the
// same contact, and adopts the incoming id rather than being inserted twice.
func TestSyncUIDByDedupeKeyFindsTheSameContact(t *testing.T) {
// Two machines can already hold the SAME old contact — the second was seeded by
// copying the database or importing an ADIF — with different row ids and no
// identity on either. The day one of them edits it, it stamps an identity and
// sends a change naming it; the other has never seen that identity and would
// insert a duplicate. Matching on the contact itself recognises it instead.
func TestIDByDedupeKeyRecognisesTheSameContact(t *testing.T) {
r := openRepo(t)
ctx := context.Background()
when := time.Date(2026, 8, 16, 14, 32, 0, 0, time.UTC)
id := addQSO(t, r, "M0ABC", when)
byKey, err := r.SyncUIDByDedupeKey(ctx)
if err != nil {
t.Fatalf("SyncUIDByDedupeKey: %v", err)
minute := when.UTC().Format("2006-01-02T15:04")
gotID, uid, found, err := r.IDByDedupeKey(ctx, "M0ABC", minute, "20m", "CW")
if err != nil || !found {
t.Fatalf("IDByDedupeKey = (%d,%q,%v,%v), want it found", gotID, uid, found, err)
}
key := DedupeKey("M0ABC", when.UTC().Format("2006-01-02T15:04"), "20m", "CW")
hit, ok := byKey[key]
if !ok {
t.Fatalf("key %q not found among %d entries", key, len(byKey))
if gotID != id {
t.Errorf("resolved to id %d, want %d", gotID, id)
}
if hit.ID != id {
t.Errorf("key resolved to id %d, want %d", hit.ID, id)
if uid != "" {
t.Errorf("uid = %q, want empty until something stamps it", uid)
}
if hit.UID != "" {
t.Errorf("UID = %q, want empty before any stamping", hit.UID)
// A contact this machine does not have must NOT match something else.
if _, _, found, _ := r.IDByDedupeKey(ctx, "M0ABC", minute, "40m", "CW"); found {
t.Error("a different band matched — the key must be all four parts")
}
if _, _, found, _ := r.IDByDedupeKey(ctx, "M0XYZ", minute, "20m", "CW"); found {
t.Error("a different callsign matched")
}
// An empty key must never match anything.
if _, _, found, _ := r.IDByDedupeKey(ctx, "", "", "", ""); found {
t.Error("an empty key matched a row")
}
}