9729f033b2
Benchmark-gated (equivalence + BEFORE/AFTER in examples/bench_*, results and reproduce commands in benches/ROUND6.md): - CardDAV whole-book REPORT + depth-1 PROPFIND stream through a PG cursor (stream_contacts_by_book, 500-contact pages) instead of materialising every vCard twice: 8 000 contacts TTFB 37.4 → 7.6 ms (4.9x), peak heap 19.0 → 7.0 MiB (2.7x), wall -23%; REPORT and PROPFIND byte-identical to the buffered writers. - NC numeric-id chain fully borrowed: get_or_create_file_ids/folder_ids take &[&str] and return HashMap<Uuid, i64>; batch_resolve_ids callers (PROPFIND pages, REPORT, trashbin, OCS search) pass id slices and look up via nc_id_of. 2.006 → 0.006 allocs/child (334x), 1.53x wall per 500-child page. batch_check_favorites binds &[&str] as text[]. - file_blob_read_repository listing SELECTs drop id::text/folder_id::text server casts: rows decode binary Uuid (16 vs 36 bytes on the wire) and render once in row_to_file. A/B on 500-row pages: 1.225 → 1.044 ms mean (1.17x), p95 1.686 → 1.345 (bench_uuid_text_cast; single-row, param and min() sites left as-is deliberately). - IncrementalHasher::finalize_hex renders through common::fmt::hex_lower instead of one format! per digest byte: 18 → 1 (md5) / 35 → 1 (sha256) allocs per chunk finalize, 14-15x wall. - Share landing overlaps the access-count UPDATE with the unlock fetch via tokio::join! (one round-trip off every public link hit). - REJECTED by benchmark and reverted: try_join_all fan-out of the batch-favorites authz pre-check — 42.6 → 56.4 ms cold, 0.15 → 0.23 ms warm against local-socket PG (bench_favorites_authz kept as evidence). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
84 lines
3.4 KiB
Rust
84 lines
3.4 KiB
Rust
use std::result::Result;
|
|
use uuid::Uuid;
|
|
|
|
use crate::common::errors::DomainError;
|
|
use crate::domain::entities::contact::{Contact, ContactGroup};
|
|
|
|
pub type ContactRepositoryResult<T> = Result<T, DomainError>;
|
|
|
|
pub trait ContactRepository: Send + Sync + 'static {
|
|
async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>;
|
|
async fn update_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>;
|
|
async fn delete_contact(&self, id: &Uuid) -> ContactRepositoryResult<()>;
|
|
async fn get_contact_by_id(&self, id: &Uuid) -> ContactRepositoryResult<Option<Contact>>;
|
|
async fn get_contact_by_uid(
|
|
&self,
|
|
address_book_id: &Uuid,
|
|
uid: &str,
|
|
) -> ContactRepositoryResult<Option<Contact>>;
|
|
/// Fetches the contacts matching any of the given vCard UIDs in one
|
|
/// indexed query (`uid = ANY(...)`). Used by CardDAV multiget so a
|
|
/// request for a handful of contacts never pays for the whole book.
|
|
/// UIDs with no matching contact are silently absent from the result.
|
|
async fn get_contacts_by_uids(
|
|
&self,
|
|
address_book_id: &Uuid,
|
|
uids: &[String],
|
|
) -> ContactRepositoryResult<Vec<Contact>>;
|
|
/// Cursor stream over every contact of the book in the listing
|
|
/// order (`full_name, first_name, last_name`) — ONE scan+sort on
|
|
/// the server; the streaming CardDAV emitters page over it.
|
|
fn stream_contacts_by_book(
|
|
&self,
|
|
address_book_id: Uuid,
|
|
) -> futures::stream::BoxStream<'static, ContactRepositoryResult<Contact>>;
|
|
|
|
async fn get_contacts_by_address_book(
|
|
&self,
|
|
address_book_id: &Uuid,
|
|
) -> ContactRepositoryResult<Vec<Contact>>;
|
|
/// Same as [`Self::get_contacts_by_address_book`] but bounded by
|
|
/// `LIMIT`/`OFFSET` for paginated listings.
|
|
async fn get_contacts_by_address_book_paginated(
|
|
&self,
|
|
address_book_id: &Uuid,
|
|
limit: i64,
|
|
offset: i64,
|
|
) -> ContactRepositoryResult<Vec<Contact>>;
|
|
async fn get_contacts_by_email(&self, email: &str) -> ContactRepositoryResult<Vec<Contact>>;
|
|
async fn get_contacts_by_group(&self, group_id: &Uuid)
|
|
-> ContactRepositoryResult<Vec<Contact>>;
|
|
async fn search_contacts(
|
|
&self,
|
|
address_book_id: &Uuid,
|
|
query: &str,
|
|
) -> ContactRepositoryResult<Vec<Contact>>;
|
|
}
|
|
|
|
pub trait ContactGroupRepository: Send + Sync + 'static {
|
|
async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup>;
|
|
async fn update_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup>;
|
|
async fn delete_group(&self, id: &Uuid) -> ContactRepositoryResult<()>;
|
|
async fn get_group_by_id(&self, id: &Uuid) -> ContactRepositoryResult<Option<ContactGroup>>;
|
|
async fn get_groups_by_address_book(
|
|
&self,
|
|
address_book_id: &Uuid,
|
|
) -> ContactRepositoryResult<Vec<ContactGroup>>;
|
|
async fn add_contact_to_group(
|
|
&self,
|
|
group_id: &Uuid,
|
|
contact_id: &Uuid,
|
|
) -> ContactRepositoryResult<()>;
|
|
async fn remove_contact_from_group(
|
|
&self,
|
|
group_id: &Uuid,
|
|
contact_id: &Uuid,
|
|
) -> ContactRepositoryResult<()>;
|
|
async fn get_contacts_in_group(&self, group_id: &Uuid)
|
|
-> ContactRepositoryResult<Vec<Contact>>;
|
|
async fn get_groups_for_contact(
|
|
&self,
|
|
contact_id: &Uuid,
|
|
) -> ContactRepositoryResult<Vec<ContactGroup>>;
|
|
}
|