perf: round 21 — CalDAV/CardDAV row-mapper pre-size, dedup hash-bind & digest-key dedup, CardDAV etag/BDAY emit, NC trashbin content-type

Round 21 of the benchmark-gated perf sweep. Six behaviour-preserving,
allocation-reducing changes, each with a BEFORE/AFTER counting-allocator
section in examples/bench_round21_micro.rs and a byte/-value equivalence
gate; all six pass their deterministic alloc gate (a non-winning AFTER
exits 1 = rollback).

- R1: pre-size the 16 CalDAV/CardDAV row-mapper Vecs (+1 HashMap) with
  Vec::with_capacity(rows.len()) — the ROUND20 §I1 file-side pattern
  extended to the calendar/contact repos it deferred. 7 → 1 allocs/op.
- R2: settle_batch binds a borrowed Vec<&str> instead of cloning every
  chunk hash into a Vec<String> (sqlx encodes &[&str] as text[]
  identically; favorites_pg_repository.rs:271 precedent). 33 → 1 allocs/op,
  39x wall.
- R3: store_loose_chunks keys its intra-request dedup set on the raw
  [u8;32] BLAKE3 digest and moves the hex on a duplicate (the ROUND17 §D2
  pattern applied to the delta-upload sibling). 401 → 209 allocs/op.
- R4: CardDAV getetag emits borrowed pre-escaped &quot; quotes via a shared
  write_quoted_etag helper (ROUND20 §C1 pattern, all 4 CardDAV etag sites).
  3 → 0 allocs/op.
- R5: BDAY stamped via the new fmt::compact_date stack renderer instead of
  chrono's strftime interpreter (chrono fallback out of the 4-digit-year
  range; byte-identical, unit-tested vs chrono). 2 → 0 allocs/op, 10.5x wall.
- R6: NC trashbin folder content-type via Cow::Borrowed instead of
  .to_string() on the constant (ROUND16 §M1 pattern). 1 → 0 allocs/op.

See benches/ROUND21.md for the full write-up and the deferred-items list
(HeaderMap-clone hot handlers, Query→typed-struct, WebDAV dead-props
HashSet, and others surfaced by the audit that want their own validated
pass). Validated: cargo fmt, cargo clippy --features bench --all-targets
-D warnings, cargo test --lib --features test_utils (529 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gHVq5Wy2TzdWeSqtEmK6m
This commit is contained in:
Claude
2026-07-20 08:48:42 +00:00
parent aec9b8f037
commit 77f13ac643
11 changed files with 910 additions and 55 deletions
+36 -14
View File
@@ -17,6 +17,21 @@ use crate::application::adapters::webdav_adapter::{
use crate::application::dtos::address_book_dto::AddressBookDto;
use crate::application::dtos::contact_dto::ContactDto;
/// Emit a WebDAV `getetag` value as `"…"` with the surrounding quotes written
/// as borrowed pre-escaped `&quot;` text events around the escaped etag body.
///
/// Byte-identical to escaping the whole `"{etag}"` String — `quick_xml` escapes
/// a literal `"` to `&quot;`, so the one-String form re-allocated an owned `Cow`
/// on write — but with **0 heap allocs per contact** (the NextCloud
/// `write_etag_element` pattern, benches/ROUND20.md §C1). Called per contact on
/// the CardDAV multiget/PROPFIND emit path.
fn write_quoted_etag<W: Write>(xml_writer: &mut Writer<W>, etag: &str) -> Result<()> {
xml_writer.write_event(Event::Text(BytesText::from_escaped("&quot;")))?;
xml_writer.write_event(Event::Text(BytesText::new(etag)))?;
xml_writer.write_event(Event::Text(BytesText::from_escaped("&quot;")))?;
Ok(())
}
/// Render a requested property as a namespaced response element name, mapping
/// the known namespaces to their response prefixes (`D:` for DAV, `CR:` for
/// CardDAV). Used for the catch-all arms of the requested-property writers so
@@ -386,7 +401,7 @@ impl CardDavAdapter {
// getetag
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
xml_writer.write_event(Event::Text(BytesText::new(&format!("\"{}\"", book.id))))?;
write_quoted_etag(xml_writer, &book.id)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
// getcontenttype
@@ -468,8 +483,7 @@ impl CardDavAdapter {
}
("DAV:", "getetag") => {
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
xml_writer
.write_event(Event::Text(BytesText::new(&format!("\"{}\"", book.id))))?;
write_quoted_etag(xml_writer, &book.id)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
}
("DAV:", "getcontenttype") => {
@@ -755,11 +769,7 @@ impl CardDavAdapter {
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
let mut quoted = String::with_capacity(contact.etag.len() + 2);
quoted.push('"');
quoted.push_str(&contact.etag);
quoted.push('"');
xml_writer.write_event(Event::Text(BytesText::new(&quoted)))?;
write_quoted_etag(xml_writer, &contact.etag)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
@@ -779,11 +789,7 @@ impl CardDavAdapter {
}
("DAV:", "getetag") => {
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
let mut quoted = String::with_capacity(contact.etag.len() + 2);
quoted.push('"');
quoted.push_str(&contact.etag);
quoted.push('"');
xml_writer.write_event(Event::Text(BytesText::new(&quoted)))?;
write_quoted_etag(xml_writer, &contact.etag)?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
}
("DAV:", "getcontenttype") => {
@@ -1017,7 +1023,23 @@ pub fn contact_to_vcard(contact: &ContactDto) -> String {
}
}
if let Some(bday) = &contact.birthday {
let _ = write!(vcard, "BDAY:{}\r\n", bday.format("%Y-%m-%d"));
// Stack render (byte-identical to chrono's `%Y-%m-%d`) with the chrono
// fallback for out-of-range years — drops the strftime interpreter + a
// heap alloc per contact-with-birthday (fmt::compact_date is the
// date-only companion to the §V2 REV renderer above).
use chrono::Datelike as _;
let mut bday_buf = [0u8; 10];
match crate::common::fmt::compact_date(&mut bday_buf, bday.year(), bday.month(), bday.day())
{
Some(s) => {
vcard.push_str("BDAY:");
vcard.push_str(s);
vcard.push_str("\r\n");
}
None => {
let _ = write!(vcard, "BDAY:{}\r\n", bday.format("%Y-%m-%d"));
}
}
}
if let Some(photo) = &contact.photo_url {
let _ = write!(vcard, "PHOTO;VALUE=URI:{}\r\n", photo);