-
Notifications
You must be signed in to change notification settings - Fork 74
Add contacts API #2817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add contacts API #2817
Conversation
How to use the Graphite Merge QueueAdd the label mergequeue to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
|
Claude finished @rygine's task —— View job Code Review Complete ✓Review Date: January 2, 2026 05:20 UTC SummaryThis is a well-architected contacts API implementation with comprehensive FTS5 search, proper database normalization, and device sync integration. The code quality is generally high with good test coverage. I've identified several important issues not already covered in previous reviews: Critical Issues1. Cursor Not Advanced in Backup Stream
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2817 +/- ##
==========================================
+ Coverage 73.95% 74.27% +0.32%
==========================================
Files 393 394 +1
Lines 50208 51008 +800
==========================================
+ Hits 37133 37888 +755
- Misses 13075 13120 +45 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
1c385c9 to
6cc7bf5
Compare
6cc7bf5 to
5653ab8
Compare
44dedf8 to
7d3fa88
Compare
aa79461 to
bf691b1
Compare
bf691b1 to
cd1cace
Compare
cd1cace to
68e5523
Compare
| WHERE contacts_fts MATCH ?", | ||
| ) | ||
| } else { | ||
| String::from("SELECT * FROM contact_list WHERE 1=1") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FTS search uses cl alias, but is_favorite and ORDER BY columns aren’t qualified, which can be ambiguous. Consider aliasing the non-search path and qualifying these with cl. (e.g., cl.is_favorite, cl.display_name).
🚀 Want me to fix this? Reply ex: "fix it for me".
Add a contacts API with database tables, FTS triggers, and device sync import/export across
xmtp_mlsandxmtp_dbIntroduce
contactsCRUD inxmtp_mls, add ORM models and migrations for contact data with FTS5 and JSON view inxmtp_db, and integrate contacts into backup/export/import and FFI/device sync.📍Where to Start
Start with the
contactshigh-level API in xmtp_mls/src/contacts/mod.rs, then follow database interactions in xmtp_db/src/encrypted_store/contacts/mod.rs and the migration in xmtp_db/migrations/2025-12-28-000000_create_contacts/up.sql.Changes since #2817 opened
contacts::DbConnection<C>QueryContactsimplementation to return inserted rows directly usingget_result, checkrows_affectedand returndiesel::result::Error::NotFoundfor updates and deletes when no rows match, and improvedget_contactsquery logic to ignore empty search strings, properly build FTS search patterns, and clamp limit/offset to non-negative i64 bounds [324e0b6]contacts::FullContactFrom<RawFullContact>implementation to deserialize companion field JSON with error handling that logs warnings includinginbox_idcontext and defaults to empty vectors on parse failure [324e0b6]client::Clientimplementation to new dedicatedcontacts::clientmodule [324e0b6]📊 Macroscope summarized 324e0b6. 20 files reviewed, 25 issues evaluated, 24 issues filtered, 1 comment posted
🗂️ Filtered Issues
xmtp_archive/src/export_stream/contact_save.rs — 0 comments posted, 1 evaluated, 1 filtered
backup_recordsfunction never updatesstate.cursor, causing an infinite loop when called repeatedly. Each call loads the same cursor value (line 14), fetches the same batch of records, but never increments the cursor. The caller likely expects the cursor to advance after each batch. [ Already posted ]xmtp_db/src/encrypted_store/contacts/addresses.rs — 0 comments posted, 1 evaluated, 1 filtered
From<AddressData> for NewContactAddresssetscontact_id: 0with a comment saying it will be set later. If the caller forgets to set the correctcontact_idbefore insertion, the address will be associated with contact ID 0, which may not exist or may be the wrong contact. [ Low confidence ]xmtp_db/src/encrypted_store/contacts/mod.rs — 1 comment posted, 5 evaluated, 4 filtered
query.offsetisSomebutquery.limitisNone, the generated SQL will containOFFSET Xwithout a precedingLIMITclause. SQLite requires LIMIT when OFFSET is used, causing a runtime database error. [ Already posted ]contacts_paged, thelimitandoffsetparameters are directly formatted into the SQL string without validation. If negative values are passed, SQLite will fail with an error like "LIMIT clause must not be negative". This is inconsistent withget_contactswhich clamps these values to non-negative using.clamp(0, i64::MAX). [ Already posted ]updated_at_ns > original_created_at) with a 20ms sleep, but this could still be flaky if the system clock has low resolution or if theupdated_at_nsis set before theupdate_contactcall rather than during it. Ifupdate_contactdoesn't actually update theupdated_at_nsfield, the assertion on line 1224 will fail. [ Already posted ]results[0],results[1], andresults[2]without first asserting thatresults.len() == 3. Ifget_contactsreturns fewer than 3 items due to a bug in the underlying implementation, the test will panic with an index out of bounds error rather than providing a clear assertion failure message indicating the actual vs expected count. [ Low confidence ]xmtp_mls/src/contacts/mod.rs — 0 comments posted, 10 evaluated, 10 filtered
updatemethod modifies the contact in the database but does not update theContactstruct's fields (e.g.,display_name,first_name, etc.). After callingupdate(), theContactinstance holds stale data that no longer matches the database, which can lead to inconsistent state if the caller continues using the sameContactobject. [ Low confidence ]addresses()method usesfilter_mapwithContactAddress::new, which silently drops addresses when the constructor returnsNone. If valid address data in the database fails to construct aContactAddressfor any reason, it will be silently omitted from the results with no warning or error. [ Already posted ]phones[0]will be the first phone number added ("555-1234"), but unlessphone_numbers()guarantees insertion order, this test could be flaky. Many storage backends do not guarantee retrieval order matches insertion order. The assertion on line 304-305 may intermittently fail if the underlying implementation returns phone numbers in a different order (e.g., by ID, alphabetically, or non-deterministically). [ Already posted ]updated_phones[0]without first verifying thatupdated_phonesis non-empty could cause a panic with an unclear index-out-of-bounds error if the update operation unexpectedly removes the phone number entry. Addingassert_eq!(updated_phones.len(), 1);before accessing the element would make test failures more diagnostic. [ Low confidence ]emails[0],updated[0].email, andupdated[0].delete()at lines 489, 494, and 496 will panic with an unclear "index out of bounds" error if theemails()call returns an empty vector. While this is test code and panicking indicates a bug, the error message would be unhelpful. Consider usingassert!(!emails.is_empty())before accessing or usingemails.first().expect("email should exist")for clearer failure messages. [ Already posted ]urls[0]without first asserting that the vector is non-empty. Ifadd_urlfails to add the URL (despite returningOk), this will panic with an index out of bounds error rather than a meaningful test failure. Consider usingassert_eq!(urls.len(), 1)before indexing or usingurls.first().expect("expected one url"). [ Low confidence ]updated[0]without verifying the vector is non-empty. If the update operation unexpectedly removes the URL or the refresh logic has a bug, this will panic with an unclear index out of bounds error. Adding an explicit length assertion before indexing would provide clearer test diagnostics. [ Already posted ]wallets[0]on line 549 assumesadd_wallet_addresssucceeded and returned a non-empty collection. If the wallet wasn't added (e.g., due to a silent failure or race condition), this would panic rather than providing a meaningful test failure message. Consider usingwallets.first().expect("wallet should exist after add")or an explicit assertion onwallets.len()before accessing. [ Low confidence ]addrs[0]on line 583 could panic ifcontact.addresses()returns an empty vector. While the test expects one address afteradd_address, if the underlying implementation has a bug or the add fails silently, this would cause an unclear panic rather than a meaningful test failure. Consider usingaddrs.first().expect("expected one address after add")or adding an assertion likeassert_eq!(addrs.len(), 1)before indexing. [ Low confidence ]updated[0]on lines 592, 593, and 595 could panic if the vector is empty. Adding an explicit length assertion before indexing would make test failures more informative. [ Already posted ]xmtp_mls/src/contacts/url.rs — 0 comments posted, 1 evaluated, 1 filtered
updatemethod modifies the database but does not update the struct'surlandlabelfields. After callingupdate(), theContactUrlinstance will contain stale values that don't match the database state. Callers accessingself.urlorself.labelafter an update will see the old values instead of the newly updated ones. [ Low confidence ]xmtp_mls/src/groups/device_sync/contact_sync.rs — 0 comments posted, 6 evaluated, 6 filtered
import_contactsfunction logs warnings for failed contact imports but continues processing. If many contacts fail to import due to a systemic issue (e.g., database corruption), the function will return a partial success count without indicating the failure rate, potentially masking serious problems. [ Low confidence ]import_single_contactfunction comparescontact_save.updated_at_nswithexisting_contact.updated_at_nsto determine if an update should occur, but if the source device's clock is behind, valid updates may be silently ignored. Additionally, ifupdated_at_nsis 0 or unset in the proto, this comparison may incorrectly skip updates. [ Low confidence ]delete_all_child_datafunction at line 116-119 only deletes addresses that haveSome(id), butAddressData::idcould beNonefor addresses returned fromget_addresses. If the database returns addresses withid: None, those addresses will not be deleted, leaving orphaned data. [ Low confidence ]assert_eq!(imported_count, 1)on line 324 may be incorrect. Ifimport_contactsreturns the count of contacts that were actually updated/imported, and the test verifies that the contact was NOT updated (because the existing one is newer), thenimported_countshould be 0, not 1. This assertion either tests the wrong expected value or indicates a misunderstanding of whatimport_contactsreturns, potentially causing the test to pass with incorrect behavior. [ Low confidence ]xmtp_mls/src/mls_store.rs — 0 comments posted, 1 evaluated, 1 filtered
find_groups_with_inbox_ids, the call toload_mls_group_with_lock(...).ok()on line 169 silently converts all errors toNone, causing groups that fail to load (due to transient errors, database locks, or corruption) to be silently excluded from results. This means a temporary failure could cause matching groups to be incorrectly omitted without any error indication to the caller. [ Already posted ]