Files
Oxicloud/src/domain/repositories/contact_repository.rs
T
Claude 9729f033b2 perf: round 6 backend — CardDAV cursor streaming, borrowed NC id chain, binary UUID decode, one-alloc hex
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
2026-07-18 09:03:33 +00:00

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>>;
}