1use crate::{
7 db::{
8 codec::{
9 finalize_hash_sha256, new_hash_sha256, write_hash_len_u32, write_hash_str_u32,
10 write_hash_tag_u8, write_hash_u32, write_hash_u64,
11 },
12 commit::CommitSchemaFingerprint,
13 direction::Direction,
14 ordered_overlay::{OrderedOverlayEntry, OrderedOverlayVisit, visit_ordered_overlay},
15 schema::{
16 AcceptedFieldKind, AcceptedSchemaSnapshot, PersistedIndexKeyItemSnapshot,
17 PersistedIndexKeySnapshot, PersistedSchemaSnapshot, SchemaVersion,
18 accepted_schema_cache_fingerprint,
19 accepted_schema_cache_fingerprint_for_persisted_snapshot,
20 accepted_schema_cache_fingerprint_method_version, decode_persisted_schema_snapshot,
21 encode_persisted_schema_snapshot,
22 enum_catalog::{
23 AcceptedEnumCatalogHandle, AcceptedSchemaAuthority, AcceptedSchemaPublicationError,
24 AcceptedSchemaRevision, AcceptedSchemaRevisionBundle, AcceptedSchemaRootSelection,
25 AcceptedStoreCatalogScope, CandidateSchemaRevision,
26 decode_verified_accepted_schema_revision_bundle,
27 prepare_accepted_schema_root_publication, select_current_accepted_schema_root,
28 },
29 schema_snapshot_integrity_detail,
30 },
31 },
32 error::InternalError,
33 types::EntityTag,
34};
35use ic_stable_structures::{
36 BTreeMap as StableBTreeMap, DefaultMemoryImpl, Storable, memory_manager::VirtualMemory,
37 storable::Bound as StorableBound,
38};
39use sha2::Digest;
40use std::borrow::Cow;
41#[cfg(test)]
42use std::cell::Cell;
43use std::cell::{OnceCell, Ref, RefCell};
44use std::collections::{BTreeMap as StdBTreeMap, BTreeSet};
45use std::convert::Infallible;
46use std::ops::Bound as RangeBound;
47
48const SCHEMA_KEY_BYTES_USIZE: usize = 16;
49const SCHEMA_KEY_BYTES: u32 = 16;
50const SCHEMA_KEY_NAMESPACE_ENTITY_SNAPSHOT: u8 = 0;
51const SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE: u8 = 1;
52const SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT: u8 = 2;
53pub(in crate::db) const MAX_SCHEMA_SNAPSHOT_BYTES: u32 = 512 * 1024;
54const SCHEMA_STORE_CATALOG_FINGERPRINT_VERSION: u8 = 1;
55const SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_VERSION: u8 = 2;
56const SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_VERSION: u8 = 3;
57const RAW_SCHEMA_SNAPSHOT_MAGIC: &[u8; 8] = b"ICYDBCAT";
58const RAW_SCHEMA_SNAPSHOT_VALUE_VERSION: u8 = 1;
59const RAW_SCHEMA_SNAPSHOT_HEADER_BYTES: usize = 25;
60
61#[cfg(test)]
62thread_local! {
63 static LATEST_RAW_SNAPSHOTS_BY_ENTITY_CALLS: Cell<u64> = const { Cell::new(0) };
64 static ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES: Cell<u64> = const { Cell::new(0) };
65}
66
67#[cfg(test)]
68pub(in crate::db) fn reset_latest_raw_snapshots_by_entity_call_count_for_tests() {
69 LATEST_RAW_SNAPSHOTS_BY_ENTITY_CALLS.with(|calls| calls.set(0));
70}
71
72#[cfg(test)]
73pub(in crate::db) fn latest_raw_snapshots_by_entity_call_count_for_tests() -> u64 {
74 LATEST_RAW_SNAPSHOTS_BY_ENTITY_CALLS.with(Cell::get)
75}
76
77#[cfg(test)]
78fn reset_accepted_schema_bundle_cache_miss_count_for_tests() {
79 ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES.with(|misses| misses.set(0));
80}
81
82#[cfg(test)]
83fn accepted_schema_bundle_cache_miss_count_for_tests() -> u64 {
84 ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES.with(Cell::get)
85}
86
87#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
96struct RawSchemaKey([u8; SCHEMA_KEY_BYTES_USIZE]);
97
98impl RawSchemaKey {
99 #[must_use]
101 fn from_entity_version(entity: EntityTag, version: SchemaVersion) -> Self {
102 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
103 out[0] = SCHEMA_KEY_NAMESPACE_ENTITY_SNAPSHOT;
104 out[4..12].copy_from_slice(&entity.value().to_be_bytes());
105 out[12..].copy_from_slice(&version.get().to_be_bytes());
106
107 Self(out)
108 }
109
110 fn from_accepted_bundle(bundle_key: super::enum_catalog::AcceptedSchemaBundleKey) -> Self {
111 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
112 out[0] = SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE;
113 out[4..12].copy_from_slice(&bundle_key.get().to_be_bytes());
114 Self(out)
115 }
116
117 fn from_accepted_root_slot(slot: usize) -> Result<Self, InternalError> {
118 let slot = u32::try_from(slot).map_err(|_| InternalError::store_invariant())?;
119 if slot > 1 {
120 return Err(InternalError::store_invariant());
121 }
122 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
123 out[0] = SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT;
124 out[12..].copy_from_slice(&slot.to_be_bytes());
125 Ok(Self(out))
126 }
127
128 #[must_use]
130 fn entity_tag(self) -> EntityTag {
131 let mut bytes = [0u8; size_of::<u64>()];
132 bytes.copy_from_slice(&self.0[4..12]);
133
134 EntityTag::new(u64::from_be_bytes(bytes))
135 }
136
137 #[must_use]
139 fn version(self) -> u32 {
140 let mut bytes = [0u8; size_of::<u32>()];
141 bytes.copy_from_slice(&self.0[12..]);
142
143 u32::from_be_bytes(bytes)
144 }
145
146 fn entity_range_bounds(entity: EntityTag) -> (RangeBound<Self>, RangeBound<Self>) {
147 (
148 RangeBound::Included(Self::from_entity_version(entity, SchemaVersion::new(0))),
149 RangeBound::Included(Self::from_entity_version(
150 entity,
151 SchemaVersion::new(u32::MAX),
152 )),
153 )
154 }
155
156 const fn all_entity_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
157 let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
158 end[0] = SCHEMA_KEY_NAMESPACE_ENTITY_SNAPSHOT;
159 (
160 RangeBound::Included(Self([0; SCHEMA_KEY_BYTES_USIZE])),
161 RangeBound::Included(Self(end)),
162 )
163 }
164
165 #[cfg(test)]
166 const fn is_entity_snapshot(self) -> bool {
167 self.0[0] == SCHEMA_KEY_NAMESPACE_ENTITY_SNAPSHOT
168 }
169
170 const fn is_accepted_root(self) -> bool {
171 self.0[0] == SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT
172 }
173}
174
175impl Storable for RawSchemaKey {
176 fn to_bytes(&self) -> Cow<'_, [u8]> {
177 Cow::Borrowed(&self.0)
178 }
179
180 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
181 debug_assert_eq!(
182 bytes.len(),
183 SCHEMA_KEY_BYTES_USIZE,
184 "RawSchemaKey::from_bytes received unexpected byte length",
185 );
186
187 if bytes.len() != SCHEMA_KEY_BYTES_USIZE {
188 return Self([0u8; SCHEMA_KEY_BYTES_USIZE]);
189 }
190
191 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
192 out.copy_from_slice(bytes.as_ref());
193 Self(out)
194 }
195
196 fn into_bytes(self) -> Vec<u8> {
197 self.0.to_vec()
198 }
199
200 const BOUND: StorableBound = StorableBound::Bounded {
201 max_size: SCHEMA_KEY_BYTES,
202 is_fixed_size: true,
203 };
204}
205
206#[derive(Clone, Debug, Eq, PartialEq)]
217struct RawSchemaSnapshot {
218 payload: Vec<u8>,
219 accepted_schema_fingerprint: Option<CommitSchemaFingerprint>,
220}
221
222impl RawSchemaSnapshot {
223 fn from_persisted_snapshot(snapshot: &PersistedSchemaSnapshot) -> Result<Self, InternalError> {
225 validate_typed_schema_snapshot_for_store(snapshot)?;
226
227 let accepted_schema_fingerprint =
228 accepted_schema_cache_fingerprint_for_persisted_snapshot(snapshot)?;
229 let payload = encode_persisted_schema_snapshot(snapshot)?;
230
231 Ok(Self {
232 payload,
233 accepted_schema_fingerprint: Some(accepted_schema_fingerprint),
234 })
235 }
236
237 #[must_use]
239 const fn from_encoded_control_record(payload: Vec<u8>) -> Self {
240 Self {
241 payload,
242 accepted_schema_fingerprint: None,
243 }
244 }
245
246 #[cfg(test)]
249 #[must_use]
250 const fn from_unchecked_persisted_snapshot_payload(payload: Vec<u8>) -> Self {
251 Self {
252 payload,
253 accepted_schema_fingerprint: Some([0; size_of::<CommitSchemaFingerprint>()]),
254 }
255 }
256
257 #[must_use]
259 const fn as_bytes(&self) -> &[u8] {
260 self.payload.as_slice()
261 }
262
263 #[must_use]
265 fn into_bytes(self) -> Vec<u8> {
266 self.payload
267 }
268
269 fn accepted_schema_fingerprint(&self) -> Result<CommitSchemaFingerprint, InternalError> {
272 self.accepted_schema_fingerprint
273 .ok_or_else(InternalError::store_corruption)
274 }
275
276 fn decode_persisted_snapshot(&self) -> Result<PersistedSchemaSnapshot, InternalError> {
278 let _fingerprint = self.accepted_schema_fingerprint()?;
281 decode_persisted_schema_snapshot(self.as_bytes())
282 }
283}
284
285#[cfg(test)]
286pub(in crate::db::schema) fn validate_raw_schema_snapshot_bytes_for_tests(
287 bytes: Vec<u8>,
288) -> Result<(), InternalError> {
289 let raw = <RawSchemaSnapshot as Storable>::from_bytes(Cow::Owned(bytes));
290 raw.decode_persisted_snapshot().map(drop)
291}
292
293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
294pub(in crate::db) struct AcceptedCatalogIdentity {
295 entity_tag: EntityTag,
296 entity_path: &'static str,
297 store_path: &'static str,
298 accepted_schema_revision: AcceptedSchemaRevision,
299 accepted_schema_version: SchemaVersion,
300 fingerprint_method_version: u8,
301 accepted_schema_fingerprint: CommitSchemaFingerprint,
302}
303
304impl AcceptedCatalogIdentity {
305 #[must_use]
306 pub(in crate::db) const fn new(
307 entity_tag: EntityTag,
308 entity_path: &'static str,
309 store_path: &'static str,
310 accepted_schema_revision: AcceptedSchemaRevision,
311 accepted_schema_version: SchemaVersion,
312 accepted_schema_fingerprint: CommitSchemaFingerprint,
313 ) -> Self {
314 Self {
315 entity_tag,
316 entity_path,
317 store_path,
318 accepted_schema_revision,
319 accepted_schema_version,
320 fingerprint_method_version: accepted_schema_cache_fingerprint_method_version(),
321 accepted_schema_fingerprint,
322 }
323 }
324
325 #[must_use]
326 pub(in crate::db) const fn entity_tag(self) -> EntityTag {
327 self.entity_tag
328 }
329
330 #[must_use]
331 pub(in crate::db) const fn entity_path(self) -> &'static str {
332 self.entity_path
333 }
334
335 #[must_use]
336 pub(in crate::db) const fn store_path(self) -> &'static str {
337 self.store_path
338 }
339
340 #[must_use]
341 pub(in crate::db) const fn accepted_schema_revision(self) -> AcceptedSchemaRevision {
342 self.accepted_schema_revision
343 }
344
345 #[must_use]
346 pub(in crate::db) const fn accepted_schema_version(self) -> SchemaVersion {
347 self.accepted_schema_version
348 }
349
350 #[must_use]
351 pub(in crate::db) const fn fingerprint_method_version(self) -> u8 {
352 self.fingerprint_method_version
353 }
354
355 #[must_use]
356 pub(in crate::db) const fn accepted_schema_fingerprint(self) -> CommitSchemaFingerprint {
357 self.accepted_schema_fingerprint
358 }
359}
360
361#[derive(Clone, Debug, Eq, PartialEq)]
362pub(in crate::db) struct AcceptedCatalogSnapshotSelection {
363 identity: AcceptedCatalogIdentity,
364 enum_catalog: AcceptedEnumCatalogHandle,
365 raw_snapshot: Vec<u8>,
366}
367
368impl AcceptedCatalogSnapshotSelection {
369 #[must_use]
370 const fn new(
371 identity: AcceptedCatalogIdentity,
372 enum_catalog: AcceptedEnumCatalogHandle,
373 raw_snapshot: Vec<u8>,
374 ) -> Self {
375 Self {
376 identity,
377 enum_catalog,
378 raw_snapshot,
379 }
380 }
381
382 #[must_use]
383 pub(in crate::db) const fn identity(&self) -> AcceptedCatalogIdentity {
384 self.identity
385 }
386
387 #[must_use]
388 pub(in crate::db) const fn enum_catalog(&self) -> &AcceptedEnumCatalogHandle {
389 &self.enum_catalog
390 }
391
392 pub(in crate::db) fn from_accepted_snapshot(
394 identity: AcceptedCatalogIdentity,
395 enum_catalog: AcceptedEnumCatalogHandle,
396 snapshot: &AcceptedSchemaSnapshot,
397 ) -> Result<Self, InternalError> {
398 let raw_snapshot =
399 RawSchemaSnapshot::from_persisted_snapshot(snapshot.persisted_snapshot())?;
400 if raw_snapshot.accepted_schema_fingerprint()? != identity.accepted_schema_fingerprint() {
401 return Err(InternalError::store_invariant());
402 }
403
404 Ok(Self::new(identity, enum_catalog, raw_snapshot.into_bytes()))
405 }
406
407 pub(in crate::db) fn decode_verified(&self) -> Result<AcceptedSchemaSnapshot, InternalError> {
408 let snapshot = decode_persisted_schema_snapshot(&self.raw_snapshot)?;
409 let accepted = AcceptedSchemaSnapshot::try_new(snapshot)?;
410 let identity = self.identity();
411
412 if accepted.persisted_snapshot().version() != identity.accepted_schema_version() {
413 return Err(InternalError::store_invariant());
414 }
415 if accepted.entity_path() != identity.entity_path() {
416 return Err(InternalError::store_invariant());
417 }
418
419 let decoded_fingerprint = accepted_schema_cache_fingerprint(&accepted)?;
420 if decoded_fingerprint != identity.accepted_schema_fingerprint() {
421 return Err(InternalError::store_invariant());
422 }
423
424 Ok(accepted)
425 }
426}
427
428impl Storable for RawSchemaSnapshot {
429 fn to_bytes(&self) -> Cow<'_, [u8]> {
430 let Some(fingerprint) = self.accepted_schema_fingerprint else {
431 return Cow::Borrowed(self.as_bytes());
432 };
433
434 let mut bytes = Vec::with_capacity(RAW_SCHEMA_SNAPSHOT_HEADER_BYTES + self.payload.len());
435 bytes.extend_from_slice(RAW_SCHEMA_SNAPSHOT_MAGIC);
436 bytes.push(RAW_SCHEMA_SNAPSHOT_VALUE_VERSION);
437 bytes.extend_from_slice(&fingerprint);
438 bytes.extend_from_slice(self.as_bytes());
439
440 Cow::Owned(bytes)
441 }
442
443 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
444 let bytes = bytes.into_owned();
445 if bytes.len() >= RAW_SCHEMA_SNAPSHOT_HEADER_BYTES
446 && &bytes[..RAW_SCHEMA_SNAPSHOT_MAGIC.len()] == RAW_SCHEMA_SNAPSHOT_MAGIC
447 && bytes[RAW_SCHEMA_SNAPSHOT_MAGIC.len()] == RAW_SCHEMA_SNAPSHOT_VALUE_VERSION
448 {
449 let fingerprint_start = RAW_SCHEMA_SNAPSHOT_MAGIC.len() + size_of::<u8>();
450 let fingerprint_end = fingerprint_start + size_of::<CommitSchemaFingerprint>();
451 let mut fingerprint = [0_u8; size_of::<CommitSchemaFingerprint>()];
452 fingerprint.copy_from_slice(&bytes[fingerprint_start..fingerprint_end]);
453
454 return Self {
455 payload: bytes[fingerprint_end..].to_vec(),
456 accepted_schema_fingerprint: Some(fingerprint),
457 };
458 }
459
460 Self {
461 payload: bytes,
462 accepted_schema_fingerprint: None,
463 }
464 }
465
466 fn into_bytes(self) -> Vec<u8> {
467 let Some(fingerprint) = self.accepted_schema_fingerprint else {
468 return self.payload;
469 };
470
471 let mut bytes = Vec::with_capacity(RAW_SCHEMA_SNAPSHOT_HEADER_BYTES + self.payload.len());
472 bytes.extend_from_slice(RAW_SCHEMA_SNAPSHOT_MAGIC);
473 bytes.push(RAW_SCHEMA_SNAPSHOT_VALUE_VERSION);
474 bytes.extend_from_slice(&fingerprint);
475 bytes.extend_from_slice(&self.payload);
476
477 bytes
478 }
479
480 const BOUND: StorableBound = StorableBound::Unbounded;
481}
482
483fn validate_typed_schema_snapshot_for_store(
487 snapshot: &PersistedSchemaSnapshot,
488) -> Result<(), InternalError> {
489 if schema_snapshot_integrity_detail(
490 "schema snapshot",
491 snapshot.version(),
492 snapshot.primary_key_field_ids(),
493 snapshot.row_layout(),
494 snapshot.fields(),
495 )
496 .is_some()
497 {
498 return Err(InternalError::store_invariant());
499 }
500
501 Ok(())
502}
503
504#[derive(Clone, Copy, Debug, Eq, PartialEq)]
513pub(in crate::db) struct SchemaStoreFootprint {
514 snapshots: u64,
515 encoded_bytes: u64,
516 latest_snapshot_bytes: u64,
517}
518
519#[derive(Clone, Copy, Debug, Eq, PartialEq)]
527pub(in crate::db) struct SchemaStoreCatalogMetadata {
528 schema_version: SchemaVersion,
529 schema_fingerprint_method_version: u8,
530 schema_fingerprint: CommitSchemaFingerprint,
531 entity_count: u64,
532}
533
534impl SchemaStoreCatalogMetadata {
535 #[must_use]
537 const fn new(
538 schema_version: SchemaVersion,
539 schema_fingerprint_method_version: u8,
540 schema_fingerprint: CommitSchemaFingerprint,
541 entity_count: u64,
542 ) -> Self {
543 Self {
544 schema_version,
545 schema_fingerprint_method_version,
546 schema_fingerprint,
547 entity_count,
548 }
549 }
550
551 #[must_use]
553 pub(in crate::db) const fn schema_version(self) -> SchemaVersion {
554 self.schema_version
555 }
556
557 #[must_use]
559 pub(in crate::db) const fn schema_fingerprint_method_version(self) -> u8 {
560 self.schema_fingerprint_method_version
561 }
562
563 #[must_use]
566 pub(in crate::db) const fn schema_fingerprint(self) -> CommitSchemaFingerprint {
567 self.schema_fingerprint
568 }
569
570 #[must_use]
572 pub(in crate::db) const fn entity_count(self) -> u64 {
573 self.entity_count
574 }
575}
576
577#[derive(Clone, Copy, Debug, Eq, PartialEq)]
586pub(in crate::db) struct SchemaStoreAllocationMetadata {
587 data: SchemaStoreCatalogMetadata,
588 index: SchemaStoreCatalogMetadata,
589 schema: SchemaStoreCatalogMetadata,
590}
591
592impl SchemaStoreAllocationMetadata {
593 #[must_use]
596 const fn new(
597 data: SchemaStoreCatalogMetadata,
598 index: SchemaStoreCatalogMetadata,
599 schema: SchemaStoreCatalogMetadata,
600 ) -> Self {
601 Self {
602 data,
603 index,
604 schema,
605 }
606 }
607
608 #[must_use]
610 pub(in crate::db) const fn data(self) -> SchemaStoreCatalogMetadata {
611 self.data
612 }
613
614 #[must_use]
616 pub(in crate::db) const fn index(self) -> SchemaStoreCatalogMetadata {
617 self.index
618 }
619
620 #[must_use]
623 pub(in crate::db) const fn schema(self) -> SchemaStoreCatalogMetadata {
624 self.schema
625 }
626}
627
628impl SchemaStoreFootprint {
629 #[must_use]
631 const fn new(snapshots: u64, encoded_bytes: u64, latest_snapshot_bytes: u64) -> Self {
632 Self {
633 snapshots,
634 encoded_bytes,
635 latest_snapshot_bytes,
636 }
637 }
638
639 #[must_use]
641 pub(in crate::db) const fn snapshots(self) -> u64 {
642 self.snapshots
643 }
644
645 #[must_use]
647 pub(in crate::db) const fn encoded_bytes(self) -> u64 {
648 self.encoded_bytes
649 }
650
651 #[must_use]
653 pub(in crate::db) const fn latest_snapshot_bytes(self) -> u64 {
654 self.latest_snapshot_bytes
655 }
656}
657
658pub struct SchemaStore {
667 backend: SchemaStoreBackend,
668 accepted_bundle_cache: RefCell<Option<AcceptedSchemaBundleCache>>,
669 accepted_catalog_scope: OnceCell<AcceptedStoreCatalogScope>,
670}
671
672struct AcceptedSchemaBundleCache {
673 selection: AcceptedSchemaRootSelection,
674 bundle: AcceptedSchemaRevisionBundle,
675}
676
677enum SchemaStoreBackend {
678 Heap(StdBTreeMap<RawSchemaKey, RawSchemaSnapshot>),
679 Journaled {
680 canonical:
681 StableBTreeMap<RawSchemaKey, RawSchemaSnapshot, VirtualMemory<DefaultMemoryImpl>>,
682 live: StdBTreeMap<RawSchemaKey, RawSchemaSnapshot>,
683 tombstones: BTreeSet<RawSchemaKey>,
684 },
685}
686
687#[derive(Clone, Copy, Debug, Eq, PartialEq)]
689enum SchemaStoreVisit {
690 Continue,
691 Stop,
692}
693
694impl SchemaStoreVisit {
695 const fn should_stop(self) -> bool {
696 matches!(self, Self::Stop)
697 }
698}
699
700impl SchemaStore {
701 #[must_use]
703 pub const fn init_heap() -> Self {
704 Self {
705 backend: SchemaStoreBackend::Heap(StdBTreeMap::new()),
706 accepted_bundle_cache: RefCell::new(None),
707 accepted_catalog_scope: OnceCell::new(),
708 }
709 }
710
711 #[must_use]
716 pub fn init_journaled(memory: VirtualMemory<DefaultMemoryImpl>) -> Self {
717 Self {
718 backend: SchemaStoreBackend::Journaled {
719 canonical: StableBTreeMap::init(memory),
720 live: StdBTreeMap::new(),
721 tombstones: BTreeSet::new(),
722 },
723 accepted_bundle_cache: RefCell::new(None),
724 accepted_catalog_scope: OnceCell::new(),
725 }
726 }
727
728 pub(in crate::db) fn insert_persisted_snapshot(
730 &mut self,
731 entity: EntityTag,
732 snapshot: &PersistedSchemaSnapshot,
733 ) -> Result<(), InternalError> {
734 let key = RawSchemaKey::from_entity_version(entity, snapshot.version());
735 let raw_snapshot = RawSchemaSnapshot::from_persisted_snapshot(snapshot)?;
736 let _ = self.insert_raw_snapshot(key, raw_snapshot);
737
738 Ok(())
739 }
740
741 pub(in crate::db) fn reset_journaled_live_projection(&mut self) -> Result<(), InternalError> {
744 let SchemaStoreBackend::Journaled {
745 live, tombstones, ..
746 } = &mut self.backend
747 else {
748 return Err(InternalError::store_invariant());
749 };
750
751 live.clear();
752 tombstones.clear();
753 self.accepted_bundle_cache.get_mut().take();
754
755 Ok(())
756 }
757
758 pub(in crate::db) fn fold_persisted_snapshot(
760 &mut self,
761 entity: EntityTag,
762 snapshot: &PersistedSchemaSnapshot,
763 ) -> Result<(), InternalError> {
764 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
765 return Err(InternalError::store_invariant());
766 };
767
768 let key = RawSchemaKey::from_entity_version(entity, snapshot.version());
769 let raw_snapshot = RawSchemaSnapshot::from_persisted_snapshot(snapshot)?;
770 canonical.insert(key, raw_snapshot);
771
772 Ok(())
773 }
774
775 pub(in crate::db) fn current_accepted_schema_root(
777 &self,
778 ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
779 let first = self.accepted_root_slot_bytes(0)?;
780 let second = self.accepted_root_slot_bytes(1)?;
781 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])
782 }
783
784 pub(in crate::db) fn current_accepted_schema_bundle(
786 &self,
787 ) -> Result<Option<AcceptedSchemaRevisionBundle>, InternalError> {
788 let Some(selection) = self.current_accepted_schema_root()? else {
789 return Ok(None);
790 };
791 let key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
792 let raw = self
793 .get_raw_snapshot(&key)
794 .ok_or_else(InternalError::store_corruption)?;
795 decode_verified_accepted_schema_revision_bundle(selection.root(), raw.as_bytes()).map(Some)
796 }
797
798 pub(in crate::db) fn current_accepted_schema_revision(
800 &self,
801 ) -> Result<Option<AcceptedSchemaRevision>, InternalError> {
802 Ok(self
803 .current_accepted_schema_root()?
804 .map(|selection| selection.root().revision()))
805 }
806
807 pub(in crate::db) fn current_accepted_schema_authority_matches(
810 &self,
811 expected: &AcceptedSchemaAuthority,
812 ) -> Result<bool, InternalError> {
813 let Some(store_scope) = self.accepted_catalog_scope.get() else {
814 return Ok(false);
815 };
816
817 if let Some(cached) = self
820 .accepted_bundle_cache
821 .try_borrow()
822 .map_err(|_| InternalError::store_invariant())?
823 .as_ref()
824 {
825 let root = cached.selection.root();
826 return Ok(expected.matches_store_root(
827 store_scope,
828 root.revision(),
829 root.fingerprint(),
830 ));
831 }
832
833 let Some(selection) = self.current_accepted_schema_root()? else {
834 return Ok(false);
835 };
836 let root = selection.root();
837
838 Ok(expected.matches_store_root(store_scope, root.revision(), root.fingerprint()))
839 }
840
841 pub(in crate::db) fn publish_accepted_schema_candidate(
845 &mut self,
846 expected_revision: AcceptedSchemaRevision,
847 candidate: &CandidateSchemaRevision,
848 ) -> Result<(), InternalError> {
849 if self.current_root_matches_candidate(candidate)? {
850 let selection = self
851 .current_accepted_schema_root()?
852 .ok_or_else(InternalError::store_corruption)?;
853 self.retain_durable_candidate_entries(candidate, selection.slot())?;
854 return Ok(());
855 }
856 let first = self.accepted_root_slot_bytes(0)?;
857 let second = self.accepted_root_slot_bytes(1)?;
858 prepare_accepted_schema_root_publication(
859 [first.as_deref(), second.as_deref()],
860 expected_revision,
861 candidate,
862 )
863 .map_err(map_schema_publication_error)?;
864
865 self.insert_durable_candidate_snapshots(candidate)?;
866 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
867 self.insert_durable_raw_value(bundle_key, candidate.encoded_bundle().to_vec());
868 let persisted_bundle = self
869 .get_raw_snapshot(&bundle_key)
870 .ok_or_else(InternalError::store_corruption)?;
871 let _verified = decode_verified_accepted_schema_revision_bundle(
872 candidate.root(),
873 persisted_bundle.as_bytes(),
874 )?;
875
876 let first = self.accepted_root_slot_bytes(0)?;
879 let second = self.accepted_root_slot_bytes(1)?;
880 let publication = prepare_accepted_schema_root_publication(
881 [first.as_deref(), second.as_deref()],
882 expected_revision,
883 candidate,
884 )
885 .map_err(map_schema_publication_error)?;
886 let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
887 self.insert_durable_raw_value(root_key, publication.encoded_root().to_vec());
888
889 let selected = self
890 .current_accepted_schema_root()?
891 .ok_or_else(InternalError::store_corruption)?;
892 if selected.root() != candidate.root() {
893 return Err(InternalError::store_corruption());
894 }
895 self.retain_durable_candidate_entries(candidate, selected.slot())?;
896 Ok(())
897 }
898
899 pub(in crate::db) fn apply_journaled_accepted_schema_candidate(
901 &mut self,
902 expected_revision: AcceptedSchemaRevision,
903 candidate: &CandidateSchemaRevision,
904 ) -> Result<(), InternalError> {
905 if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
906 return Err(InternalError::store_invariant());
907 }
908 if self.current_root_matches_candidate(candidate)? {
909 let selection = self
910 .current_accepted_schema_root()?
911 .ok_or_else(InternalError::store_corruption)?;
912 self.retain_materialized_candidate_entries(candidate, selection.slot())?;
913 return Ok(());
914 }
915
916 let first = self.accepted_root_slot_bytes(0)?;
917 let second = self.accepted_root_slot_bytes(1)?;
918 prepare_accepted_schema_root_publication(
919 [first.as_deref(), second.as_deref()],
920 expected_revision,
921 candidate,
922 )
923 .map_err(map_schema_publication_error)?;
924
925 for (entity_tag, snapshot) in candidate.bundle().entity_snapshots() {
926 self.insert_persisted_snapshot(*entity_tag, snapshot)?;
927 }
928 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
929 self.insert_raw_snapshot(
930 bundle_key,
931 RawSchemaSnapshot::from_encoded_control_record(candidate.encoded_bundle().to_vec()),
932 );
933 let persisted_bundle = self
934 .get_raw_snapshot(&bundle_key)
935 .ok_or_else(InternalError::store_corruption)?;
936 let _verified = decode_verified_accepted_schema_revision_bundle(
937 candidate.root(),
938 persisted_bundle.as_bytes(),
939 )?;
940
941 let first = self.accepted_root_slot_bytes(0)?;
942 let second = self.accepted_root_slot_bytes(1)?;
943 let publication = prepare_accepted_schema_root_publication(
944 [first.as_deref(), second.as_deref()],
945 expected_revision,
946 candidate,
947 )
948 .map_err(map_schema_publication_error)?;
949 let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
950 self.insert_raw_snapshot(
951 root_key,
952 RawSchemaSnapshot::from_encoded_control_record(publication.encoded_root().to_vec()),
953 );
954
955 if !self.current_root_matches_candidate(candidate)? {
956 return Err(InternalError::store_corruption());
957 }
958 let selection = self
959 .current_accepted_schema_root()?
960 .ok_or_else(InternalError::store_corruption)?;
961 self.retain_materialized_candidate_entries(candidate, selection.slot())?;
962 Ok(())
963 }
964
965 pub(in crate::db) fn fold_journaled_accepted_schema_candidate(
967 &mut self,
968 expected_revision: AcceptedSchemaRevision,
969 candidate: &CandidateSchemaRevision,
970 ) -> Result<(), InternalError> {
971 if self.canonical_root_matches_candidate(candidate)? {
972 let first = self.canonical_root_slot_bytes(0)?;
973 let second = self.canonical_root_slot_bytes(1)?;
974 let selection =
975 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
976 .ok_or_else(InternalError::store_corruption)?;
977 self.retain_canonical_candidate_entries(candidate, selection.slot())?;
978 return Ok(());
979 }
980
981 let first = self.canonical_root_slot_bytes(0)?;
982 let second = self.canonical_root_slot_bytes(1)?;
983 prepare_accepted_schema_root_publication(
984 [first.as_deref(), second.as_deref()],
985 expected_revision,
986 candidate,
987 )
988 .map_err(map_schema_publication_error)?;
989
990 for (entity_tag, snapshot) in candidate.bundle().entity_snapshots() {
991 self.fold_persisted_snapshot(*entity_tag, snapshot)?;
992 }
993 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
994 self.insert_canonical_raw_value(bundle_key, candidate.encoded_bundle().to_vec())?;
995 let persisted_bundle = self
996 .get_canonical_raw_value(&bundle_key)?
997 .ok_or_else(InternalError::store_corruption)?;
998 let _verified = decode_verified_accepted_schema_revision_bundle(
999 candidate.root(),
1000 persisted_bundle.as_bytes(),
1001 )?;
1002
1003 let first = self.canonical_root_slot_bytes(0)?;
1004 let second = self.canonical_root_slot_bytes(1)?;
1005 let publication = prepare_accepted_schema_root_publication(
1006 [first.as_deref(), second.as_deref()],
1007 expected_revision,
1008 candidate,
1009 )
1010 .map_err(map_schema_publication_error)?;
1011 let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
1012 self.insert_canonical_raw_value(root_key, publication.encoded_root().to_vec())?;
1013
1014 if !self.canonical_root_matches_candidate(candidate)? {
1015 return Err(InternalError::store_corruption());
1016 }
1017 let first = self.canonical_root_slot_bytes(0)?;
1018 let second = self.canonical_root_slot_bytes(1)?;
1019 let selection = select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
1020 .ok_or_else(InternalError::store_corruption)?;
1021 self.retain_canonical_candidate_entries(candidate, selection.slot())?;
1022 Ok(())
1023 }
1024
1025 #[cfg(test)]
1027 pub(in crate::db) fn get_persisted_snapshot(
1028 &self,
1029 entity: EntityTag,
1030 version: SchemaVersion,
1031 ) -> Result<Option<PersistedSchemaSnapshot>, InternalError> {
1032 let key = RawSchemaKey::from_entity_version(entity, version);
1033 self.get_raw_snapshot(&key)
1034 .map(|snapshot| snapshot.decode_persisted_snapshot())
1035 .transpose()
1036 }
1037
1038 pub(in crate::db) fn latest_staged_persisted_snapshot(
1043 &self,
1044 entity: EntityTag,
1045 ) -> Result<Option<PersistedSchemaSnapshot>, InternalError> {
1046 self.latest_raw_snapshot(entity)
1047 .map(|snapshot| snapshot.decode_persisted_snapshot())
1048 .transpose()
1049 }
1050
1051 pub(in crate::db) fn current_accepted_persisted_snapshot(
1054 &self,
1055 entity: EntityTag,
1056 ) -> Result<Option<PersistedSchemaSnapshot>, InternalError> {
1057 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
1058 return Ok(None);
1059 };
1060
1061 Ok(bundle.entity_snapshots().get(&entity).cloned())
1062 }
1063
1064 pub(in crate::db) fn current_accepted_catalog_selection(
1066 &self,
1067 entity: EntityTag,
1068 entity_path: &'static str,
1069 store_path: &'static str,
1070 ) -> Result<Option<AcceptedCatalogSnapshotSelection>, InternalError> {
1071 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
1072 return Ok(None);
1073 };
1074 if bundle.store_path() != store_path {
1075 return Err(InternalError::store_corruption());
1076 }
1077 let Some(snapshot) = bundle.entity_snapshots().get(&entity) else {
1078 return Ok(None);
1079 };
1080 if snapshot.entity_path() != entity_path {
1081 return Err(InternalError::store_corruption());
1082 }
1083
1084 let raw_snapshot = RawSchemaSnapshot::from_persisted_snapshot(snapshot)?;
1085 let fingerprint = raw_snapshot.accepted_schema_fingerprint()?;
1086 let identity = AcceptedCatalogIdentity::new(
1087 entity,
1088 entity_path,
1089 store_path,
1090 bundle.revision(),
1091 snapshot.version(),
1092 fingerprint,
1093 );
1094
1095 Ok(Some(AcceptedCatalogSnapshotSelection::new(
1096 identity,
1097 AcceptedEnumCatalogHandle::new(
1098 bundle.enum_catalog().clone(),
1099 self.accepted_catalog_scope
1100 .get_or_init(AcceptedStoreCatalogScope::new)
1101 .clone(),
1102 bundle.revision(),
1103 self.current_accepted_schema_root()?
1104 .ok_or_else(InternalError::store_corruption)?
1105 .root()
1106 .fingerprint(),
1107 ),
1108 raw_snapshot.into_bytes(),
1109 )))
1110 }
1111
1112 #[must_use]
1114 pub(in crate::db) fn entity_footprint(&self, entity: EntityTag) -> SchemaStoreFootprint {
1115 let mut snapshots = 0u64;
1116 let mut encoded_bytes = 0u64;
1117 let mut latest = None::<(SchemaVersion, u64)>;
1118
1119 let _: Result<(), std::convert::Infallible> = self.visit_raw_snapshots(|key, snapshot| {
1120 if key.entity_tag() != entity {
1121 return Ok(SchemaStoreVisit::Continue);
1122 }
1123
1124 let snapshot_bytes = u64::try_from(snapshot.as_bytes().len()).unwrap_or(u64::MAX);
1125 snapshots = snapshots.saturating_add(1);
1126 encoded_bytes = encoded_bytes.saturating_add(snapshot_bytes);
1127
1128 let version = SchemaVersion::new(key.version());
1129 if latest
1130 .as_ref()
1131 .is_none_or(|(latest_version, _)| version > *latest_version)
1132 {
1133 latest = Some((version, snapshot_bytes));
1134 }
1135 Ok(SchemaStoreVisit::Continue)
1136 });
1137
1138 SchemaStoreFootprint::new(
1139 snapshots,
1140 encoded_bytes,
1141 latest.map_or(0, |(_, snapshot_bytes)| snapshot_bytes),
1142 )
1143 }
1144
1145 #[cfg(test)]
1151 pub(in crate::db) fn catalog_metadata(
1152 &self,
1153 ) -> Result<Option<SchemaStoreCatalogMetadata>, InternalError> {
1154 Ok(self
1155 .allocation_metadata()?
1156 .map(SchemaStoreAllocationMetadata::schema))
1157 }
1158
1159 pub(in crate::db) fn allocation_metadata(
1166 &self,
1167 ) -> Result<Option<SchemaStoreAllocationMetadata>, InternalError> {
1168 let latest_by_entity = self.latest_raw_snapshots_by_entity();
1169 if latest_by_entity.is_empty() {
1170 return Ok(None);
1171 }
1172
1173 Ok(Some(SchemaStoreAllocationMetadata::new(
1174 derive_data_allocation_metadata(&latest_by_entity)?,
1175 derive_index_allocation_metadata(&latest_by_entity)?,
1176 derive_schema_catalog_metadata(&latest_by_entity)?,
1177 )))
1178 }
1179
1180 fn insert_raw_snapshot(
1182 &mut self,
1183 key: RawSchemaKey,
1184 snapshot: RawSchemaSnapshot,
1185 ) -> Option<RawSchemaSnapshot> {
1186 self.invalidate_accepted_bundle_cache_for_key(key);
1187 let previous_journaled = if matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1188 self.get_raw_snapshot_for_backend(&key)
1189 } else {
1190 None
1191 };
1192 match &mut self.backend {
1193 SchemaStoreBackend::Heap(map) => map.insert(key, snapshot),
1194 SchemaStoreBackend::Journaled {
1195 live, tombstones, ..
1196 } => {
1197 tombstones.remove(&key);
1198 live.insert(key, snapshot);
1199 previous_journaled
1200 }
1201 }
1202 }
1203
1204 #[must_use]
1206 fn get_raw_snapshot(&self, key: &RawSchemaKey) -> Option<RawSchemaSnapshot> {
1207 match &self.backend {
1208 SchemaStoreBackend::Heap(map) => map.get(key).cloned(),
1209 SchemaStoreBackend::Journaled { .. } => self.get_raw_snapshot_for_backend(key),
1210 }
1211 }
1212
1213 fn accepted_root_slot_bytes(&self, slot: usize) -> Result<Option<Vec<u8>>, InternalError> {
1214 let key = RawSchemaKey::from_accepted_root_slot(slot)?;
1215 Ok(self
1216 .get_raw_snapshot(&key)
1217 .map(RawSchemaSnapshot::into_bytes))
1218 }
1219
1220 fn canonical_root_slot_bytes(&self, slot: usize) -> Result<Option<Vec<u8>>, InternalError> {
1221 let key = RawSchemaKey::from_accepted_root_slot(slot)?;
1222 Ok(self
1223 .get_canonical_raw_value(&key)?
1224 .map(RawSchemaSnapshot::into_bytes))
1225 }
1226
1227 fn current_root_matches_candidate(
1228 &self,
1229 candidate: &CandidateSchemaRevision,
1230 ) -> Result<bool, InternalError> {
1231 let Some(selection) = self.current_accepted_schema_root()? else {
1232 return Ok(false);
1233 };
1234 if selection.root() != candidate.root() {
1235 return Ok(false);
1236 }
1237 let key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
1238 let bundle = self
1239 .get_raw_snapshot(&key)
1240 .ok_or_else(InternalError::store_corruption)?;
1241 let _verified =
1242 decode_verified_accepted_schema_revision_bundle(candidate.root(), bundle.as_bytes())?;
1243 Ok(true)
1244 }
1245
1246 fn canonical_root_matches_candidate(
1247 &self,
1248 candidate: &CandidateSchemaRevision,
1249 ) -> Result<bool, InternalError> {
1250 let first = self.canonical_root_slot_bytes(0)?;
1251 let second = self.canonical_root_slot_bytes(1)?;
1252 let Some(selection) =
1253 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
1254 else {
1255 return Ok(false);
1256 };
1257 if selection.root() != candidate.root() {
1258 return Ok(false);
1259 }
1260 let key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
1261 let bundle = self
1262 .get_canonical_raw_value(&key)?
1263 .ok_or_else(InternalError::store_corruption)?;
1264 let _verified =
1265 decode_verified_accepted_schema_revision_bundle(candidate.root(), bundle.as_bytes())?;
1266 Ok(true)
1267 }
1268
1269 fn get_canonical_raw_value(
1270 &self,
1271 key: &RawSchemaKey,
1272 ) -> Result<Option<RawSchemaSnapshot>, InternalError> {
1273 match &self.backend {
1274 SchemaStoreBackend::Journaled { canonical, .. } => Ok(canonical.get(key)),
1275 SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
1276 }
1277 }
1278
1279 fn insert_canonical_raw_value(
1280 &mut self,
1281 key: RawSchemaKey,
1282 bytes: Vec<u8>,
1283 ) -> Result<(), InternalError> {
1284 self.invalidate_accepted_bundle_cache_for_key(key);
1285 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1286 return Err(InternalError::store_invariant());
1287 };
1288 canonical.insert(key, RawSchemaSnapshot::from_encoded_control_record(bytes));
1289 Ok(())
1290 }
1291
1292 fn insert_durable_raw_value(&mut self, key: RawSchemaKey, bytes: Vec<u8>) {
1296 self.invalidate_accepted_bundle_cache_for_key(key);
1297 let value = RawSchemaSnapshot::from_encoded_control_record(bytes);
1298 match &mut self.backend {
1299 SchemaStoreBackend::Heap(map) => {
1300 map.insert(key, value);
1301 }
1302 SchemaStoreBackend::Journaled {
1303 canonical,
1304 live,
1305 tombstones,
1306 } => {
1307 live.remove(&key);
1308 tombstones.remove(&key);
1309 canonical.insert(key, value);
1310 }
1311 }
1312 }
1313
1314 fn invalidate_accepted_bundle_cache_for_key(&mut self, key: RawSchemaKey) {
1315 if key.is_accepted_root() {
1316 self.accepted_bundle_cache.get_mut().take();
1317 }
1318 }
1319
1320 fn insert_durable_candidate_snapshots(
1321 &mut self,
1322 candidate: &CandidateSchemaRevision,
1323 ) -> Result<(), InternalError> {
1324 for (entity_tag, snapshot) in candidate.bundle().entity_snapshots() {
1325 let key = RawSchemaKey::from_entity_version(*entity_tag, snapshot.version());
1326 let value = RawSchemaSnapshot::from_persisted_snapshot(snapshot)?;
1327 match &mut self.backend {
1328 SchemaStoreBackend::Heap(map) => {
1329 map.insert(key, value);
1330 }
1331 SchemaStoreBackend::Journaled {
1332 canonical,
1333 live,
1334 tombstones,
1335 } => {
1336 live.remove(&key);
1337 tombstones.remove(&key);
1338 canonical.insert(key, value);
1339 }
1340 }
1341 }
1342 Ok(())
1343 }
1344
1345 fn candidate_entry_keys(
1346 candidate: &CandidateSchemaRevision,
1347 root_slot: usize,
1348 ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
1349 let mut keys = candidate
1350 .bundle()
1351 .entity_snapshots()
1352 .iter()
1353 .map(|(entity_tag, snapshot)| {
1354 RawSchemaKey::from_entity_version(*entity_tag, snapshot.version())
1355 })
1356 .collect::<BTreeSet<_>>();
1357 keys.insert(RawSchemaKey::from_accepted_bundle(
1358 candidate.root().bundle_key(),
1359 ));
1360 keys.insert(RawSchemaKey::from_accepted_root_slot(root_slot)?);
1361 Ok(keys)
1362 }
1363
1364 fn retain_durable_candidate_entries(
1368 &mut self,
1369 candidate: &CandidateSchemaRevision,
1370 root_slot: usize,
1371 ) -> Result<(), InternalError> {
1372 let keep = Self::candidate_entry_keys(candidate, root_slot)?;
1373 self.accepted_bundle_cache.get_mut().take();
1374 match &mut self.backend {
1375 SchemaStoreBackend::Heap(map) => map.retain(|key, _| keep.contains(key)),
1376 SchemaStoreBackend::Journaled {
1377 canonical,
1378 live,
1379 tombstones,
1380 } => {
1381 let stale = canonical
1382 .iter()
1383 .filter_map(|entry| (!keep.contains(entry.key())).then_some(*entry.key()))
1384 .collect::<Vec<_>>();
1385 for key in stale {
1386 canonical.remove(&key);
1387 }
1388 live.retain(|key, _| keep.contains(key));
1389 tombstones.clear();
1390 }
1391 }
1392 Ok(())
1393 }
1394
1395 fn retain_materialized_candidate_entries(
1396 &mut self,
1397 candidate: &CandidateSchemaRevision,
1398 root_slot: usize,
1399 ) -> Result<(), InternalError> {
1400 let keep = Self::candidate_entry_keys(candidate, root_slot)?;
1401 self.accepted_bundle_cache.get_mut().take();
1402 let SchemaStoreBackend::Journaled {
1403 canonical,
1404 live,
1405 tombstones,
1406 } = &mut self.backend
1407 else {
1408 return Err(InternalError::store_invariant());
1409 };
1410 live.retain(|key, _| keep.contains(key));
1411 let canonical_keys = canonical
1412 .iter()
1413 .map(|entry| *entry.key())
1414 .collect::<Vec<_>>();
1415 for key in canonical_keys {
1416 if keep.contains(&key) {
1417 tombstones.remove(&key);
1418 } else {
1419 tombstones.insert(key);
1420 }
1421 }
1422 Ok(())
1423 }
1424
1425 fn retain_canonical_candidate_entries(
1426 &mut self,
1427 candidate: &CandidateSchemaRevision,
1428 root_slot: usize,
1429 ) -> Result<(), InternalError> {
1430 let keep = Self::candidate_entry_keys(candidate, root_slot)?;
1431 self.accepted_bundle_cache.get_mut().take();
1432 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1433 return Err(InternalError::store_invariant());
1434 };
1435 let stale = canonical
1436 .iter()
1437 .filter_map(|entry| (!keep.contains(entry.key())).then_some(*entry.key()))
1438 .collect::<Vec<_>>();
1439 for key in stale {
1440 canonical.remove(&key);
1441 }
1442 Ok(())
1443 }
1444
1445 #[must_use]
1447 #[cfg(test)]
1448 fn contains_raw_snapshot(&self, key: &RawSchemaKey) -> bool {
1449 match &self.backend {
1450 SchemaStoreBackend::Heap(map) => map.contains_key(key),
1451 SchemaStoreBackend::Journaled { .. } => {
1452 self.get_raw_snapshot_for_backend(key).is_some()
1453 }
1454 }
1455 }
1456
1457 #[must_use]
1459 #[cfg(test)]
1460 pub(in crate::db) fn len(&self) -> u64 {
1461 match &self.backend {
1462 SchemaStoreBackend::Heap(map) => u64::try_from(map.len()).unwrap_or(u64::MAX),
1463 SchemaStoreBackend::Journaled { .. } => {
1464 let mut count = 0_u64;
1465 let _: Result<(), Infallible> = self.visit_raw_snapshots(|_key, _snapshot| {
1466 count = count.saturating_add(1);
1467 Ok(SchemaStoreVisit::Continue)
1468 });
1469 count
1470 }
1471 }
1472 }
1473
1474 #[must_use]
1476 #[cfg(test)]
1477 pub(in crate::db) fn is_empty(&self) -> bool {
1478 match &self.backend {
1479 SchemaStoreBackend::Heap(map) => map.is_empty(),
1480 SchemaStoreBackend::Journaled { .. } => {
1481 let mut empty = true;
1482 let _: Result<(), Infallible> = self.visit_raw_snapshots(|_key, _snapshot| {
1483 empty = false;
1484 Ok(SchemaStoreVisit::Stop)
1485 });
1486 empty
1487 }
1488 }
1489 }
1490
1491 #[cfg(test)]
1493 pub(in crate::db) fn clear(&mut self) {
1494 self.accepted_bundle_cache.get_mut().take();
1495 match &mut self.backend {
1496 SchemaStoreBackend::Heap(map) => map.clear(),
1497 SchemaStoreBackend::Journaled {
1498 canonical,
1499 live,
1500 tombstones,
1501 } => {
1502 live.clear();
1503 tombstones.clear();
1504 let keys = canonical
1505 .iter()
1506 .map(|entry| *entry.key())
1507 .collect::<Vec<_>>();
1508 for key in keys {
1509 if key.is_entity_snapshot() {
1510 tombstones.insert(key);
1511 } else {
1512 canonical.remove(&key);
1513 }
1514 }
1515 }
1516 }
1517 }
1518
1519 fn current_accepted_schema_bundle_ref(
1520 &self,
1521 ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
1522 let Some(selection) = self.current_accepted_schema_root()? else {
1523 self.accepted_bundle_cache
1524 .try_borrow_mut()
1525 .map_err(|_| InternalError::store_invariant())?
1526 .take();
1527 return Ok(None);
1528 };
1529
1530 let cache_matches = self
1531 .accepted_bundle_cache
1532 .try_borrow()
1533 .map_err(|_| InternalError::store_invariant())?
1534 .as_ref()
1535 .is_some_and(|cached| cached.selection == selection);
1536 if !cache_matches {
1537 let key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
1538 let raw = self
1539 .get_raw_snapshot(&key)
1540 .ok_or_else(InternalError::store_corruption)?;
1541 let bundle =
1542 decode_verified_accepted_schema_revision_bundle(selection.root(), raw.as_bytes())?;
1543 #[cfg(test)]
1544 ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES
1545 .with(|misses| misses.set(misses.get().saturating_add(1)));
1546 *self
1547 .accepted_bundle_cache
1548 .try_borrow_mut()
1549 .map_err(|_| InternalError::store_invariant())? =
1550 Some(AcceptedSchemaBundleCache { selection, bundle });
1551 }
1552
1553 let cache = self
1554 .accepted_bundle_cache
1555 .try_borrow()
1556 .map_err(|_| InternalError::store_invariant())?;
1557 Ref::filter_map(cache, |cache| {
1558 cache
1559 .as_ref()
1560 .filter(|cached| cached.selection == selection)
1561 .map(|cached| &cached.bundle)
1562 })
1563 .map(Some)
1564 .map_err(|_| InternalError::store_invariant())
1565 }
1566
1567 fn latest_raw_snapshots_by_entity(
1568 &self,
1569 ) -> StdBTreeMap<EntityTag, (SchemaVersion, RawSchemaSnapshot)> {
1570 #[cfg(test)]
1571 LATEST_RAW_SNAPSHOTS_BY_ENTITY_CALLS.with(|calls| calls.set(calls.get().saturating_add(1)));
1572
1573 let mut latest_by_entity =
1574 StdBTreeMap::<EntityTag, (SchemaVersion, RawSchemaSnapshot)>::new();
1575
1576 let _: Result<(), std::convert::Infallible> = self.visit_raw_snapshots(|key, snapshot| {
1577 let version = SchemaVersion::new(key.version());
1578 match latest_by_entity.get_mut(&key.entity_tag()) {
1579 Some((latest_version, latest_snapshot)) if version > *latest_version => {
1580 *latest_version = version;
1581 *latest_snapshot = snapshot.clone();
1582 }
1583 None => {
1584 latest_by_entity.insert(key.entity_tag(), (version, snapshot.clone()));
1585 }
1586 Some(_) => {}
1587 }
1588 Ok(SchemaStoreVisit::Continue)
1589 });
1590
1591 latest_by_entity
1592 }
1593
1594 fn visit_raw_snapshots<E>(
1597 &self,
1598 visitor: impl FnMut(&RawSchemaKey, &RawSchemaSnapshot) -> Result<SchemaStoreVisit, E>,
1599 ) -> Result<(), E> {
1600 let bounds = RawSchemaKey::all_entity_range_bounds();
1601 match &self.backend {
1602 SchemaStoreBackend::Heap(map) => {
1603 let mut visitor = visitor;
1604 for (key, snapshot) in map.range((bounds.0, bounds.1)) {
1605 if visitor(key, snapshot)?.should_stop() {
1606 break;
1607 }
1608 }
1609 }
1610 SchemaStoreBackend::Journaled {
1611 canonical,
1612 live,
1613 tombstones,
1614 } => Self::visit_journaled_raw_snapshot_range(
1615 canonical,
1616 live,
1617 tombstones,
1618 bounds,
1619 Direction::Asc,
1620 visitor,
1621 )?,
1622 }
1623
1624 Ok(())
1625 }
1626
1627 #[cfg(test)]
1628 #[must_use]
1629 pub(in crate::db) fn canonical_len_for_tests(&self) -> u64 {
1630 match &self.backend {
1631 SchemaStoreBackend::Journaled { canonical: map, .. } => map.len(),
1632 SchemaStoreBackend::Heap(_) => 0,
1633 }
1634 }
1635
1636 fn get_raw_snapshot_for_backend(&self, key: &RawSchemaKey) -> Option<RawSchemaSnapshot> {
1637 let SchemaStoreBackend::Journaled {
1638 canonical,
1639 live,
1640 tombstones,
1641 } = &self.backend
1642 else {
1643 return None;
1644 };
1645
1646 if tombstones.contains(key) {
1647 return None;
1648 }
1649 live.get(key).cloned().or_else(|| canonical.get(key))
1650 }
1651
1652 fn latest_raw_snapshot(&self, entity: EntityTag) -> Option<RawSchemaSnapshot> {
1653 self.latest_raw_snapshot_entry(entity)
1654 .map(|(_, snapshot)| snapshot)
1655 }
1656
1657 fn latest_raw_snapshot_entry(
1658 &self,
1659 entity: EntityTag,
1660 ) -> Option<(SchemaVersion, RawSchemaSnapshot)> {
1661 let bounds = RawSchemaKey::entity_range_bounds(entity);
1662 match &self.backend {
1663 SchemaStoreBackend::Heap(map) => map
1664 .range((bounds.0, bounds.1))
1665 .next_back()
1666 .map(|(key, snapshot)| (SchemaVersion::new(key.version()), snapshot.clone())),
1667 SchemaStoreBackend::Journaled {
1668 canonical,
1669 live,
1670 tombstones,
1671 } => {
1672 let mut latest = None;
1673 let _: Result<(), Infallible> = Self::visit_journaled_raw_snapshot_range(
1674 canonical,
1675 live,
1676 tombstones,
1677 bounds,
1678 Direction::Desc,
1679 |key, snapshot| {
1680 latest = Some((SchemaVersion::new(key.version()), snapshot.clone()));
1681 Ok(SchemaStoreVisit::Stop)
1682 },
1683 );
1684 latest
1685 }
1686 }
1687 }
1688
1689 fn visit_journaled_raw_snapshot_range<E>(
1690 canonical: &StableBTreeMap<
1691 RawSchemaKey,
1692 RawSchemaSnapshot,
1693 VirtualMemory<DefaultMemoryImpl>,
1694 >,
1695 live: &StdBTreeMap<RawSchemaKey, RawSchemaSnapshot>,
1696 tombstones: &BTreeSet<RawSchemaKey>,
1697 bounds: (RangeBound<RawSchemaKey>, RangeBound<RawSchemaKey>),
1698 direction: Direction,
1699 mut visitor: impl FnMut(&RawSchemaKey, &RawSchemaSnapshot) -> Result<SchemaStoreVisit, E>,
1700 ) -> Result<(), E> {
1701 match direction {
1702 Direction::Asc => visit_ordered_overlay(
1703 canonical.range((bounds.0, bounds.1)),
1704 live.range((bounds.0, bounds.1)),
1705 Direction::Asc,
1706 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
1707 |canonical_entry| !tombstones.contains(canonical_entry.key()),
1708 |live_entry| !tombstones.contains(live_entry.0),
1709 |entry| {
1710 let visit = match entry {
1711 OrderedOverlayEntry::Canonical(canonical_entry) => {
1712 visitor(canonical_entry.key(), &canonical_entry.value())?
1713 }
1714 OrderedOverlayEntry::Live((key, snapshot)) => visitor(key, snapshot)?,
1715 };
1716 Ok(if visit.should_stop() {
1717 OrderedOverlayVisit::Stop
1718 } else {
1719 OrderedOverlayVisit::Continue
1720 })
1721 },
1722 ),
1723 Direction::Desc => visit_ordered_overlay(
1724 canonical.range((bounds.0, bounds.1)).rev(),
1725 live.range((bounds.0, bounds.1)).rev(),
1726 Direction::Desc,
1727 |canonical_entry, live_entry| canonical_entry.key().cmp(live_entry.0),
1728 |canonical_entry| !tombstones.contains(canonical_entry.key()),
1729 |live_entry| !tombstones.contains(live_entry.0),
1730 |entry| {
1731 let visit = match entry {
1732 OrderedOverlayEntry::Canonical(canonical_entry) => {
1733 visitor(canonical_entry.key(), &canonical_entry.value())?
1734 }
1735 OrderedOverlayEntry::Live((key, snapshot)) => visitor(key, snapshot)?,
1736 };
1737 Ok(if visit.should_stop() {
1738 OrderedOverlayVisit::Stop
1739 } else {
1740 OrderedOverlayVisit::Continue
1741 })
1742 },
1743 ),
1744 }
1745 }
1746}
1747
1748fn map_schema_publication_error(error: AcceptedSchemaPublicationError) -> InternalError {
1749 match error {
1750 AcceptedSchemaPublicationError::StaleSchemaRevision { .. }
1751 | AcceptedSchemaPublicationError::RevisionExhausted => InternalError::store_unsupported(),
1752 AcceptedSchemaPublicationError::InvalidCandidate => InternalError::store_invariant(),
1753 AcceptedSchemaPublicationError::CorruptRootSlots => InternalError::store_corruption(),
1754 }
1755}
1756
1757fn derive_data_allocation_metadata(
1758 latest_by_entity: &StdBTreeMap<EntityTag, (SchemaVersion, RawSchemaSnapshot)>,
1759) -> Result<SchemaStoreCatalogMetadata, InternalError> {
1760 let mut max_version = SchemaVersion::initial();
1761 let mut hasher = new_hash_sha256();
1762 write_hash_tag_u8(
1763 &mut hasher,
1764 SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_VERSION,
1765 );
1766
1767 for (entity, (_, snapshot)) in latest_by_entity {
1768 let persisted = snapshot.decode_persisted_snapshot()?;
1769 if persisted.version() > max_version {
1770 max_version = persisted.version();
1771 }
1772
1773 let data_projection = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
1774 persisted.version(),
1775 persisted.entity_path().to_string(),
1776 persisted.entity_name().to_string(),
1777 persisted.primary_key_field_ids().to_vec(),
1778 persisted.row_layout().clone(),
1779 persisted.fields().to_vec(),
1780 Vec::new(),
1781 );
1782 let encoded = encode_persisted_schema_snapshot(&data_projection)?;
1783
1784 write_hash_u64(&mut hasher, entity.value());
1785 write_hash_u32(&mut hasher, persisted.version().get());
1786 write_hash_len_u32(&mut hasher, encoded.len());
1787 hasher.update(encoded);
1788 }
1789
1790 Ok(finalize_schema_metadata(
1791 max_version,
1792 SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_VERSION,
1793 hasher,
1794 latest_by_entity.len(),
1795 ))
1796}
1797
1798fn derive_index_allocation_metadata(
1799 latest_by_entity: &StdBTreeMap<EntityTag, (SchemaVersion, RawSchemaSnapshot)>,
1800) -> Result<SchemaStoreCatalogMetadata, InternalError> {
1801 let mut max_version = SchemaVersion::initial();
1802 let mut hasher = new_hash_sha256();
1803 write_hash_tag_u8(
1804 &mut hasher,
1805 SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_VERSION,
1806 );
1807
1808 for (entity, (_, snapshot)) in latest_by_entity {
1809 let persisted = snapshot.decode_persisted_snapshot()?;
1810 if persisted.version() > max_version {
1811 max_version = persisted.version();
1812 }
1813
1814 write_hash_u64(&mut hasher, entity.value());
1815 write_hash_u32(&mut hasher, persisted.version().get());
1816 write_hash_len_u32(&mut hasher, persisted.indexes().len());
1817 for index in persisted.indexes() {
1818 write_hash_u32(&mut hasher, u32::from(index.ordinal()));
1819 write_hash_str_u32(&mut hasher, index.name());
1820 write_hash_str_u32(&mut hasher, index.store());
1821 write_hash_tag_u8(&mut hasher, u8::from(index.unique()));
1822 write_hash_str_u32(&mut hasher, persisted_index_origin_name(index.origin()));
1823 match index.predicate_sql() {
1824 Some(predicate_sql) => {
1825 write_hash_tag_u8(&mut hasher, 1);
1826 write_hash_str_u32(&mut hasher, predicate_sql);
1827 }
1828 None => write_hash_tag_u8(&mut hasher, 0),
1829 }
1830 hash_persisted_index_key(&mut hasher, index.key());
1831 }
1832 }
1833
1834 Ok(finalize_schema_metadata(
1835 max_version,
1836 SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_VERSION,
1837 hasher,
1838 latest_by_entity.len(),
1839 ))
1840}
1841
1842fn derive_schema_catalog_metadata(
1843 latest_by_entity: &StdBTreeMap<EntityTag, (SchemaVersion, RawSchemaSnapshot)>,
1844) -> Result<SchemaStoreCatalogMetadata, InternalError> {
1845 let mut max_version = SchemaVersion::initial();
1846 let mut hasher = new_hash_sha256();
1847 write_hash_tag_u8(&mut hasher, SCHEMA_STORE_CATALOG_FINGERPRINT_VERSION);
1848
1849 for (entity, (version, snapshot)) in latest_by_entity {
1850 let persisted = snapshot.decode_persisted_snapshot()?;
1851 if persisted.version() > max_version {
1852 max_version = persisted.version();
1853 }
1854
1855 write_hash_u64(&mut hasher, entity.value());
1856 write_hash_u32(&mut hasher, version.get());
1857 write_hash_len_u32(&mut hasher, snapshot.as_bytes().len());
1858 hasher.update(snapshot.as_bytes());
1859 }
1860
1861 Ok(finalize_schema_metadata(
1862 max_version,
1863 SCHEMA_STORE_CATALOG_FINGERPRINT_VERSION,
1864 hasher,
1865 latest_by_entity.len(),
1866 ))
1867}
1868
1869fn finalize_schema_metadata(
1870 schema_version: SchemaVersion,
1871 schema_fingerprint_method_version: u8,
1872 hasher: sha2::Sha256,
1873 entity_count: usize,
1874) -> SchemaStoreCatalogMetadata {
1875 let digest = finalize_hash_sha256(hasher);
1876 let mut schema_fingerprint = [0u8; 16];
1877 schema_fingerprint.copy_from_slice(&digest[..16]);
1878
1879 SchemaStoreCatalogMetadata::new(
1880 schema_version,
1881 schema_fingerprint_method_version,
1882 schema_fingerprint,
1883 u64::try_from(entity_count).unwrap_or(u64::MAX),
1884 )
1885}
1886
1887fn hash_persisted_index_key(hasher: &mut sha2::Sha256, key: &PersistedIndexKeySnapshot) {
1888 match key {
1889 PersistedIndexKeySnapshot::FieldPath(paths) => {
1890 write_hash_tag_u8(hasher, 1);
1891 write_hash_len_u32(hasher, paths.len());
1892 for path in paths {
1893 hash_persisted_index_field_path(hasher, path);
1894 }
1895 }
1896 PersistedIndexKeySnapshot::Items(items) => {
1897 write_hash_tag_u8(hasher, 2);
1898 write_hash_len_u32(hasher, items.len());
1899 for item in items {
1900 match item {
1901 PersistedIndexKeyItemSnapshot::FieldPath(path) => {
1902 write_hash_tag_u8(hasher, 1);
1903 hash_persisted_index_field_path(hasher, path);
1904 }
1905 PersistedIndexKeyItemSnapshot::Expression(expression) => {
1906 write_hash_tag_u8(hasher, 2);
1907 write_hash_str_u32(hasher, persisted_expression_op_name(expression.op()));
1908 hash_persisted_index_field_path(hasher, expression.source());
1909 hash_accepted_field_kind(hasher, expression.input_kind());
1910 hash_accepted_field_kind(hasher, expression.output_kind());
1911 write_hash_str_u32(hasher, expression.canonical_text());
1912 }
1913 }
1914 }
1915 }
1916 }
1917}
1918
1919fn hash_persisted_index_field_path(
1920 hasher: &mut sha2::Sha256,
1921 path: &crate::db::schema::PersistedIndexFieldPathSnapshot,
1922) {
1923 write_hash_u32(hasher, path.field_id().get());
1924 write_hash_u32(hasher, u32::from(path.slot().get()));
1925 write_hash_len_u32(hasher, path.path().len());
1926 for segment in path.path() {
1927 write_hash_str_u32(hasher, segment);
1928 }
1929 hash_accepted_field_kind(hasher, path.kind());
1930 write_hash_tag_u8(hasher, u8::from(path.nullable()));
1931}
1932
1933fn hash_accepted_field_kind(hasher: &mut sha2::Sha256, kind: &AcceptedFieldKind) {
1934 match kind {
1935 AcceptedFieldKind::Account => write_hash_tag_u8(hasher, 1),
1936 AcceptedFieldKind::Blob { max_len } => {
1937 write_hash_tag_u8(hasher, 2);
1938 hash_optional_u32(hasher, *max_len);
1939 }
1940 AcceptedFieldKind::Bool => write_hash_tag_u8(hasher, 3),
1941 AcceptedFieldKind::Date => write_hash_tag_u8(hasher, 4),
1942 AcceptedFieldKind::Decimal { scale } => {
1943 write_hash_tag_u8(hasher, 5);
1944 write_hash_u32(hasher, *scale);
1945 }
1946 AcceptedFieldKind::Duration => write_hash_tag_u8(hasher, 6),
1947 AcceptedFieldKind::Enum { type_id } => {
1948 write_hash_tag_u8(hasher, 7);
1949 write_hash_u32(hasher, type_id.get());
1950 }
1951 AcceptedFieldKind::Float32 => write_hash_tag_u8(hasher, 8),
1952 AcceptedFieldKind::Float64 => write_hash_tag_u8(hasher, 9),
1953 AcceptedFieldKind::Int8 => write_hash_tag_u8(hasher, 10),
1954 AcceptedFieldKind::Int16 => write_hash_tag_u8(hasher, 11),
1955 AcceptedFieldKind::Int32 => write_hash_tag_u8(hasher, 12),
1956 AcceptedFieldKind::Int64 => write_hash_tag_u8(hasher, 13),
1957 AcceptedFieldKind::Int128 => write_hash_tag_u8(hasher, 14),
1958 AcceptedFieldKind::IntBig { max_bytes } => {
1959 write_hash_tag_u8(hasher, 15);
1960 write_hash_u32(hasher, *max_bytes);
1961 }
1962 AcceptedFieldKind::Principal => write_hash_tag_u8(hasher, 16),
1963 AcceptedFieldKind::Subaccount => write_hash_tag_u8(hasher, 17),
1964 AcceptedFieldKind::Text { max_len } => {
1965 write_hash_tag_u8(hasher, 18);
1966 hash_optional_u32(hasher, *max_len);
1967 }
1968 AcceptedFieldKind::Timestamp => write_hash_tag_u8(hasher, 19),
1969 AcceptedFieldKind::Nat8 => write_hash_tag_u8(hasher, 20),
1970 AcceptedFieldKind::Nat16 => write_hash_tag_u8(hasher, 21),
1971 AcceptedFieldKind::Nat32 => write_hash_tag_u8(hasher, 22),
1972 AcceptedFieldKind::Nat64 => write_hash_tag_u8(hasher, 23),
1973 AcceptedFieldKind::Nat128 => write_hash_tag_u8(hasher, 24),
1974 AcceptedFieldKind::NatBig { max_bytes } => {
1975 write_hash_tag_u8(hasher, 25);
1976 write_hash_u32(hasher, *max_bytes);
1977 }
1978 AcceptedFieldKind::Ulid => write_hash_tag_u8(hasher, 26),
1979 AcceptedFieldKind::Unit => write_hash_tag_u8(hasher, 27),
1980 AcceptedFieldKind::Relation {
1981 target_path,
1982 target_entity_name,
1983 target_entity_tag,
1984 target_store_path,
1985 key_kind,
1986 strength,
1987 } => {
1988 write_hash_tag_u8(hasher, 28);
1989 write_hash_str_u32(hasher, target_path);
1990 write_hash_str_u32(hasher, target_entity_name);
1991 write_hash_u64(hasher, target_entity_tag.value());
1992 write_hash_str_u32(hasher, target_store_path);
1993 hash_accepted_field_kind(hasher, key_kind);
1994 write_hash_str_u32(hasher, accepted_relation_strength_name(*strength));
1995 }
1996 AcceptedFieldKind::List(inner) => {
1997 write_hash_tag_u8(hasher, 29);
1998 hash_accepted_field_kind(hasher, inner);
1999 }
2000 AcceptedFieldKind::Set(inner) => {
2001 write_hash_tag_u8(hasher, 30);
2002 hash_accepted_field_kind(hasher, inner);
2003 }
2004 AcceptedFieldKind::Map { key, value } => {
2005 write_hash_tag_u8(hasher, 31);
2006 hash_accepted_field_kind(hasher, key);
2007 hash_accepted_field_kind(hasher, value);
2008 }
2009 AcceptedFieldKind::Structured { queryable } => {
2010 write_hash_tag_u8(hasher, 32);
2011 write_hash_tag_u8(hasher, u8::from(*queryable));
2012 }
2013 }
2014}
2015
2016fn hash_optional_u32(hasher: &mut sha2::Sha256, value: Option<u32>) {
2017 match value {
2018 Some(value) => {
2019 write_hash_tag_u8(hasher, 1);
2020 write_hash_u32(hasher, value);
2021 }
2022 None => write_hash_tag_u8(hasher, 0),
2023 }
2024}
2025
2026const fn persisted_index_origin_name(
2027 origin: crate::db::schema::PersistedIndexOrigin,
2028) -> &'static str {
2029 match origin {
2030 crate::db::schema::PersistedIndexOrigin::Generated => "generated",
2031 crate::db::schema::PersistedIndexOrigin::SqlDdl => "sql_ddl",
2032 }
2033}
2034
2035const fn persisted_expression_op_name(
2036 op: crate::db::schema::PersistedIndexExpressionOp,
2037) -> &'static str {
2038 match op {
2039 crate::db::schema::PersistedIndexExpressionOp::Lower => "lower",
2040 crate::db::schema::PersistedIndexExpressionOp::Upper => "upper",
2041 crate::db::schema::PersistedIndexExpressionOp::Trim => "trim",
2042 crate::db::schema::PersistedIndexExpressionOp::LowerTrim => "lower_trim",
2043 crate::db::schema::PersistedIndexExpressionOp::Date => "date",
2044 crate::db::schema::PersistedIndexExpressionOp::Year => "year",
2045 crate::db::schema::PersistedIndexExpressionOp::Month => "month",
2046 crate::db::schema::PersistedIndexExpressionOp::Day => "day",
2047 }
2048}
2049
2050const fn accepted_relation_strength_name(
2051 strength: crate::db::schema::AcceptedRelationStrength,
2052) -> &'static str {
2053 match strength {
2054 crate::db::schema::AcceptedRelationStrength::Strong => "strong",
2055 crate::db::schema::AcceptedRelationStrength::Weak => "weak",
2056 }
2057}
2058
2059#[cfg(test)]
2064mod tests;