Contacts sync: a card with an FN or ORG over 200 characters fails every run on PostgreSQL, and adoption leaves a new contact each time #20
Labels
No labels
bug
documentation
enhancement
performance
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-dav#20
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-dav at
97abc9779, checking every valueapply_cardwrites against the length of the field it lands in.apply_cardcuts the role, the email, the number and the URL to their fields' lengths, but not the name and not the company. Adoption creates the contact, applies the card and binds the link as three separate steps with no transaction around them. On PostgreSQL an over-long value fails the save, the run ends there, and the contact already created stays behind without a link, so the next run adopts the same card again. Reasoned from the code; not run against PostgreSQL. On SQLite, which does not enforcemax_length, the same card was adopted with a 250-character name stored in a 200-character field.src/postulo_dav/sync.py:148(contact.name = card.name or contact.name, not cut),src/postulo_dav/sync.py:152-153and:536(card.orgpassed whole toget_or_create_company),src/postulo_dav/sync.py:536-549(create, apply, bind),src/postulo_dav/sync.py:400-414(sync, contacts then interviews with nothing between).Contact.nameandCompany.namearemax_length=200(jobs/models.py:622,:245).FN.Contact.objects.create(..., name=card.name[:200])succeeds.apply_cardthen assigns the uncut name andcontact.save()raises. The scheduler runs syncs outside any transaction, so the created contact is committed.SyncLink.bindnever runs, the card is still unlinked on the next run, and each run adds another contact before failing.ORG, or a longFNon a card already linked.get_or_create_companyorcontact.save()raises; no duplicate, but the run fails every time.syncbefore the calendar, so interviews stop syncing until somebody shortens the card. Related to #1 (closed), which asked that a failure in one half not sink the other; that part was not done.ATOMIC_REQUESTS,config/settings/base.py:140). A failed statement aborts that transaction on PostgreSQL, so the core's ownconnection.saveinsync_connection(plugins/syncing.py:93), which is documented as never raising, would fail as well.card.nameandcard.orgto 200 inapply_cardand atsync.py:536, as the other fields already are. Wrap the handling of one card (for adoption: create, apply, bind) intransaction.atomic(), catch a database or validation error there, and turn it into a note that names the card, so one card neither leaves a half-made contact nor ends the run. Run the contacts half and the interviews half so that an error in the first is reported and the second still runs. Related to #7, whose point 5 asks the same for a server's refusal of one item; this is the database's refusal.Done when
tests/test_dav.pyhas a test that adopts a card whoseFNandORGare each 250 characters: one contact, one link, both names at most 200 characters, and a second run that saysnothing to do.tests/test_dav.pyhas a test in which applying one card raises (for exampleapply_cardpatched to raise for that card): no contact is left without a link, the report carries a note, the other cards are adopted and an interview is still pushed in the same run.href#32Fixed by pull request #35, merged as
fb8bbd7ae.