1use std::collections::{BTreeMap, btree_map::Entry};
6
7use haematite::branch::{CommitDurability, CommitRequest, current_timestamp};
8use haematite::db::{ReadOnlyDatabase, branches::BranchDatabase};
9use haematite::tree::Hash;
10
11use super::mutations::PreparedWrite;
12use super::{
13 BranchRefRecord, BranchRefStore, BranchRegistry, Captured, Failure, MergePlan, MergeState,
14 Receipt, SERVICE, Storage, branch_name, collection_key, edges, identifier, open_branch, reads,
15 receipt_key, revision_key, version,
16};
17use crate::serve::access::Access;
18use crate::serve::protocol::{
19 AuditVerdict, AuditedBucket, Change, CountBucket, CountReason, CountsAudit, CountsRebuild,
20 KeptCount, ReceiptQuery, Version,
21};
22
23pub(super) struct View<'a> {
24 reader: &'a ReadOnlyDatabase,
25 at: &'a Captured,
26 ready: bool,
27}
28
29impl<'a> View<'a> {
30 pub(super) fn new(reader: &'a ReadOnlyDatabase, at: &'a Captured) -> Result<Self, Failure> {
31 Ok(Self {
32 reader,
33 at,
34 ready: is_ready(reader, at)?,
35 })
36 }
37
38 pub(super) const fn unavailable(&self, allowed: bool) -> Option<CountReason> {
39 if !self.ready {
40 Some(CountReason::CountsNotReady)
41 } else if !allowed {
42 Some(CountReason::Withheld)
43 } else {
44 None
45 }
46 }
47
48 pub(super) fn documents(
49 &self,
50 access: &Access,
51 collection: &str,
52 ) -> Result<KeptCount, Failure> {
53 if let Some(reason) = self.unavailable(access.can_read_collection(collection)) {
54 return Ok(KeptCount {
55 value: None,
56 reason: Some(reason),
57 });
58 }
59 Ok(KeptCount {
60 value: Some(required_documents(self.reader, self.at, collection)?),
61 reason: None,
62 })
63 }
64
65 pub(super) fn relationships(
66 &self,
67 access: &Access,
68 relation: &str,
69 ) -> Result<KeptCount, Failure> {
70 if let Some(reason) = self.unavailable(access.can_read_store()) {
71 return Ok(KeptCount {
72 value: None,
73 reason: Some(reason),
74 });
75 }
76 let key = format!("k\0e\0{relation}");
77 let value = read(self.reader, self.at, key.as_bytes())?
78 .ok_or_else(|| Failure::new("counts_corrupt", "declared relation has no count"))?;
79 Ok(KeptCount {
80 value: Some(decode(&value)?),
81 reason: None,
82 })
83 }
84}
85
86const READY_KEY: &[u8] = b"k\0ready";
87const READY_VALUE: &[u8] = b"1";
88
89#[derive(Default)]
90struct BucketCount {
91 counted: u64,
92 kept: Option<u64>,
93 present: bool,
94}
95
96impl CountBucket {
97 fn key(&self) -> Vec<u8> {
98 match self {
99 Self::Documents { collection } => document_count_key(collection),
100 Self::Relationships { relation } => format!("k\0e\0{relation}").into_bytes(),
101 Self::Vectors { collection, field } => {
102 format!("k\0v\0{collection}\0{field}").into_bytes()
103 }
104 }
105 }
106}
107
108fn counted_buckets(
109 reader: &ReadOnlyDatabase,
110 at: &Captured,
111) -> Result<BTreeMap<CountBucket, BucketCount>, Failure> {
112 let mut counts = BTreeMap::new();
113 for (prefix, parts, kind) in [
114 (b"c\0".as_slice(), 1, 0),
115 (b"n\0".as_slice(), 1, 1),
116 (b"w\0".as_slice(), 2, 2),
117 (b"d\0".as_slice(), 2, 3),
118 (b"e\0".as_slice(), 5, 4),
119 (b"v\0".as_slice(), 3, 5),
120 ] {
121 reads::scan_rows(reader, at, prefix, parts, |parts, bytes| {
122 let bucket = match kind {
123 0 | 3 => CountBucket::Documents {
124 collection: parts[0].to_owned(),
125 },
126 1 => {
127 super::relations::decode(parts[0], bytes)?;
128 CountBucket::Relationships {
129 relation: parts[0].to_owned(),
130 }
131 }
132 4 => CountBucket::Relationships {
133 relation: parts[2].to_owned(),
134 },
135 _ => {
136 if kind == 2
137 && matches!(
138 super::embedded::read_field(bytes)?,
139 super::embedded::Field::Embedded(_)
140 )
141 {
142 return Ok(());
143 }
144 CountBucket::Vectors {
145 collection: parts[0].to_owned(),
146 field: parts[1].to_owned(),
147 }
148 }
149 };
150 let counter: &mut BucketCount = counts.entry(bucket).or_default();
151 counter.present = true;
152 if kind >= 3 {
153 counter.counted = added_count(counter.counted, 1)?;
154 }
155 Ok(())
156 })?;
157 }
158 Ok(counts)
159}
160
161fn count_rows(
162 reader: &ReadOnlyDatabase,
163 at: &Captured,
164 counts: &mut BTreeMap<CountBucket, BucketCount>,
165 audit: bool,
166) -> Result<(), Failure> {
167 for (prefix, parts, kind) in [
168 (b"k\0d\0".as_slice(), 1, 0),
169 (b"k\0e\0".as_slice(), 1, 1),
170 (b"k\0v\0".as_slice(), 2, 2),
171 ] {
172 reads::scan_rows(reader, at, prefix, parts, |parts, bytes| {
173 let bucket = match kind {
174 0 => CountBucket::Documents {
175 collection: parts[0].to_owned(),
176 },
177 1 => CountBucket::Relationships {
178 relation: parts[0].to_owned(),
179 },
180 _ => CountBucket::Vectors {
181 collection: parts[0].to_owned(),
182 field: parts[1].to_owned(),
183 },
184 };
185 let counter = counts.entry(bucket).or_default();
186 if audit && counter.kept.replace(decode(bytes)?).is_some() {
187 return Err(Failure::new("counts_corrupt", "duplicate count bucket"));
188 }
189 Ok(())
190 })?;
191 }
192 Ok(())
193}
194
195impl Storage {
196 pub fn rebuild_counts(&mut self, request: &CountsRebuild) -> Result<Receipt, Failure> {
202 identifier(&request.branch)?;
203 identifier(&request.request_id)?;
204 let name = branch_name(&request.branch);
205 if self.pending_discards.contains(&name) {
206 return Err(Failure::new(
207 "counts_discard_pending",
208 "branch discard is incomplete",
209 ));
210 }
211 let record = self
212 .refs
213 .get(&name)
214 .ok_or_else(|| Failure::new("counts_branch_missing", &request.branch))?
215 .clone();
216 let fingerprint =
217 Hash::of(&serde_json::to_vec(&("counts.rebuild", request)).map_err(Failure::storage)?)
218 .to_string();
219 if let Some(receipt) = self.receipt(&ReceiptQuery {
220 branch: request.branch.clone(),
221 request_id: request.request_id.clone(),
222 })? {
223 return if receipt.fingerprint == fingerprint {
224 Ok(receipt)
225 } else {
226 Err(Failure::new(
227 "request_conflict",
228 "request identity has different content",
229 ))
230 };
231 }
232 let at = Captured {
233 branch: request.branch.clone(),
234 version: version(&record),
235 shards: record.shards.clone(),
236 };
237 self.refuse_count_merge(&at).map_err(|error| {
238 if error.code == "counts_merge_pending" {
239 error
240 } else {
241 count_scan_failure(&error)
242 }
243 })?;
244 let next = Version {
245 generation: record.created,
246 sequence: record
247 .seq
248 .checked_add(1)
249 .ok_or_else(|| Failure::new("sequence_exhausted", "branch sequence exhausted"))?,
250 };
251 let counts = rebuild_buckets(&self.reader, &at)?;
252 #[cfg(test)]
253 rebuild_probe::pause()?;
254 let receipt = Receipt {
255 retention: None,
256 versions_released: None,
257 branch: request.branch.clone(),
258 request_id: request.request_id.clone(),
259 fingerprint,
260 version: next,
261 };
262 self.commit_rebuilt_counts(&name, &record, &at, receipt, counts)
263 }
264
265 fn commit_rebuilt_counts(
266 &mut self,
267 name: &str,
268 record: &BranchRefRecord,
269 at: &Captured,
270 receipt: Receipt,
271 counts: BTreeMap<CountBucket, BucketCount>,
272 ) -> Result<Receipt, Failure> {
273 self.rows_before_advance(&at.branch, record)?;
274 let branch =
275 open_branch(name, &self.refs, &self.registry).map_err(Failure::branch_commit)?;
276 for (bucket, counter) in counts {
277 let key = bucket.key();
278 let shard = self.writer.shard_for(&key);
279 if counter.present {
280 branch.put(shard, key, counter.counted.to_string().into_bytes())
281 } else {
282 branch.delete(shard, key)
283 }
284 .map_err(Failure::branch)?;
285 }
286 branch
287 .put(self.writer.shard_for(READY_KEY), READY_KEY, READY_VALUE)
288 .map_err(Failure::branch)?;
289 let key = receipt_key(
290 &receipt.branch,
291 receipt.version.generation,
292 &receipt.request_id,
293 );
294 branch
295 .put(
296 self.writer.shard_for(&key),
297 key,
298 serde_json::to_vec(&receipt).map_err(Failure::storage)?,
299 )
300 .map_err(Failure::branch)?;
301 #[cfg(test)]
302 rebuild_probe::before_commit();
303 self.writer
304 .commit(
305 &branch,
306 &self.registry,
307 CommitRequest {
308 durability: CommitDurability::Durable {
309 refs: &mut self.refs,
310 },
311 extra_parents: &[],
312 timestamp: current_timestamp(),
313 note: None,
314 },
315 )
316 .map_err(|error| {
317 Failure::new(
318 if self
319 .refs
320 .get(name)
321 .is_some_and(|now| version(now) != at.version)
322 {
323 "durability_unknown"
324 } else {
325 "write_refused"
326 },
327 error.to_string(),
328 )
329 })?;
330 self.publish_record(name)?;
331 Ok(receipt)
332 }
333
334 fn refuse_count_merge(&self, at: &Captured) -> Result<(), Failure> {
335 if reads::get_bytes(&self.reader, at, b"b\0merge-candidate-progress")?.is_some() {
336 return Err(Failure::new(
337 "counts_merge_pending",
338 "branch holds a merge candidate",
339 ));
340 }
341 let service = self.merge_service_selection()?;
342 reads::scan_rows(&self.reader, &service, b"j\0merge\0", 1, |parts, bytes| {
343 let plan: MergePlan = serde_json::from_slice(bytes).map_err(Failure::storage)?;
344 identifier(&plan.id)?;
345 for pin in [&plan.source, &plan.target, &plan.origin] {
346 identifier(&pin.branch)?;
347 }
348 if Hash::of(plan.id.as_bytes()).to_string() != parts[0]
349 || plan.origin.branch != plan.target.branch
350 {
351 return Err(Failure::new(
352 "merge_corrupt",
353 "merge plan key differs from identity",
354 ));
355 }
356 if plan.state == MergeState::Open
357 && [&plan.source, &plan.target, &plan.origin]
358 .iter()
359 .any(|pin| pin.branch == at.branch)
360 {
361 return Err(Failure::new(
362 "counts_merge_pending",
363 "branch has an open merge",
364 ));
365 }
366 Ok(())
367 })
368 }
369
370 pub fn audit_counts(
375 access: &Access,
376 reader: &ReadOnlyDatabase,
377 at: &Captured,
378 ) -> Result<CountsAudit, Failure> {
379 if !access.can_read_store() {
380 return Ok(CountsAudit::CannotTell {
381 reason: CountReason::Withheld,
382 });
383 }
384 if !is_ready(reader, at)? {
385 return Ok(CountsAudit::CannotTell {
386 reason: CountReason::CountsNotReady,
387 });
388 }
389 let mut counts = counted_buckets(reader, at)?;
390 count_rows(reader, at, &mut counts, true)?;
391 Ok(CountsAudit::Compared {
392 buckets: counts
393 .into_iter()
394 .map(|(bucket, count)| {
395 let verdict = if count.kept == Some(count.counted) {
396 AuditVerdict::Agree
397 } else {
398 AuditVerdict::Differ {
399 kept: count.kept,
400 counted: count.counted,
401 }
402 };
403 AuditedBucket { bucket, verdict }
404 })
405 .collect(),
406 })
407 }
408
409 #[cfg(test)]
410 pub(in crate::serve) fn arm_count_rebuild_pause(
411 paused: std::sync::mpsc::Sender<()>,
412 release: std::sync::mpsc::Receiver<()>,
413 ) {
414 rebuild_probe::arm(paused, release);
415 }
416}
417
418fn rebuild_buckets(
419 reader: &ReadOnlyDatabase,
420 at: &Captured,
421) -> Result<BTreeMap<CountBucket, BucketCount>, Failure> {
422 let mut counts = counted_buckets(reader, at).map_err(|error| count_scan_failure(&error))?;
423 count_rows(reader, at, &mut counts, false).map_err(|error| count_scan_failure(&error))?;
424 Ok(counts)
425}
426
427fn count_scan_failure(error: &Failure) -> Failure {
428 Failure::new(
429 "counts_scan_refused",
430 format!("{}: {}", error.code, error.message),
431 )
432}
433
434#[cfg(test)]
435pub(super) mod rebuild_probe {
436 use super::Failure;
437 use std::cell::{Cell, RefCell};
438 use std::sync::mpsc::{Receiver, Sender};
439 thread_local! {
440 static CRASH: Cell<bool> = const { Cell::new(false) };
441 static PAUSE: RefCell<Option<(Sender<()>, Receiver<()>)>> = const { RefCell::new(None) };
442 }
443 pub fn crash_next() {
444 CRASH.with(|slot| slot.set(true));
445 }
446 pub fn arm(paused: Sender<()>, release: Receiver<()>) {
447 PAUSE.with(|slot| *slot.borrow_mut() = Some((paused, release)));
448 }
449 pub fn pause() -> Result<(), Failure> {
450 if let Some((paused, release)) = PAUSE.with(|slot| slot.borrow_mut().take()) {
451 paused.send(()).map_err(Failure::storage)?;
452 release.recv().map_err(Failure::storage)?;
453 }
454 Ok(())
455 }
456 pub fn before_commit() {
457 assert!(
458 !CRASH.with(|slot| slot.replace(false)),
459 "injected rebuild interruption"
460 );
461 }
462}
463
464pub(in crate::serve::storage) fn initialize_new_store(
465 writer: &mut BranchDatabase,
466 refs: &mut BranchRefStore,
467 registry: &BranchRegistry,
468) -> Result<(), Failure> {
469 let service = open_branch(SERVICE, refs, registry).map_err(Failure::branch_commit)?;
470 service
471 .put(writer.shard_for(READY_KEY), READY_KEY, READY_VALUE)
472 .map_err(Failure::branch)?;
473 writer
474 .commit(
475 &service,
476 registry,
477 CommitRequest {
478 durability: CommitDurability::Durable { refs },
479 extra_parents: &[],
480 timestamp: current_timestamp(),
481 note: None,
482 },
483 )
484 .map_err(Failure::branch_commit)?;
485 Ok(())
486}
487
488fn document_count_key(collection: &str) -> Vec<u8> {
489 format!("k\0d\0{collection}").into_bytes()
490}
491
492fn read(reader: &ReadOnlyDatabase, at: &Captured, key: &[u8]) -> Result<Option<Vec<u8>>, Failure> {
493 #[cfg(test)]
494 probe::read(key);
495 reads::get_bytes(reader, at, key)
496}
497
498fn is_ready(reader: &ReadOnlyDatabase, at: &Captured) -> Result<bool, Failure> {
499 match read(reader, at, READY_KEY)? {
500 None => Ok(false),
501 Some(value) if value.as_slice() == READY_VALUE => Ok(true),
502 Some(_) => Err(Failure::new(
503 "counts_corrupt",
504 "count readiness format is invalid",
505 )),
506 }
507}
508
509fn decode(value: &[u8]) -> Result<u64, Failure> {
510 if value.is_empty()
511 || value.len() > 20
512 || (value.len() > 1 && value.first() == Some(&b'0'))
513 || !value.iter().all(u8::is_ascii_digit)
514 {
515 return Err(Failure::new(
516 "counts_corrupt",
517 "count is not a canonical decimal u64",
518 ));
519 }
520 std::str::from_utf8(value)
521 .map_err(|error| Failure::new("counts_corrupt", error.to_string()))?
522 .parse()
523 .map_err(|error: std::num::ParseIntError| Failure::new("counts_corrupt", error.to_string()))
524}
525
526fn required_documents(
527 reader: &ReadOnlyDatabase,
528 at: &Captured,
529 collection: &str,
530) -> Result<u64, Failure> {
531 let value = read(reader, at, &document_count_key(collection))?
532 .ok_or_else(|| Failure::new("counts_corrupt", "ready collection has no document count"))?;
533 decode(&value)
534}
535
536fn document_exists(
537 reader: &ReadOnlyDatabase,
538 at: &Captured,
539 collection: &str,
540 id: &str,
541) -> Result<bool, Failure> {
542 let key = revision_key(collection, id);
543 Ok(read(reader, at, &key)?.is_some())
544}
545
546fn increased(count: u64, added: u64) -> Result<Option<u64>, Failure> {
547 if added == 0 {
548 return Ok(None);
549 }
550 added_count(count, added).map(Some)
551}
552
553fn added_count(count: u64, added: u64) -> Result<u64, Failure> {
554 count
555 .checked_add(added)
556 .ok_or_else(|| Failure::new("count_overflow", "object count exceeds u64"))
557}
558
559fn removed_count(count: u64, removed: u64) -> Result<u64, Failure> {
560 count
561 .checked_sub(removed)
562 .ok_or_else(|| Failure::new("count_underflow", "removed objects exceed count"))
563}
564
565pub(super) fn append(
566 reader: &ReadOnlyDatabase,
567 at: &Captured,
568 change: &Change,
569 writes: &mut Vec<PreparedWrite>,
570) -> Result<(), Failure> {
571 let collection = match change {
572 Change::Collection(write) => Some(&write.collection),
573 Change::Put(write) => Some(&write.collection),
574 Change::AppendPrune(write) => Some(&write.collection),
575 Change::Delete(write) => Some(&write.collection),
576 Change::DeleteMany(delete) => Some(&delete.collection),
577 Change::PutMany(import) => Some(&import.collection),
578 Change::EdgePut(_)
579 | Change::EdgeDelete(_)
580 | Change::EdgePutMany(_)
581 | Change::VectorPut(_)
582 | Change::VectorPutMany(_)
583 | Change::VectorDelete(_)
584 | Change::RelationPut(_) => None,
585 _ => return Ok(()),
586 };
587 if !is_ready(reader, at)? {
588 return Ok(());
589 }
590 if let Some(collection) = collection {
591 append_documents(reader, at, change, collection, writes)?;
592 }
593 let mut counters = declared_counters(reader, at, writes)?;
594 append_objects(reader, at, writes, &mut counters)?;
595 for (key, counter) in counters {
596 if counter.write {
597 writes.push((key, Some(counter.value.to_string().into_bytes())));
598 }
599 }
600 #[cfg(test)]
601 probe::write_count(writes);
602 Ok(())
603}
604
605fn append_documents(
606 reader: &ReadOnlyDatabase,
607 at: &Captured,
608 change: &Change,
609 collection: &str,
610 writes: &mut Vec<PreparedWrite>,
611) -> Result<(), Failure> {
612 let count = match change {
613 Change::Collection(_) => {
614 if read(reader, at, &collection_key(collection))?.is_some() {
615 required_documents(reader, at, collection)?;
616 None
617 } else {
618 if read(reader, at, &document_count_key(collection))?.is_some() {
619 return Err(Failure::new(
620 "counts_corrupt",
621 "document count has no collection",
622 ));
623 }
624 Some(0)
625 }
626 }
627 Change::Put(write) => {
628 let present = document_exists(reader, at, collection, &write.document_id)?;
629 increased(
630 required_documents(reader, at, collection)?,
631 u64::from(!present),
632 )?
633 }
634 Change::Delete(write) => {
635 if document_exists(reader, at, collection, &write.document_id)? {
636 Some(removed_count(
637 required_documents(reader, at, collection)?,
638 1,
639 )?)
640 } else {
641 None
642 }
643 }
644 Change::DeleteMany(delete) => Some(removed_count(
645 required_documents(reader, at, collection)?,
646 u64::try_from(delete.document_ids.len()).map_err(Failure::storage)?,
647 )?),
648 Change::AppendPrune(_) => {
649 let prefix = revision_key(collection, "");
650 let mut added = 0_u64;
651 let mut removed = 0_u64;
652 for (key, value) in writes.iter() {
653 let Some(id) = key.strip_prefix(prefix.as_slice()) else {
654 continue;
655 };
656 let id = std::str::from_utf8(id).map_err(Failure::storage)?;
657 let present = document_exists(reader, at, collection, id)?;
658 if value.is_some() && !present {
659 added = added_count(added, 1)?;
660 }
661 if value.is_none() && present {
662 removed = added_count(removed, 1)?;
663 }
664 }
665 Some(added_count(
666 removed_count(required_documents(reader, at, collection)?, removed)?,
667 added,
668 )?)
669 }
670 Change::PutMany(import) => {
671 let mut added = 0_u64;
672 for document in &import.documents {
673 if !document_exists(reader, at, collection, &document.document_id)? {
674 added = added.checked_add(1).ok_or_else(|| {
675 Failure::new("count_overflow", "import delta exceeds u64")
676 })?;
677 }
678 }
679 increased(required_documents(reader, at, collection)?, added)?
680 }
681 _ => return Ok(()),
682 };
683 if let Some(count) = count {
684 writes.push((
685 document_count_key(collection),
686 Some(count.to_string().into_bytes()),
687 ));
688 }
689 Ok(())
690}
691
692#[derive(Default)]
693struct Delta {
694 added: u64,
695 removed: u64,
696 existing: bool,
697}
698
699struct Counter {
700 value: u64,
701 write: bool,
702 initialized: bool,
703}
704
705pub(super) struct CandidateCounts {
706 ready: bool,
707 changes: BTreeMap<Vec<u8>, (bool, bool)>,
708}
709
710impl CandidateCounts {
711 pub(super) fn new(reader: &ReadOnlyDatabase, at: &Captured) -> Result<Self, Failure> {
712 Ok(Self {
713 ready: is_ready(reader, at)?,
714 changes: BTreeMap::new(),
715 })
716 }
717
718 pub(super) fn record(
719 &mut self,
720 reader: &ReadOnlyDatabase,
721 at: &Captured,
722 key: &[u8],
723 presence_key: &[u8],
724 after: bool,
725 ) -> Result<(), Failure> {
726 if !self.ready {
727 return Ok(());
728 }
729 if let Some(change) = self.changes.get_mut(key) {
730 change.1 = after;
731 } else {
732 let before = read(reader, at, presence_key)?.is_some();
733 self.changes.insert(key.to_vec(), (before, after));
734 }
735 Ok(())
736 }
737
738 pub(super) fn append(
739 self,
740 reader: &ReadOnlyDatabase,
741 at: &Captured,
742 rows: &mut BTreeMap<Vec<u8>, Option<Vec<u8>>>,
743 ) -> Result<(), Failure> {
744 if !self.ready {
745 return Ok(());
746 }
747 let mut counters = BTreeMap::new();
748 for (key, value) in &*rows {
749 let Some(value) = value else { continue };
750 if key.starts_with(b"w\0")
751 && matches!(
752 super::embedded::read_field(value)?,
753 super::embedded::Field::Embedded(_)
754 )
755 {
756 continue;
757 }
758 let count_key = if let Some(name) = key.strip_prefix(b"c\0") {
759 let collection = std::str::from_utf8(name).map_err(Failure::storage)?;
760 identifier(collection)?;
761 Some(document_count_key(collection))
762 } else {
763 declaration_count_key(key)?
764 };
765 let Some(count_key) = count_key else { continue };
766 let stored = read(reader, at, &count_key)?;
767 let count = match &stored {
768 Some(bytes) => decode(bytes)?,
769 None if read(reader, at, key)?.is_none() => 0,
770 None => return Err(Failure::new("counts_corrupt", "declared kind has no count")),
771 };
772 counters.insert(
773 count_key,
774 Counter {
775 value: count,
776 write: stored.is_none(),
777 initialized: stored.is_none(),
778 },
779 );
780 }
781 let deltas = candidate_deltas(self.changes)?;
782 for (key, delta) in deltas {
783 let counter = match counters.entry(key) {
784 Entry::Occupied(entry) => entry.into_mut(),
785 Entry::Vacant(entry) => {
786 let value = match read(reader, at, entry.key())? {
787 Some(bytes) => decode(&bytes)?,
788 None if !delta.existing => {
789 candidate_missing_count(reader, at, entry.key())?
790 }
791 None => {
792 return Err(Failure::new(
793 "counts_corrupt",
794 "present object has no count",
795 ));
796 }
797 };
798 entry.insert(Counter {
799 value,
800 write: false,
801 initialized: false,
802 })
803 }
804 };
805 if counter.initialized && delta.existing {
806 return Err(Failure::new(
807 "counts_corrupt",
808 "present object has no previous count",
809 ));
810 }
811 counter.value = added_count(removed_count(counter.value, delta.removed)?, delta.added)?;
812 counter.write |= delta.added != 0 || delta.removed != 0;
813 }
814 for (key, counter) in counters {
815 if counter.write {
816 rows.insert(key, Some(counter.value.to_string().into_bytes()));
817 }
818 }
819 Ok(())
820 }
821}
822
823fn candidate_deltas(
824 changes: BTreeMap<Vec<u8>, (bool, bool)>,
825) -> Result<BTreeMap<Vec<u8>, Delta>, Failure> {
826 let mut deltas: BTreeMap<Vec<u8>, Delta> = BTreeMap::new();
827 for (key, (before, after)) in changes {
828 let count_key = if let Some(suffix) = key.strip_prefix(b"d\0") {
829 let text = std::str::from_utf8(suffix).map_err(Failure::storage)?;
830 let (collection, document) = text
831 .split_once('\0')
832 .ok_or_else(|| Failure::new("corrupt_key", "document owner has no identifier"))?;
833 identifier(collection)?;
834 identifier(document)?;
835 document_count_key(collection)
836 } else {
837 object_count_key(&key)?
838 .ok_or_else(|| Failure::new("counts_corrupt", "candidate object has no bucket"))?
839 };
840 let delta = deltas.entry(count_key).or_default();
841 delta.existing |= before;
842 if after && !before {
843 delta.added = added_count(delta.added, 1)?;
844 }
845 if before && !after {
846 delta.removed = added_count(delta.removed, 1)?;
847 }
848 }
849 Ok(deltas)
850}
851
852fn candidate_missing_count(
853 reader: &ReadOnlyDatabase,
854 at: &Captured,
855 key: &[u8],
856) -> Result<u64, Failure> {
857 if let Some(collection) = key.strip_prefix(b"k\0d\0") {
858 let collection = std::str::from_utf8(collection).map_err(Failure::storage)?;
859 identifier(collection)?;
860 if read(reader, at, &collection_key(collection))?.is_some() {
861 return Err(Failure::new(
862 "counts_corrupt",
863 "declared collection has no count",
864 ));
865 }
866 Ok(0)
867 } else {
868 unregistered_count(reader, at, key)
869 }
870}
871
872fn declaration_count_key(key: &[u8]) -> Result<Option<Vec<u8>>, Failure> {
873 if let Some(name) = key.strip_prefix(b"n\0") {
874 let name = std::str::from_utf8(name).map_err(Failure::storage)?;
875 identifier(name)?;
876 return Ok(Some(format!("k\0e\0{name}").into_bytes()));
877 }
878 if !key.starts_with(b"w\0") {
879 return Ok(None);
880 }
881 let text = std::str::from_utf8(key).map_err(Failure::storage)?;
882 let parts: Vec<_> = text.split('\0').collect();
883 let ["w", collection, field] = parts.as_slice() else {
884 return Err(invalid_vector_key());
885 };
886 identifier(collection)?;
887 identifier(field)?;
888 Ok(Some(format!("k\0v\0{collection}\0{field}").into_bytes()))
889}
890
891fn declared_counters(
892 reader: &ReadOnlyDatabase,
893 at: &Captured,
894 writes: &[PreparedWrite],
895) -> Result<BTreeMap<Vec<u8>, Counter>, Failure> {
896 let mut counters = BTreeMap::new();
897 for (key, value) in writes {
898 if value.is_none() {
899 continue;
900 }
901 let Some(count_key) = declaration_count_key(key)? else {
902 continue;
903 };
904 if counters.contains_key(&count_key) {
905 continue;
906 }
907 let declared = read(reader, at, key)?.is_some();
908 let counter = match read(reader, at, &count_key)? {
909 Some(value) => Counter {
910 value: decode(&value)?,
911 write: false,
912 initialized: false,
913 },
914 None if !declared => Counter {
915 value: 0,
916 write: true,
917 initialized: true,
918 },
919 None => return Err(Failure::new("counts_corrupt", "declared kind has no count")),
920 };
921 counters.insert(count_key, counter);
922 }
923 Ok(counters)
924}
925
926fn object_count_key(key: &[u8]) -> Result<Option<Vec<u8>>, Failure> {
927 if key.starts_with(b"e\0") {
928 let edge = edges::decode_key(key)?;
929 return Ok(Some(format!("k\0e\0{}", edge.relation).into_bytes()));
930 }
931 if !key.starts_with(b"v\0") {
932 return Ok(None);
933 }
934 let text = std::str::from_utf8(key).map_err(Failure::storage)?;
935 let mut parts = text.split('\0');
936 if parts.next() != Some("v") {
937 return Err(invalid_vector_key());
938 }
939 let collection = parts.next().ok_or_else(invalid_vector_key)?;
940 let field = parts.next().ok_or_else(invalid_vector_key)?;
941 let document = parts.next().ok_or_else(invalid_vector_key)?;
942 if parts.next().is_some() {
943 return Err(invalid_vector_key());
944 }
945 identifier(collection)?;
946 identifier(field)?;
947 identifier(document)?;
948 Ok(Some(format!("k\0v\0{collection}\0{field}").into_bytes()))
949}
950
951fn invalid_vector_key() -> Failure {
952 Failure::new("vector_key", "vector key must have three identifier parts")
953}
954
955fn append_objects(
956 reader: &ReadOnlyDatabase,
957 at: &Captured,
958 writes: &[PreparedWrite],
959 counters: &mut BTreeMap<Vec<u8>, Counter>,
960) -> Result<(), Failure> {
961 let mut final_presence = BTreeMap::new();
964 for (key, value) in writes {
965 if key.starts_with(b"e\0") || key.starts_with(b"v\0") {
966 final_presence.insert(key.as_slice(), value.is_some());
967 }
968 }
969 let mut deltas: BTreeMap<Vec<u8>, Delta> = BTreeMap::new();
970 for (key, present) in final_presence {
971 let count_key = object_count_key(key)?
972 .ok_or_else(|| Failure::new("counts_corrupt", "counted object has no bucket"))?;
973 let existed = read(reader, at, key)?.is_some();
974 let delta = deltas.entry(count_key).or_default();
975 delta.existing |= existed;
976 if present && !existed {
977 delta.added = delta
978 .added
979 .checked_add(1)
980 .ok_or_else(|| Failure::new("count_overflow", "object delta exceeds u64"))?;
981 } else if existed && !present {
982 delta.removed = delta
983 .removed
984 .checked_add(1)
985 .ok_or_else(|| Failure::new("count_overflow", "object delta exceeds u64"))?;
986 }
987 }
988 for (key, delta) in deltas {
989 if let Some(counter) = counters.get_mut(&key) {
990 if counter.initialized && delta.existing {
991 return Err(Failure::new(
992 "counts_corrupt",
993 "unregistered kind has stored objects",
994 ));
995 }
996 counter.value = added_count(removed_count(counter.value, delta.removed)?, delta.added)?;
997 counter.write |= delta.added != 0 || delta.removed != 0;
998 continue;
999 }
1000 let count = match read(reader, at, &key)? {
1001 Some(value) => decode(&value)?,
1002 None if !delta.existing => unregistered_count(reader, at, &key)?,
1003 None => {
1004 return Err(Failure::new(
1005 "counts_corrupt",
1006 "present object has no count",
1007 ));
1008 }
1009 };
1010 let remaining = removed_count(count, delta.removed)?;
1011 let next = added_count(remaining, delta.added)?;
1012 if delta.added != 0 || delta.removed != 0 {
1013 counters.insert(
1014 key,
1015 Counter {
1016 value: next,
1017 write: true,
1018 initialized: false,
1019 },
1020 );
1021 }
1022 }
1023 Ok(())
1024}
1025
1026fn unregistered_count(
1027 reader: &ReadOnlyDatabase,
1028 at: &Captured,
1029 key: &[u8],
1030) -> Result<u64, Failure> {
1031 let declaration = if let Some(name) = key.strip_prefix(b"k\0e\0") {
1032 let mut declaration = b"n\0".to_vec();
1033 declaration.extend_from_slice(name);
1034 declaration
1035 } else if let Some(field) = key.strip_prefix(b"k\0v\0") {
1036 let mut declaration = b"w\0".to_vec();
1037 declaration.extend_from_slice(field);
1038 declaration
1039 } else {
1040 return Err(Failure::new(
1041 "counts_corrupt",
1042 "count key has no declaration family",
1043 ));
1044 };
1045 if read(reader, at, &declaration)?.is_some() {
1046 return Err(Failure::new("counts_corrupt", "declared kind has no count"));
1047 }
1048 Ok(0)
1049}
1050
1051pub(super) fn sum_rows(
1052 reader: &ReadOnlyDatabase,
1053 at: &Captured,
1054 prefix: &[u8],
1055 parts: usize,
1056) -> Result<(u64, std::collections::BTreeSet<String>), Failure> {
1057 let mut total = 0;
1058 let mut names = std::collections::BTreeSet::new();
1059 reads::scan_rows(reader, at, prefix, parts, |parts, value| {
1060 let name = parts.join("\0");
1061 if !names.insert(name) {
1062 return Err(Failure::new(
1063 "counts_corrupt",
1064 "count exists in multiple shards",
1065 ));
1066 }
1067 total = added_count(total, decode(value)?)?;
1068 Ok(())
1069 })?;
1070 Ok((total, names))
1071}
1072
1073#[cfg(test)]
1074mod probe {
1075 use std::cell::Cell;
1076
1077 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1078 pub(super) struct Reads {
1079 pub(super) ready: usize,
1080 pub(super) revisions: usize,
1081 pub(super) bodies: usize,
1082 pub(super) objects: usize,
1083 pub(super) counts: usize,
1084 pub(super) count_writes: usize,
1085 }
1086
1087 std::thread_local! {
1088 static READS: Cell<Reads> = const { Cell::new(Reads {
1089 ready: 0, revisions: 0, bodies: 0, objects: 0,
1090 counts: 0, count_writes: 0,
1091 }) };
1092 }
1093
1094 pub(super) fn read(key: &[u8]) {
1095 READS.with(|slot| {
1096 let mut value = slot.get();
1097 if key == super::READY_KEY {
1098 value.ready += 1;
1099 } else if key.starts_with(b"m\0") {
1100 value.revisions += 1;
1101 } else if key.starts_with(b"d\0") {
1102 value.bodies += 1;
1103 } else if key.starts_with(b"e\0") || key.starts_with(b"v\0") {
1104 value.objects += 1;
1105 } else if key.starts_with(b"k\0") {
1106 value.counts += 1;
1107 }
1108 slot.set(value);
1109 });
1110 }
1111
1112 pub(super) fn write_count(writes: &[super::PreparedWrite]) {
1113 READS.with(|slot| {
1114 let mut value = slot.get();
1115 value.count_writes += writes
1116 .iter()
1117 .filter(|(key, _)| key.starts_with(b"k\0"))
1118 .count();
1119 slot.set(value);
1120 });
1121 }
1122
1123 pub(super) fn take() -> Reads {
1124 READS.with(Cell::take)
1125 }
1126}
1127
1128#[cfg(test)]
1129#[path = "counts_tests.rs"]
1130mod tests;