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