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