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:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user