1use crate::db::schema::identity_state::{
7 IdentityAdvanceId, IdentityRangeAdvance, IdentityRangeCommitState, IdentityState,
8 IdentityStateInventory, IdentityStateLifecycle, IdentityStateTransition,
9 IdentityStatementCursor, MAX_IDENTITY_STATE_RECORDS_PER_DATABASE, decode_identity_state,
10 encode_identity_state, prepare_identity_state_transition, validate_identity_state_closure,
11};
12use crate::db::schema::{
13 cardinality_build::{
14 CardinalityAcceptedDomain, CardinalityReadyCandidate, EmptyCardinalityReadyCandidate,
15 },
16 cardinality_generation::{
17 CardinalityAcceptedRootIdentity, CardinalityBuildCursor, CardinalityCountDigest,
18 CardinalityCountRecord, CardinalityCountSlot, CardinalityGenerationHeader,
19 CardinalityGenerationId, CardinalityGenerationState, CardinalitySourceIdentity,
20 },
21};
22use crate::{
23 db::{
24 codec::{
25 finalize_hash_sha256, new_hash_sha256, write_hash_len_u32, write_hash_str_u32,
26 write_hash_tag_u8, write_hash_u32, write_hash_u64,
27 },
28 commit::CommitSchemaFingerprint,
29 direction::Direction,
30 integrity::DatabaseIncarnationId,
31 journal::{JournalBatch, JournalRecord},
32 ordered_overlay::{OrderedOverlayEntry, ordered_overlay_entries},
33 positioned_overlay::{
34 JournalOverlayPosition, PositionedOverlayMetadata, PositionedOverlayRetirement,
35 },
36 runtime_entity_catalog::AcceptedRuntimeEntity,
37 schema::{
38 AcceptedFieldKind, AcceptedSchemaSnapshot, ConstraintActivationKind,
39 ConstraintActivationState, ConstraintId, ConstraintOrigin, ConstraintValidationJob,
40 FieldId, PersistedIndexKeyItemSnapshot, PersistedIndexKeySnapshot,
41 PersistedSchemaSnapshot, SchemaVersion, accepted_schema_cache_fingerprint,
42 accepted_schema_cache_fingerprint_for_persisted_snapshot,
43 accepted_schema_cache_fingerprint_method_version, decode_constraint_validation_job,
44 encode_constraint_validation_job, encode_persisted_schema_snapshot,
45 enum_catalog::{
46 AcceptedSchemaAuthority, AcceptedSchemaPublicationError, AcceptedSchemaRevision,
47 AcceptedSchemaRevisionBundle, AcceptedSchemaRootSelection,
48 AcceptedStoreCatalogScope, AcceptedValueCatalogHandle, CandidateSchemaRevision,
49 decode_verified_accepted_schema_revision_bundle,
50 prepare_accepted_schema_root_publication, select_current_accepted_schema_root,
51 },
52 },
53 },
54 error::InternalError,
55 types::EntityTag,
56};
57use ic_memory::{DefaultMemoryImpl, RuntimeMemory};
58use ic_storage::adapter::{StableBTreeMap, Storable, storable::Bound as StorableBound};
59use sha2::Digest;
60use std::borrow::Cow;
61#[cfg(test)]
62use std::cell::Cell;
63use std::cell::{OnceCell, Ref, RefCell};
64use std::collections::{BTreeMap as StdBTreeMap, BTreeSet};
65use std::ops::Bound as RangeBound;
66use std::rc::Rc;
67
68const SCHEMA_KEY_BYTES_USIZE: usize = 16;
69const SCHEMA_KEY_BYTES: u32 = 16;
70const SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE: u8 = 1;
71const SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT: u8 = 2;
72const SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB: u8 = 3;
73const SCHEMA_KEY_NAMESPACE_IDENTITY_STATE: u8 = 4;
74const SCHEMA_STORE_FINGERPRINT_METHOD_VERSION: u8 = 1;
77const SCHEMA_STORE_CATALOG_FINGERPRINT_DOMAIN: u8 = 1;
78const SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_DOMAIN: u8 = 2;
79const SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_DOMAIN: u8 = 3;
80const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_BOOL: u8 = 3;
81const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_LIST: u8 = 29;
82const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_SET: u8 = 30;
83const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_MAP: u8 = 31;
84const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_COMPOSITE: u8 = 32;
85
86#[cfg(test)]
87thread_local! {
88 static ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES: Cell<u64> = const { Cell::new(0) };
89 static CANONICAL_SCHEMA_BUNDLE_LOADS: Cell<u64> = const { Cell::new(0) };
90}
91
92#[cfg(test)]
93fn reset_accepted_schema_bundle_cache_miss_count_for_tests() {
94 ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES.with(|misses| misses.set(0));
95}
96
97#[cfg(test)]
98fn accepted_schema_bundle_cache_miss_count_for_tests() -> u64 {
99 ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES.with(Cell::get)
100}
101
102#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
110struct RawSchemaKey([u8; SCHEMA_KEY_BYTES_USIZE]);
111
112impl RawSchemaKey {
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 fn from_constraint_validation_job(entity: EntityTag, constraint_id: ConstraintId) -> Self {
132 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
133 out[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
134 out[4..12].copy_from_slice(&entity.value().to_be_bytes());
135 out[12..].copy_from_slice(&constraint_id.get().to_be_bytes());
136 Self(out)
137 }
138
139 fn from_identity_state(entity: EntityTag, field_id: FieldId) -> Self {
140 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
141 out[0] = SCHEMA_KEY_NAMESPACE_IDENTITY_STATE;
142 out[4..12].copy_from_slice(&entity.value().to_be_bytes());
143 out[12..].copy_from_slice(&field_id.get().to_be_bytes());
144 Self(out)
145 }
146
147 #[must_use]
149 fn entity_tag(self) -> EntityTag {
150 let mut bytes = [0u8; size_of::<u64>()];
151 bytes.copy_from_slice(&self.0[4..12]);
152
153 EntityTag::new(u64::from_be_bytes(bytes))
154 }
155
156 #[must_use]
158 fn version(self) -> u32 {
159 let mut bytes = [0u8; size_of::<u32>()];
160 bytes.copy_from_slice(&self.0[12..]);
161
162 u32::from_be_bytes(bytes)
163 }
164
165 const fn all_catalog_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
168 let mut start = [0u8; SCHEMA_KEY_BYTES_USIZE];
169 start[0] = SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE;
170 let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
171 end[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
172 (
173 RangeBound::Included(Self(start)),
174 RangeBound::Included(Self(end)),
175 )
176 }
177
178 const fn all_constraint_validation_job_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
179 let mut start = [0u8; SCHEMA_KEY_BYTES_USIZE];
180 start[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
181 let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
182 end[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
183 (
184 RangeBound::Included(Self(start)),
185 RangeBound::Included(Self(end)),
186 )
187 }
188
189 const fn all_identity_state_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
190 let mut start = [0u8; SCHEMA_KEY_BYTES_USIZE];
191 start[0] = SCHEMA_KEY_NAMESPACE_IDENTITY_STATE;
192 let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
193 end[0] = SCHEMA_KEY_NAMESPACE_IDENTITY_STATE;
194 (
195 RangeBound::Included(Self(start)),
196 RangeBound::Included(Self(end)),
197 )
198 }
199
200 const fn is_accepted_root(self) -> bool {
201 self.0[0] == SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT
202 }
203
204 const fn is_constraint_validation_job(self) -> bool {
205 self.0[0] == SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB
206 }
207
208 const fn is_identity_state(self) -> bool {
209 self.0[0] == SCHEMA_KEY_NAMESPACE_IDENTITY_STATE
210 }
211
212 fn constraint_id(self) -> Option<ConstraintId> {
213 self.is_constraint_validation_job()
214 .then(|| ConstraintId::new(self.version()))
215 .flatten()
216 }
217}
218
219impl RawSchemaKey {
220 const NAMESPACE_CARDINALITY_CONTROL: u8 = 5;
221 const NAMESPACE_CARDINALITY_COUNT_A: u8 = 6;
222 const NAMESPACE_CARDINALITY_COUNT_B: u8 = 7;
223 const CARDINALITY_HEADER_DISCRIMINATOR: u8 = 0;
224 const CARDINALITY_BUILD_CURSOR_DISCRIMINATOR: u8 = 1;
225
226 const fn from_cardinality_generation_header() -> Self {
227 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
228 out[0] = Self::NAMESPACE_CARDINALITY_CONTROL;
229 out[SCHEMA_KEY_BYTES_USIZE - 1] = Self::CARDINALITY_HEADER_DISCRIMINATOR;
230 Self(out)
231 }
232
233 const fn from_cardinality_build_cursor() -> Self {
234 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
235 out[0] = Self::NAMESPACE_CARDINALITY_CONTROL;
236 out[SCHEMA_KEY_BYTES_USIZE - 1] = Self::CARDINALITY_BUILD_CURSOR_DISCRIMINATOR;
237 Self(out)
238 }
239
240 fn from_cardinality_count(slot: CardinalityCountSlot, digest: CardinalityCountDigest) -> Self {
241 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
242 out[0] = match slot {
243 CardinalityCountSlot::A => Self::NAMESPACE_CARDINALITY_COUNT_A,
244 CardinalityCountSlot::B => Self::NAMESPACE_CARDINALITY_COUNT_B,
245 };
246 out[1..].copy_from_slice(&digest.as_bytes()[..SCHEMA_KEY_BYTES_USIZE - 1]);
247 Self(out)
248 }
249
250 const fn cardinality_count_range_bounds(
251 slot: CardinalityCountSlot,
252 ) -> (RangeBound<Self>, RangeBound<Self>) {
253 let namespace = match slot {
254 CardinalityCountSlot::A => Self::NAMESPACE_CARDINALITY_COUNT_A,
255 CardinalityCountSlot::B => Self::NAMESPACE_CARDINALITY_COUNT_B,
256 };
257 let mut start = [0_u8; SCHEMA_KEY_BYTES_USIZE];
258 start[0] = namespace;
259 let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
260 end[0] = namespace;
261 (
262 RangeBound::Included(Self(start)),
263 RangeBound::Included(Self(end)),
264 )
265 }
266}
267
268impl Storable for RawSchemaKey {
269 fn to_bytes(&self) -> Cow<'_, [u8]> {
270 Cow::Borrowed(&self.0)
271 }
272
273 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
274 debug_assert_eq!(
275 bytes.len(),
276 SCHEMA_KEY_BYTES_USIZE,
277 "RawSchemaKey::from_bytes received unexpected byte length",
278 );
279
280 if bytes.len() != SCHEMA_KEY_BYTES_USIZE {
281 return Self([0u8; SCHEMA_KEY_BYTES_USIZE]);
282 }
283
284 let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
285 out.copy_from_slice(bytes.as_ref());
286 Self(out)
287 }
288
289 fn into_bytes(self) -> Vec<u8> {
290 self.0.to_vec()
291 }
292
293 const BOUND: StorableBound = StorableBound::Bounded {
294 max_size: SCHEMA_KEY_BYTES,
295 is_fixed_size: true,
296 };
297}
298
299#[derive(Clone, Debug, Eq, PartialEq)]
302struct RawSchemaValue {
303 payload: Vec<u8>,
304}
305
306impl RawSchemaValue {
307 #[must_use]
309 const fn from_encoded_control_record(payload: Vec<u8>) -> Self {
310 Self { payload }
311 }
312
313 #[must_use]
314 const fn as_bytes(&self) -> &[u8] {
315 self.payload.as_slice()
316 }
317}
318
319#[derive(Clone, Debug, Eq, PartialEq)]
320pub(in crate::db) struct AcceptedCatalogIdentity {
321 entity_tag: EntityTag,
322 entity_path: Rc<str>,
323 store_path: &'static str,
324 accepted_schema_revision: AcceptedSchemaRevision,
325 accepted_schema_version: SchemaVersion,
326 fingerprint_method_version: u8,
327 accepted_schema_fingerprint: CommitSchemaFingerprint,
328}
329
330impl AcceptedCatalogIdentity {
331 #[must_use]
332 pub(in crate::db) fn new(
333 entity_tag: EntityTag,
334 entity_path: impl Into<Rc<str>>,
335 store_path: &'static str,
336 accepted_schema_revision: AcceptedSchemaRevision,
337 accepted_schema_version: SchemaVersion,
338 accepted_schema_fingerprint: CommitSchemaFingerprint,
339 ) -> Self {
340 Self {
341 entity_tag,
342 entity_path: entity_path.into(),
343 store_path,
344 accepted_schema_revision,
345 accepted_schema_version,
346 fingerprint_method_version: accepted_schema_cache_fingerprint_method_version(),
347 accepted_schema_fingerprint,
348 }
349 }
350
351 #[must_use]
352 pub(in crate::db) const fn entity_tag(&self) -> EntityTag {
353 self.entity_tag
354 }
355
356 #[must_use]
357 pub(in crate::db) fn entity_path(&self) -> &str {
358 self.entity_path.as_ref()
359 }
360
361 #[must_use]
362 pub(in crate::db) fn entity_path_handle(&self) -> Rc<str> {
363 self.entity_path.clone()
364 }
365
366 #[must_use]
367 pub(in crate::db) const fn store_path(&self) -> &'static str {
368 self.store_path
369 }
370
371 #[must_use]
372 pub(in crate::db) const fn accepted_schema_revision(&self) -> AcceptedSchemaRevision {
373 self.accepted_schema_revision
374 }
375
376 #[must_use]
377 pub(in crate::db) const fn accepted_schema_version(&self) -> SchemaVersion {
378 self.accepted_schema_version
379 }
380
381 #[must_use]
382 pub(in crate::db) const fn fingerprint_method_version(&self) -> u8 {
383 self.fingerprint_method_version
384 }
385
386 #[must_use]
387 pub(in crate::db) const fn accepted_schema_fingerprint(&self) -> CommitSchemaFingerprint {
388 self.accepted_schema_fingerprint
389 }
390}
391
392#[derive(Clone, Debug, Eq, PartialEq)]
393pub(in crate::db) struct AcceptedCatalogSnapshotSelection {
394 identity: AcceptedCatalogIdentity,
395 value_catalog: AcceptedValueCatalogHandle,
396 snapshot: Rc<AcceptedSchemaSnapshot>,
397}
398
399impl AcceptedCatalogSnapshotSelection {
400 fn from_verified_snapshot(
403 entity: EntityTag,
404 store_path: &'static str,
405 snapshot: &PersistedSchemaSnapshot,
406 value_catalog: AcceptedValueCatalogHandle,
407 ) -> Result<Self, InternalError> {
408 let snapshot = Rc::new(AcceptedSchemaSnapshot::try_new(snapshot.clone())?);
409 let fingerprint = accepted_schema_cache_fingerprint(&snapshot)?;
410 let identity = AcceptedCatalogIdentity::new(
411 entity,
412 snapshot.entity_path(),
413 store_path,
414 value_catalog.revision(),
415 snapshot.persisted_snapshot().version(),
416 fingerprint,
417 );
418
419 Ok(Self {
420 identity,
421 value_catalog,
422 snapshot,
423 })
424 }
425
426 #[must_use]
427 pub(in crate::db) fn identity(&self) -> AcceptedCatalogIdentity {
428 self.identity.clone()
429 }
430
431 #[must_use]
432 pub(in crate::db) const fn value_catalog_handle(&self) -> &AcceptedValueCatalogHandle {
433 &self.value_catalog
434 }
435
436 #[cfg(any(test, feature = "migration"))]
439 pub(in crate::db) fn from_candidate(
440 candidate: &CandidateSchemaRevision,
441 entity_tag: EntityTag,
442 entity_path: &str,
443 store_path: &'static str,
444 ) -> Result<Option<Self>, InternalError> {
445 if candidate.store_path() != store_path {
446 return Err(InternalError::store_corruption());
447 }
448 let Some(snapshot) = candidate.bundle().entity_snapshots().get(&entity_tag) else {
449 return Ok(None);
450 };
451 if snapshot.entity_path() != entity_path {
452 return Err(InternalError::store_corruption());
453 }
454
455 Self::from_verified_snapshot(
456 entity_tag,
457 store_path,
458 snapshot,
459 AcceptedValueCatalogHandle::new(
460 candidate.bundle().enum_catalog().clone(),
461 candidate.bundle().composite_catalog().clone(),
462 AcceptedStoreCatalogScope::new(),
463 candidate.revision(),
464 candidate.root().fingerprint(),
465 ),
466 )
467 .map(Some)
468 }
469
470 #[must_use]
472 pub(in crate::db) fn snapshot(&self) -> Rc<AcceptedSchemaSnapshot> {
473 self.snapshot.clone()
474 }
475}
476
477impl Storable for RawSchemaValue {
478 fn to_bytes(&self) -> Cow<'_, [u8]> {
479 Cow::Borrowed(self.as_bytes())
480 }
481
482 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
483 Self::from_encoded_control_record(bytes.into_owned())
484 }
485
486 fn into_bytes(self) -> Vec<u8> {
487 self.payload
488 }
489
490 const BOUND: StorableBound = StorableBound::Unbounded;
491}
492
493#[derive(Clone, Copy, Debug, Eq, PartialEq)]
500pub(in crate::db) struct SchemaStoreCatalogMetadata {
501 schema_version: SchemaVersion,
502 schema_fingerprint_method_version: u8,
503 schema_fingerprint: CommitSchemaFingerprint,
504 entity_count: u64,
505}
506
507impl SchemaStoreCatalogMetadata {
508 #[must_use]
510 const fn new(
511 schema_version: SchemaVersion,
512 schema_fingerprint_method_version: u8,
513 schema_fingerprint: CommitSchemaFingerprint,
514 entity_count: u64,
515 ) -> Self {
516 Self {
517 schema_version,
518 schema_fingerprint_method_version,
519 schema_fingerprint,
520 entity_count,
521 }
522 }
523
524 #[must_use]
526 pub(in crate::db) const fn schema_version(self) -> SchemaVersion {
527 self.schema_version
528 }
529
530 #[must_use]
532 pub(in crate::db) const fn schema_fingerprint_method_version(self) -> u8 {
533 self.schema_fingerprint_method_version
534 }
535
536 #[must_use]
539 pub(in crate::db) const fn schema_fingerprint(self) -> CommitSchemaFingerprint {
540 self.schema_fingerprint
541 }
542
543 #[must_use]
545 pub(in crate::db) const fn entity_count(self) -> u64 {
546 self.entity_count
547 }
548}
549
550#[derive(Clone, Copy, Debug, Eq, PartialEq)]
558pub(in crate::db) struct SchemaStoreAllocationMetadata {
559 data: SchemaStoreCatalogMetadata,
560 index: SchemaStoreCatalogMetadata,
561 schema: SchemaStoreCatalogMetadata,
562}
563
564impl SchemaStoreAllocationMetadata {
565 #[must_use]
568 const fn new(
569 data: SchemaStoreCatalogMetadata,
570 index: SchemaStoreCatalogMetadata,
571 schema: SchemaStoreCatalogMetadata,
572 ) -> Self {
573 Self {
574 data,
575 index,
576 schema,
577 }
578 }
579
580 #[must_use]
582 pub(in crate::db) const fn data(self) -> SchemaStoreCatalogMetadata {
583 self.data
584 }
585
586 #[must_use]
588 pub(in crate::db) const fn index(self) -> SchemaStoreCatalogMetadata {
589 self.index
590 }
591
592 #[must_use]
595 pub(in crate::db) const fn schema(self) -> SchemaStoreCatalogMetadata {
596 self.schema
597 }
598}
599
600pub(in crate::db) struct PendingRelationActivationDeleteBarrier {
608 accepted_schema_fingerprint: CommitSchemaFingerprint,
609 source_entity_tag: EntityTag,
610 constraint_id: ConstraintId,
611}
612
613impl PendingRelationActivationDeleteBarrier {
614 #[must_use]
615 pub(in crate::db) const fn accepted_schema_fingerprint(&self) -> CommitSchemaFingerprint {
616 self.accepted_schema_fingerprint
617 }
618
619 #[must_use]
620 pub(in crate::db) const fn source_entity_tag(&self) -> EntityTag {
621 self.source_entity_tag
622 }
623
624 #[must_use]
626 pub(in crate::db) const fn constraint_id(&self) -> ConstraintId {
627 self.constraint_id
628 }
629}
630
631pub struct SchemaStore {
640 backend: SchemaStoreBackend,
641 accepted_bundle_cache: RefCell<Option<AcceptedSchemaBundleCache>>,
642 cardinality_header_cache: RefCell<Option<(Vec<u8>, CardinalityGenerationHeader)>>,
643 accepted_catalog_scope: OnceCell<AcceptedStoreCatalogScope>,
644}
645
646struct AcceptedSchemaBundleCache {
647 selection: AcceptedSchemaRootSelection,
648 bundle: AcceptedSchemaRevisionBundle,
649 cardinality_domain: Rc<CardinalityAcceptedDomain>,
650 value_catalog: AcceptedValueCatalogHandle,
651 entity_selections: RefCell<StdBTreeMap<EntityTag, AcceptedCatalogSnapshotSelection>>,
652}
653
654enum SchemaStoreBackend {
655 Heap(StdBTreeMap<RawSchemaKey, RawSchemaValue>),
656 Journaled {
657 canonical: StableBTreeMap<RawSchemaKey, RawSchemaValue, DefaultMemoryImpl>,
658 live: StdBTreeMap<RawSchemaKey, RawSchemaValue>,
659 tombstones: BTreeSet<RawSchemaKey>,
660 positions: PositionedOverlayMetadata<RawSchemaKey>,
661 },
662}
663
664#[derive(Clone, Copy, Debug, Eq, PartialEq)]
666enum SchemaStoreVisit {
667 Continue,
668 #[cfg(test)]
669 Stop,
670}
671
672impl SchemaStoreVisit {
673 const fn should_stop(self) -> bool {
674 match self {
675 Self::Continue => false,
676 #[cfg(test)]
677 Self::Stop => true,
678 }
679 }
680}
681
682#[derive(Clone, Copy)]
683enum IdentityStateStorageView {
684 Effective,
685 Canonical,
686}
687
688pub(in crate::db) struct PreparedAcceptedSchemaFold {
691 candidate: CandidateSchemaRevision,
692 expected_revision: AcceptedSchemaRevision,
693 identity_updates: Vec<(RawSchemaKey, Vec<u8>)>,
694 identity_removals: Vec<RawSchemaKey>,
695 retained: BTreeSet<RawSchemaKey>,
696 root_slot: usize,
697}
698
699pub(in crate::db) struct PreparedSchemaPositionPublication {
701 keys: Vec<RawSchemaKey>,
702 position: JournalOverlayPosition,
703}
704
705pub(in crate::db) struct PreparedSchemaPositionRetirement {
707 entries: Vec<(RawSchemaKey, PositionedOverlayRetirement)>,
708}
709
710pub(in crate::db) struct PreparedCardinalityCountWrites {
712 slot: CardinalityCountSlot,
713 generation: CardinalityGenerationId,
714 entries: Vec<(RawSchemaKey, RawSchemaValue)>,
715 new_count_keys: u64,
716}
717
718impl PreparedCardinalityCountWrites {
719 #[must_use]
720 pub(in crate::db) const fn new_count_keys(&self) -> u64 {
721 self.new_count_keys
722 }
723}
724
725pub(in crate::db) struct PreparedCardinalityBuildPage {
727 count_entries: Vec<(RawSchemaKey, RawSchemaValue)>,
728 cursor: (RawSchemaKey, RawSchemaValue),
729}
730
731pub(in crate::db) struct PreparedCardinalityMaintenance {
733 count_entries: Vec<(RawSchemaKey, Option<RawSchemaValue>)>,
734 header: (RawSchemaKey, RawSchemaValue),
735}
736
737#[derive(Clone, Copy)]
738enum IdentityStateWriteTarget {
739 Durable,
740 Materialized,
741 Canonical,
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 cardinality_header_cache: RefCell::new(None),
752 accepted_catalog_scope: OnceCell::new(),
753 }
754 }
755
756 #[must_use]
761 pub fn init_journaled(memory: RuntimeMemory<DefaultMemoryImpl>) -> Self {
762 Self {
763 backend: SchemaStoreBackend::Journaled {
764 canonical: StableBTreeMap::init(memory),
765 live: StdBTreeMap::new(),
766 tombstones: BTreeSet::new(),
767 positions: PositionedOverlayMetadata::new(),
768 },
769 accepted_bundle_cache: RefCell::new(None),
770 cardinality_header_cache: RefCell::new(None),
771 accepted_catalog_scope: OnceCell::new(),
772 }
773 }
774
775 pub(in crate::db) fn cardinality_generation_header(
777 &self,
778 ) -> Result<Option<CardinalityGenerationHeader>, InternalError> {
779 let key = RawSchemaKey::from_cardinality_generation_header();
780 let raw = self.get_canonical_raw_value(&key)?;
781 if let Some(raw) = raw.as_ref() {
782 self.decode_cardinality_header_cached(raw).map(Some)
783 } else {
784 self.cardinality_header_cache
785 .try_borrow_mut()
786 .map_err(|_| InternalError::store_invariant())?
787 .take();
788 Ok(None)
789 }
790 }
791
792 pub(in crate::db) fn cardinality_build_cursor(
794 &self,
795 ) -> Result<Option<CardinalityBuildCursor>, InternalError> {
796 let key = RawSchemaKey::from_cardinality_build_cursor();
797 self.get_canonical_raw_value(&key)?
798 .map(|raw| CardinalityBuildCursor::decode(raw.as_bytes()))
799 .transpose()
800 }
801
802 pub(in crate::db) fn cardinality_generation_control(
804 &self,
805 ) -> Result<
806 (
807 Option<CardinalityGenerationHeader>,
808 Option<CardinalityBuildCursor>,
809 ),
810 InternalError,
811 > {
812 let SchemaStoreBackend::Journaled { canonical, .. } = &self.backend else {
813 return Err(InternalError::store_invariant());
814 };
815 let header_key = RawSchemaKey::from_cardinality_generation_header();
816 let cursor_key = RawSchemaKey::from_cardinality_build_cursor();
817 let mut header = None;
818 let mut cursor = None;
819 for entry in canonical.range(header_key..=cursor_key) {
820 if *entry.key() == header_key {
821 header = Some(self.decode_cardinality_header_cached(&entry.value())?);
822 } else if *entry.key() == cursor_key {
823 cursor = Some(CardinalityBuildCursor::decode(entry.value().as_bytes())?);
824 } else {
825 return Err(InternalError::store_corruption());
826 }
827 }
828 Ok((header, cursor))
829 }
830
831 pub(in crate::db) fn cardinality_generation_lifecycle_control(
836 &self,
837 ) -> Result<(Option<CardinalityGenerationHeader>, bool), InternalError> {
838 let SchemaStoreBackend::Journaled { canonical, .. } = &self.backend else {
839 return Err(InternalError::store_invariant());
840 };
841 let header_key = RawSchemaKey::from_cardinality_generation_header();
842 let cursor_key = RawSchemaKey::from_cardinality_build_cursor();
843 let mut header = None;
844 let mut cursor_present = false;
845 for entry in canonical.range(header_key..=cursor_key) {
846 if *entry.key() == header_key {
847 header = Some(self.decode_cardinality_header_cached(&entry.value())?);
848 } else if *entry.key() == cursor_key {
849 cursor_present = true;
850 } else {
851 return Err(InternalError::store_corruption());
852 }
853 }
854
855 Ok((header, cursor_present))
856 }
857
858 fn decode_cardinality_header_cached(
859 &self,
860 raw: &RawSchemaValue,
861 ) -> Result<CardinalityGenerationHeader, InternalError> {
862 if let Some((_, header)) = self
863 .cardinality_header_cache
864 .try_borrow()
865 .map_err(|_| InternalError::store_invariant())?
866 .as_ref()
867 .filter(|(bytes, _)| bytes.as_slice() == raw.as_bytes())
868 {
869 return Ok(*header);
870 }
871 let header = CardinalityGenerationHeader::decode(raw.as_bytes())?;
872 *self
873 .cardinality_header_cache
874 .try_borrow_mut()
875 .map_err(|_| InternalError::store_invariant())? =
876 Some((raw.as_bytes().to_vec(), header));
877 Ok(header)
878 }
879
880 pub(in crate::db) fn cardinality_storage_is_pristine(&self) -> Result<bool, InternalError> {
882 if self.cardinality_generation_header()?.is_some()
883 || self.cardinality_build_cursor()?.is_some()
884 {
885 return Ok(false);
886 }
887 Ok(
888 self.cardinality_count_slot_is_empty(CardinalityCountSlot::A)?
889 && self.cardinality_count_slot_is_empty(CardinalityCountSlot::B)?,
890 )
891 }
892
893 pub(in crate::db) fn write_cardinality_generation_header(
895 &mut self,
896 header: CardinalityGenerationHeader,
897 ) -> Result<(), InternalError> {
898 self.insert_canonical_raw_value(
899 RawSchemaKey::from_cardinality_generation_header(),
900 header.encode(),
901 )
902 }
903
904 pub(in crate::db) fn restart_cardinality_generation(
910 &mut self,
911 current: CardinalityGenerationHeader,
912 source: CardinalitySourceIdentity,
913 ) -> Result<CardinalityGenerationHeader, InternalError> {
914 if self.cardinality_generation_header()? != Some(current) {
915 return Err(InternalError::store_corruption());
916 }
917 if current.validate_source(source).is_ok() {
918 return Err(InternalError::store_invariant());
919 }
920 if let Some(cursor) = self.cardinality_build_cursor()? {
921 cursor.validate_header(current)?;
922 }
923 let next = CardinalityGenerationHeader::new(
924 current.generation().checked_next()?,
925 CardinalityGenerationState::Building,
926 current.slot().alternate(),
927 source,
928 );
929 let encoded = RawSchemaValue::from_encoded_control_record(next.encode());
930 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
931 return Err(InternalError::store_invariant());
932 };
933 canonical.insert(RawSchemaKey::from_cardinality_generation_header(), encoded);
934 canonical.remove(&RawSchemaKey::from_cardinality_build_cursor());
935 Ok(next)
936 }
937
938 pub(in crate::db) fn publish_empty_cardinality_generation(
940 &mut self,
941 candidate: &EmptyCardinalityReadyCandidate,
942 ) -> Result<CardinalityGenerationHeader, InternalError> {
943 if !self.cardinality_storage_is_pristine()? {
944 return Err(InternalError::store_corruption());
945 }
946 let ready = CardinalityGenerationHeader::new(
947 CardinalityGenerationId::INITIAL,
948 CardinalityGenerationState::Ready,
949 CardinalityCountSlot::A,
950 candidate.source(),
951 );
952 let encoded = RawSchemaValue::from_encoded_control_record(ready.encode());
953 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
954 return Err(InternalError::store_invariant());
955 };
956 canonical.insert(RawSchemaKey::from_cardinality_generation_header(), encoded);
957 Ok(ready)
958 }
959
960 pub(in crate::db) fn publish_ready_cardinality_generation(
962 &mut self,
963 candidate: &CardinalityReadyCandidate,
964 source: CardinalitySourceIdentity,
965 ) -> Result<CardinalityGenerationHeader, InternalError> {
966 let building = candidate.header();
967 candidate.cursor().validate_header(building)?;
968 if building.state() != CardinalityGenerationState::Building
969 || building.validate_source(source).is_err()
970 || self.cardinality_generation_header()? != Some(building)
971 || self.cardinality_build_cursor()?.as_ref() != Some(candidate.cursor())
972 {
973 return Err(InternalError::store_corruption());
974 }
975 let ready = CardinalityGenerationHeader::new(
976 building.generation(),
977 CardinalityGenerationState::Ready,
978 building.slot(),
979 building.source(),
980 );
981 let encoded = RawSchemaValue::from_encoded_control_record(ready.encode());
982 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
983 return Err(InternalError::store_invariant());
984 };
985 canonical.insert(RawSchemaKey::from_cardinality_generation_header(), encoded);
986 canonical.remove(&RawSchemaKey::from_cardinality_build_cursor());
987 Ok(ready)
988 }
989
990 pub(in crate::db) fn clear_cardinality_count_slot_page(
996 &mut self,
997 header: CardinalityGenerationHeader,
998 initial_cursor: &CardinalityBuildCursor,
999 limit: usize,
1000 ) -> Result<bool, InternalError> {
1001 if limit == 0 {
1002 return Err(InternalError::store_invariant());
1003 }
1004 initial_cursor.validate_header(header)?;
1005 let encoded_cursor = initial_cursor.encode()?;
1006 if self.cardinality_generation_header()? != Some(header)
1007 || self.cardinality_build_cursor()?.is_some()
1008 {
1009 return Err(InternalError::store_corruption());
1010 }
1011 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1012 return Err(InternalError::store_invariant());
1013 };
1014 let bounds = RawSchemaKey::cardinality_count_range_bounds(header.slot());
1015 let collect_limit = limit
1016 .checked_add(1)
1017 .ok_or_else(InternalError::store_unsupported)?;
1018 let mut keys = Vec::new();
1019 keys.try_reserve_exact(collect_limit)
1020 .map_err(|_| InternalError::store_unsupported())?;
1021 for entry in canonical.range(bounds).take(collect_limit) {
1022 keys.push(*entry.key());
1023 }
1024 let has_more = keys.len() > limit;
1025 for key in keys.into_iter().take(limit) {
1026 canonical.remove(&key);
1027 }
1028 if !has_more {
1029 canonical.insert(
1030 RawSchemaKey::from_cardinality_build_cursor(),
1031 RawSchemaValue::from_encoded_control_record(encoded_cursor),
1032 );
1033 }
1034 Ok(has_more)
1035 }
1036
1037 pub(in crate::db) fn prepare_cardinality_count_increments(
1039 &self,
1040 slot: CardinalityCountSlot,
1041 generation: CardinalityGenerationId,
1042 increments: &[(CardinalityCountDigest, u64)],
1043 ) -> Result<PreparedCardinalityCountWrites, InternalError> {
1044 if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1045 return Err(InternalError::store_invariant());
1046 }
1047 let mut entries = Vec::new();
1048 entries
1049 .try_reserve_exact(increments.len())
1050 .map_err(|_| InternalError::store_unsupported())?;
1051 let mut physical_keys = StdBTreeMap::new();
1052 let mut new_count_keys = 0_u64;
1053 for (digest, increment) in increments {
1054 if *increment == 0 {
1055 return Err(InternalError::store_invariant());
1056 }
1057 let key = RawSchemaKey::from_cardinality_count(slot, *digest);
1058 if let Some(previous_digest) = physical_keys.insert(key, *digest) {
1059 return Err(if previous_digest == *digest {
1060 InternalError::store_invariant()
1061 } else {
1062 InternalError::store_corruption()
1063 });
1064 }
1065 let current = self.get_canonical_raw_value(&key)?;
1066 let count = if let Some(raw) = current {
1067 let record = CardinalityCountRecord::decode(raw.as_bytes())?;
1068 record
1069 .validate_identity(generation, *digest)
1070 .map_err(|_| InternalError::store_corruption())?
1071 .checked_add(*increment)
1072 .ok_or_else(InternalError::store_unsupported)?
1073 } else {
1074 new_count_keys = new_count_keys
1075 .checked_add(1)
1076 .ok_or_else(InternalError::store_unsupported)?;
1077 *increment
1078 };
1079 let record = CardinalityCountRecord::new(generation, *digest, count)?;
1080 entries.push((
1081 key,
1082 RawSchemaValue::from_encoded_control_record(record.encode().to_vec()),
1083 ));
1084 }
1085 Ok(PreparedCardinalityCountWrites {
1086 slot,
1087 generation,
1088 entries,
1089 new_count_keys,
1090 })
1091 }
1092
1093 pub(in crate::db) fn prepare_cardinality_build_page(
1095 &self,
1096 header: CardinalityGenerationHeader,
1097 current_cursor: &CardinalityBuildCursor,
1098 counts: PreparedCardinalityCountWrites,
1099 next_cursor: &CardinalityBuildCursor,
1100 ) -> Result<PreparedCardinalityBuildPage, InternalError> {
1101 current_cursor.validate_header(header)?;
1102 next_cursor.validate_header(header)?;
1103 if counts.slot != header.slot() || counts.generation != header.generation() {
1104 return Err(InternalError::store_invariant());
1105 }
1106 if self.cardinality_generation_header()? != Some(header)
1107 || self.cardinality_build_cursor()?.as_ref() != Some(current_cursor)
1108 {
1109 return Err(InternalError::store_corruption());
1110 }
1111 let encoded_cursor = next_cursor.encode()?;
1112 Ok(PreparedCardinalityBuildPage {
1113 count_entries: counts.entries,
1114 cursor: (
1115 RawSchemaKey::from_cardinality_build_cursor(),
1116 RawSchemaValue::from_encoded_control_record(encoded_cursor),
1117 ),
1118 })
1119 }
1120
1121 pub(in crate::db) fn apply_prepared_cardinality_build_page(
1123 &mut self,
1124 prepared: PreparedCardinalityBuildPage,
1125 ) -> Result<(), InternalError> {
1126 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1127 return Err(InternalError::store_invariant());
1128 };
1129 for (key, value) in prepared.count_entries {
1130 canonical.insert(key, value);
1131 }
1132 canonical.insert(prepared.cursor.0, prepared.cursor.1);
1133 Ok(())
1134 }
1135
1136 pub(in crate::db) fn cardinality_count(
1138 &self,
1139 slot: CardinalityCountSlot,
1140 generation: CardinalityGenerationId,
1141 digest: CardinalityCountDigest,
1142 ) -> Result<Option<u64>, InternalError> {
1143 let key = RawSchemaKey::from_cardinality_count(slot, digest);
1144 self.get_canonical_raw_value(&key)?
1145 .map(|raw| {
1146 CardinalityCountRecord::decode(raw.as_bytes())?
1147 .validate_identity(generation, digest)
1148 .map_err(|_| InternalError::store_corruption())
1149 })
1150 .transpose()
1151 }
1152
1153 pub(in crate::db) fn prepare_cardinality_maintenance(
1155 &self,
1156 current: CardinalityGenerationHeader,
1157 current_source: CardinalitySourceIdentity,
1158 next_source: CardinalitySourceIdentity,
1159 changes: &[(CardinalityCountDigest, i64)],
1160 ) -> Result<PreparedCardinalityMaintenance, InternalError> {
1161 if current.state() != CardinalityGenerationState::Ready
1162 || current.validate_source(current_source).is_err()
1163 || self.cardinality_generation_header()? != Some(current)
1164 || self.cardinality_build_cursor()?.is_some()
1165 {
1166 return Err(InternalError::store_corruption());
1167 }
1168 let next = CardinalityGenerationHeader::new(
1169 current.generation(),
1170 CardinalityGenerationState::Ready,
1171 current.slot(),
1172 next_source,
1173 );
1174 let mut count_entries = Vec::new();
1175 count_entries
1176 .try_reserve_exact(changes.len())
1177 .map_err(|_| InternalError::store_unsupported())?;
1178 let mut physical_keys = StdBTreeMap::new();
1179 for (digest, delta) in changes {
1180 if *delta == 0 {
1181 return Err(InternalError::store_invariant());
1182 }
1183 let key = RawSchemaKey::from_cardinality_count(current.slot(), *digest);
1184 if let Some(previous_digest) = physical_keys.insert(key, *digest) {
1185 return Err(if previous_digest == *digest {
1186 InternalError::store_invariant()
1187 } else {
1188 InternalError::store_corruption()
1189 });
1190 }
1191 let base = self
1192 .cardinality_count(current.slot(), current.generation(), *digest)?
1193 .unwrap_or(0);
1194 let count = if *delta > 0 {
1195 base.checked_add(
1196 u64::try_from(*delta).map_err(|_| InternalError::store_invariant())?,
1197 )
1198 } else {
1199 base.checked_sub(delta.unsigned_abs())
1200 }
1201 .ok_or_else(InternalError::store_corruption)?;
1202 let value = if count == 0 {
1203 None
1204 } else {
1205 Some(RawSchemaValue::from_encoded_control_record(
1206 CardinalityCountRecord::new(current.generation(), *digest, count)?
1207 .encode()
1208 .to_vec(),
1209 ))
1210 };
1211 count_entries.push((key, value));
1212 }
1213 Ok(PreparedCardinalityMaintenance {
1214 count_entries,
1215 header: (
1216 RawSchemaKey::from_cardinality_generation_header(),
1217 RawSchemaValue::from_encoded_control_record(next.encode()),
1218 ),
1219 })
1220 }
1221
1222 pub(in crate::db) fn apply_prepared_cardinality_maintenance(
1224 &mut self,
1225 prepared: PreparedCardinalityMaintenance,
1226 ) -> Result<(), InternalError> {
1227 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1228 return Err(InternalError::store_invariant());
1229 };
1230 for (key, value) in prepared.count_entries {
1231 if let Some(value) = value {
1232 canonical.insert(key, value);
1233 } else {
1234 canonical.remove(&key);
1235 }
1236 }
1237 canonical.insert(prepared.header.0, prepared.header.1);
1238 Ok(())
1239 }
1240
1241 pub(in crate::db) fn cardinality_count_slot_is_empty(
1242 &self,
1243 slot: CardinalityCountSlot,
1244 ) -> Result<bool, InternalError> {
1245 let SchemaStoreBackend::Journaled { canonical, .. } = &self.backend else {
1246 return Err(InternalError::store_invariant());
1247 };
1248 Ok(canonical
1249 .range(RawSchemaKey::cardinality_count_range_bounds(slot))
1250 .next()
1251 .is_none())
1252 }
1253
1254 pub(in crate::db) fn preflight_fold_recovered_journal(&self) -> Result<(), InternalError> {
1256 match self.backend {
1257 SchemaStoreBackend::Journaled { .. } => Ok(()),
1258 SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
1259 }
1260 }
1261
1262 fn prepare_identity_state_transition(
1263 &self,
1264 incarnation: DatabaseIncarnationId,
1265 candidate: &CandidateSchemaRevision,
1266 view: IdentityStateStorageView,
1267 ) -> Result<IdentityStateTransition, InternalError> {
1268 let current = match view {
1269 IdentityStateStorageView::Effective => self
1270 .current_accepted_schema_bundle_ref()?
1271 .as_ref()
1272 .map(|bundle| (*bundle).clone()),
1273 IdentityStateStorageView::Canonical => {
1274 self.current_canonical_accepted_schema_bundle()?
1275 }
1276 };
1277 let inventory = self.identity_state_inventory(view)?;
1278 prepare_identity_state_transition(
1279 incarnation,
1280 current.as_ref(),
1281 candidate.bundle(),
1282 inventory,
1283 )
1284 }
1285
1286 fn validate_identity_state_closure(
1287 &self,
1288 bundle: &AcceptedSchemaRevisionBundle,
1289 ) -> Result<(), InternalError> {
1290 let inventory = self.identity_state_inventory(IdentityStateStorageView::Effective)?;
1291 validate_identity_state_closure(bundle, &inventory)
1292 }
1293
1294 pub(in crate::db) fn identity_statement_cursor(
1296 &self,
1297 database_incarnation_id: DatabaseIncarnationId,
1298 entity_tag: EntityTag,
1299 field_id: FieldId,
1300 accepted_kind: &AcceptedFieldKind,
1301 ) -> Result<IdentityStatementCursor, InternalError> {
1302 let key = RawSchemaKey::from_identity_state(entity_tag, field_id);
1303 let raw = self
1304 .get_raw_value(&key)
1305 .ok_or_else(InternalError::identity_state_corruption)?;
1306 let state = decode_identity_state(raw.as_bytes())?;
1307 let owner = state.owner();
1308 if owner.database_incarnation_id() != database_incarnation_id
1309 || owner.entity_tag() != entity_tag
1310 || owner.field_id() != field_id
1311 || state.accepted_kind() != accepted_kind
1312 || state.lifecycle() != IdentityStateLifecycle::Active
1313 {
1314 return Err(InternalError::identity_state_corruption());
1315 }
1316 IdentityStatementCursor::from_active_state(&state)
1317 }
1318
1319 pub(in crate::db) fn identity_high_water_for_integrity(
1321 &self,
1322 database_incarnation_id: DatabaseIncarnationId,
1323 entity_tag: EntityTag,
1324 field_id: FieldId,
1325 accepted_kind: &AcceptedFieldKind,
1326 ) -> Result<u128, InternalError> {
1327 let key = RawSchemaKey::from_identity_state(entity_tag, field_id);
1328 let raw = self
1329 .get_raw_value(&key)
1330 .ok_or_else(InternalError::identity_state_corruption)?;
1331 let state = decode_identity_state(raw.as_bytes())?;
1332 let owner = state.owner();
1333 if owner.database_incarnation_id() != database_incarnation_id
1334 || owner.entity_tag() != entity_tag
1335 || owner.field_id() != field_id
1336 || state.accepted_kind() != accepted_kind
1337 || state.lifecycle() != IdentityStateLifecycle::Active
1338 {
1339 return Err(InternalError::identity_state_corruption());
1340 }
1341 Ok(state.materialized_high_water())
1342 }
1343
1344 pub(in crate::db) fn preflight_identity_range_advance(
1346 &self,
1347 range: IdentityRangeAdvance,
1348 ) -> Result<(), InternalError> {
1349 let state =
1350 self.identity_state_for_owner(range.owner(), IdentityStateStorageView::Effective)?;
1351 state.preflight_range_advance(range)
1352 }
1353
1354 pub(in crate::db) fn apply_identity_range_advance(
1356 &mut self,
1357 range: IdentityRangeAdvance,
1358 advance_id: IdentityAdvanceId,
1359 ) -> Result<(), InternalError> {
1360 self.apply_identity_range_advance_to(
1361 range,
1362 advance_id,
1363 IdentityStateStorageView::Effective,
1364 IdentityStateWriteTarget::Materialized,
1365 )
1366 }
1367
1368 pub(in crate::db) fn fold_identity_range_advance(
1370 &mut self,
1371 range: IdentityRangeAdvance,
1372 advance_id: IdentityAdvanceId,
1373 ) -> Result<(), InternalError> {
1374 self.apply_identity_range_advance_to(
1375 range,
1376 advance_id,
1377 IdentityStateStorageView::Canonical,
1378 IdentityStateWriteTarget::Canonical,
1379 )
1380 }
1381
1382 pub(in crate::db) fn preflight_fold_identity_range_advance(
1384 &self,
1385 range: IdentityRangeAdvance,
1386 advance_id: IdentityAdvanceId,
1387 ) -> Result<(), InternalError> {
1388 if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1389 return Err(InternalError::store_invariant());
1390 }
1391 let state =
1392 self.identity_state_for_owner(range.owner(), IdentityStateStorageView::Canonical)?;
1393 let advanced = state.apply_range_advance(range, advance_id)?;
1394 let _encoded = encode_identity_state(&advanced)?;
1395 Ok(())
1396 }
1397
1398 pub(in crate::db) fn verify_identity_range_advance(
1400 &self,
1401 range: IdentityRangeAdvance,
1402 advance_id: IdentityAdvanceId,
1403 ) -> Result<(), InternalError> {
1404 let state =
1405 self.identity_state_for_owner(range.owner(), IdentityStateStorageView::Effective)?;
1406 if state.materialized_high_water() != range.new_high_water()
1407 || state.last_applied_advance() != Some(advance_id)
1408 {
1409 return Err(InternalError::recovery_effect_verification_failed());
1410 }
1411 Ok(())
1412 }
1413
1414 pub(in crate::db) fn identity_range_commit_state(
1416 &self,
1417 range: IdentityRangeAdvance,
1418 advance_id: IdentityAdvanceId,
1419 canonical: bool,
1420 ) -> Result<IdentityRangeCommitState, InternalError> {
1421 let view = if canonical {
1422 IdentityStateStorageView::Canonical
1423 } else {
1424 IdentityStateStorageView::Effective
1425 };
1426 self.identity_state_for_owner(range.owner(), view)?
1427 .range_commit_state(range, advance_id)
1428 }
1429
1430 pub(in crate::db) fn identity_state_inventory_for_integrity(
1433 &self,
1434 incarnation: DatabaseIncarnationId,
1435 ) -> Result<Vec<IdentityState>, InternalError> {
1436 let has_accepted_bundle = self.current_accepted_schema_bundle_ref()?.is_some();
1437 let inventory = self.identity_state_inventory(IdentityStateStorageView::Effective)?;
1438 if !has_accepted_bundle && !inventory.is_empty() {
1439 return Err(InternalError::identity_state_corruption());
1440 }
1441 if inventory
1442 .values()
1443 .any(|state| state.owner().database_incarnation_id() != incarnation)
1444 {
1445 return Err(InternalError::identity_state_corruption());
1446 }
1447 Ok(inventory.into_values().collect())
1448 }
1449
1450 fn identity_state_for_owner(
1451 &self,
1452 owner: crate::db::schema::identity_state::IdentityStateOwner,
1453 view: IdentityStateStorageView,
1454 ) -> Result<IdentityState, InternalError> {
1455 let key = RawSchemaKey::from_identity_state(owner.entity_tag(), owner.field_id());
1456 let raw = match view {
1457 IdentityStateStorageView::Effective => self.get_raw_value(&key),
1458 IdentityStateStorageView::Canonical => self.get_canonical_raw_value(&key)?,
1459 }
1460 .ok_or_else(InternalError::identity_state_corruption)?;
1461 let state = decode_identity_state(raw.as_bytes())?;
1462 if state.owner() != owner {
1463 return Err(InternalError::identity_state_corruption());
1464 }
1465 Ok(state)
1466 }
1467
1468 fn apply_identity_range_advance_to(
1469 &mut self,
1470 range: IdentityRangeAdvance,
1471 advance_id: IdentityAdvanceId,
1472 view: IdentityStateStorageView,
1473 target: IdentityStateWriteTarget,
1474 ) -> Result<(), InternalError> {
1475 let state = self.identity_state_for_owner(range.owner(), view)?;
1476 let advanced = state.apply_range_advance(range, advance_id)?;
1477 let key = RawSchemaKey::from_identity_state(
1478 advanced.owner().entity_tag(),
1479 advanced.owner().field_id(),
1480 );
1481 let bytes = encode_identity_state(&advanced)?;
1482 match target {
1483 IdentityStateWriteTarget::Materialized => {
1484 self.insert_raw_value(key, RawSchemaValue::from_encoded_control_record(bytes));
1485 }
1486 IdentityStateWriteTarget::Canonical => {
1487 self.insert_canonical_raw_value(key, bytes)?;
1488 }
1489 IdentityStateWriteTarget::Durable => {
1490 return Err(InternalError::store_invariant());
1491 }
1492 }
1493 Ok(())
1494 }
1495
1496 fn identity_state_inventory(
1497 &self,
1498 view: IdentityStateStorageView,
1499 ) -> Result<IdentityStateInventory, InternalError> {
1500 let bounds = RawSchemaKey::all_identity_state_range_bounds();
1501 let mut inventory = StdBTreeMap::new();
1502 let mut collect =
1503 |key: &RawSchemaKey, raw: &RawSchemaValue| -> Result<SchemaStoreVisit, InternalError> {
1504 if inventory.len() >= MAX_IDENTITY_STATE_RECORDS_PER_DATABASE {
1505 return Err(InternalError::identity_state_corruption());
1506 }
1507 let state = decode_identity_state(raw.as_bytes())?;
1508 let state_key = (key.entity_tag(), FieldId::new(key.version()));
1509 if !key.is_identity_state()
1510 || state.owner().entity_tag() != state_key.0
1511 || state.owner().field_id() != state_key.1
1512 || inventory.insert(state_key, state).is_some()
1513 {
1514 return Err(InternalError::identity_state_corruption());
1515 }
1516 Ok(SchemaStoreVisit::Continue)
1517 };
1518
1519 match (&self.backend, view) {
1520 (SchemaStoreBackend::Heap(map), IdentityStateStorageView::Effective) => {
1521 for (key, raw) in map.range((bounds.0, bounds.1)) {
1522 collect(key, raw)?;
1523 }
1524 }
1525 (
1526 SchemaStoreBackend::Journaled {
1527 canonical,
1528 live,
1529 tombstones,
1530 ..
1531 },
1532 IdentityStateStorageView::Effective,
1533 ) => Self::visit_journaled_raw_value_range(
1534 canonical,
1535 live,
1536 tombstones,
1537 bounds,
1538 Direction::Asc,
1539 &mut collect,
1540 )?,
1541 (
1542 SchemaStoreBackend::Journaled { canonical, .. },
1543 IdentityStateStorageView::Canonical,
1544 ) => {
1545 for entry in canonical.range((bounds.0, bounds.1)) {
1546 collect(entry.key(), &entry.value())?;
1547 }
1548 }
1549 (SchemaStoreBackend::Heap(_), IdentityStateStorageView::Canonical) => {
1550 return Err(InternalError::store_invariant());
1551 }
1552 }
1553
1554 Ok(inventory)
1555 }
1556
1557 fn apply_identity_state_transition(
1558 &mut self,
1559 transition: IdentityStateTransition,
1560 target: IdentityStateWriteTarget,
1561 ) -> Result<(), InternalError> {
1562 let (updates, removals) = transition.into_effects();
1563 for owner in removals {
1564 self.remove_identity_state_key(
1565 RawSchemaKey::from_identity_state(owner.entity_tag(), owner.field_id()),
1566 target,
1567 )?;
1568 }
1569 for state in updates {
1570 let key = RawSchemaKey::from_identity_state(
1571 state.owner().entity_tag(),
1572 state.owner().field_id(),
1573 );
1574 let bytes = encode_identity_state(&state)?;
1575 match target {
1576 IdentityStateWriteTarget::Durable => {
1577 self.insert_durable_raw_value(key, bytes);
1578 }
1579 IdentityStateWriteTarget::Materialized => {
1580 self.insert_raw_value(key, RawSchemaValue::from_encoded_control_record(bytes));
1581 }
1582 IdentityStateWriteTarget::Canonical => {
1583 self.insert_canonical_raw_value(key, bytes)?;
1584 }
1585 }
1586 }
1587 Ok(())
1588 }
1589
1590 fn remove_identity_state_key(
1594 &mut self,
1595 key: RawSchemaKey,
1596 target: IdentityStateWriteTarget,
1597 ) -> Result<(), InternalError> {
1598 match (&mut self.backend, target) {
1599 (
1600 SchemaStoreBackend::Heap(map),
1601 IdentityStateWriteTarget::Durable | IdentityStateWriteTarget::Materialized,
1602 ) => {
1603 map.remove(&key);
1604 }
1605 (
1606 SchemaStoreBackend::Journaled {
1607 live, tombstones, ..
1608 },
1609 IdentityStateWriteTarget::Materialized,
1610 ) => {
1611 live.remove(&key);
1612 tombstones.insert(key);
1613 }
1614 (
1615 SchemaStoreBackend::Journaled {
1616 canonical,
1617 live,
1618 tombstones,
1619 ..
1620 },
1621 IdentityStateWriteTarget::Durable,
1622 ) => {
1623 live.remove(&key);
1624 tombstones.remove(&key);
1625 canonical.remove(&key);
1626 }
1627 (
1628 SchemaStoreBackend::Journaled { canonical, .. },
1629 IdentityStateWriteTarget::Canonical,
1630 ) => {
1631 canonical.remove(&key);
1632 }
1633 (SchemaStoreBackend::Heap(_), IdentityStateWriteTarget::Canonical) => {
1634 return Err(InternalError::store_invariant());
1635 }
1636 }
1637 Ok(())
1638 }
1639
1640 pub(in crate::db) fn current_canonical_accepted_schema_bundle(
1641 &self,
1642 ) -> Result<Option<AcceptedSchemaRevisionBundle>, InternalError> {
1643 self.current_canonical_accepted_schema_authority()
1644 .map(|authority| authority.map(|(_, bundle)| bundle))
1645 }
1646
1647 pub(in crate::db) fn current_canonical_accepted_schema_authority(
1649 &self,
1650 ) -> Result<Option<(AcceptedSchemaRootSelection, AcceptedSchemaRevisionBundle)>, InternalError>
1651 {
1652 #[cfg(test)]
1653 CANONICAL_SCHEMA_BUNDLE_LOADS.with(|loads| loads.set(loads.get() + 1));
1654 let Some(selection) = self.current_canonical_accepted_schema_root()? else {
1655 return Ok(None);
1656 };
1657 let bundle_key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
1658 let raw = self
1659 .get_canonical_raw_value(&bundle_key)?
1660 .ok_or_else(InternalError::store_corruption)?;
1661 let bundle =
1662 decode_verified_accepted_schema_revision_bundle(selection.root(), raw.as_bytes())?;
1663 Ok(Some((selection, bundle)))
1664 }
1665
1666 #[cfg(test)]
1669 pub(in crate::db) fn take_canonical_bundle_load_count_for_tests() -> u64 {
1670 CANONICAL_SCHEMA_BUNDLE_LOADS.with(|loads| loads.replace(0))
1671 }
1672
1673 pub(in crate::db) fn current_canonical_accepted_schema_root(
1675 &self,
1676 ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
1677 let first = self.canonical_root_slot_bytes(0)?;
1678 let second = self.canonical_root_slot_bytes(1)?;
1679 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])
1680 }
1681
1682 pub(in crate::db) fn current_effective_and_canonical_accepted_schema_roots(
1684 &self,
1685 ) -> Result<
1686 (
1687 Option<AcceptedSchemaRootSelection>,
1688 Option<AcceptedSchemaRootSelection>,
1689 ),
1690 InternalError,
1691 > {
1692 let SchemaStoreBackend::Journaled {
1693 canonical,
1694 live,
1695 tombstones,
1696 ..
1697 } = &self.backend
1698 else {
1699 return Err(InternalError::store_invariant());
1700 };
1701 let first_key = RawSchemaKey::from_accepted_root_slot(0)?;
1702 let second_key = RawSchemaKey::from_accepted_root_slot(1)?;
1703 let mut canonical_first = None;
1704 let mut canonical_second = None;
1705 for entry in canonical.range(first_key..=second_key) {
1706 if *entry.key() == first_key {
1707 canonical_first = Some(entry.value().clone());
1708 } else if *entry.key() == second_key {
1709 canonical_second = Some(entry.value().clone());
1710 } else {
1711 return Err(InternalError::store_corruption());
1712 }
1713 }
1714 let effective_first = if tombstones.contains(&first_key) {
1715 None
1716 } else {
1717 live.get(&first_key)
1718 .cloned()
1719 .or_else(|| canonical_first.clone())
1720 };
1721 let effective_second = if tombstones.contains(&second_key) {
1722 None
1723 } else {
1724 live.get(&second_key)
1725 .cloned()
1726 .or_else(|| canonical_second.clone())
1727 };
1728 let effective_first = effective_first.map(RawSchemaValue::into_bytes);
1729 let effective_second = effective_second.map(RawSchemaValue::into_bytes);
1730 let canonical_first = canonical_first.map(RawSchemaValue::into_bytes);
1731 let canonical_second = canonical_second.map(RawSchemaValue::into_bytes);
1732 Ok((
1733 select_current_accepted_schema_root([
1734 effective_first.as_deref(),
1735 effective_second.as_deref(),
1736 ])?,
1737 select_current_accepted_schema_root([
1738 canonical_first.as_deref(),
1739 canonical_second.as_deref(),
1740 ])?,
1741 ))
1742 }
1743
1744 pub(in crate::db) fn constraint_validation_job(
1746 &self,
1747 entity: EntityTag,
1748 constraint_id: ConstraintId,
1749 ) -> Result<Option<ConstraintValidationJob>, InternalError> {
1750 let key = RawSchemaKey::from_constraint_validation_job(entity, constraint_id);
1751 self.get_raw_value(&key)
1752 .map(|raw| decode_constraint_validation_job(raw.as_bytes()))
1753 .transpose()
1754 }
1755
1756 pub(in crate::db) fn apply_constraint_validation_job(
1758 &mut self,
1759 job: &ConstraintValidationJob,
1760 ) -> Result<(), InternalError> {
1761 let key =
1762 RawSchemaKey::from_constraint_validation_job(job.entity_tag(), job.constraint_id());
1763 let bytes = encode_constraint_validation_job(job)?;
1764 let _ = self.insert_raw_value(key, RawSchemaValue::from_encoded_control_record(bytes));
1765 Ok(())
1766 }
1767
1768 #[expect(
1770 clippy::unnecessary_wraps,
1771 reason = "marker apply operations share one fallible callback contract"
1772 )]
1773 pub(in crate::db) fn apply_constraint_validation_job_removal(
1774 &mut self,
1775 entity: EntityTag,
1776 constraint_id: ConstraintId,
1777 ) -> Result<(), InternalError> {
1778 let key = RawSchemaKey::from_constraint_validation_job(entity, constraint_id);
1779 match &mut self.backend {
1780 SchemaStoreBackend::Heap(map) => {
1781 map.remove(&key);
1782 }
1783 SchemaStoreBackend::Journaled {
1784 live, tombstones, ..
1785 } => {
1786 live.remove(&key);
1787 tombstones.insert(key);
1788 }
1789 }
1790 Ok(())
1791 }
1792
1793 pub(in crate::db) fn fold_constraint_validation_job(
1795 &mut self,
1796 job: &ConstraintValidationJob,
1797 ) -> Result<(), InternalError> {
1798 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1799 return Err(InternalError::store_invariant());
1800 };
1801 let key =
1802 RawSchemaKey::from_constraint_validation_job(job.entity_tag(), job.constraint_id());
1803 let bytes = encode_constraint_validation_job(job)?;
1804 canonical.insert(key, RawSchemaValue::from_encoded_control_record(bytes));
1805 Ok(())
1806 }
1807
1808 pub(in crate::db) fn preflight_fold_constraint_validation_job(
1810 &self,
1811 job: &ConstraintValidationJob,
1812 ) -> Result<(), InternalError> {
1813 if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1814 return Err(InternalError::store_invariant());
1815 }
1816 let _encoded = encode_constraint_validation_job(job)?;
1817 Ok(())
1818 }
1819
1820 pub(in crate::db) fn fold_constraint_validation_job_removal(
1822 &mut self,
1823 entity: EntityTag,
1824 constraint_id: ConstraintId,
1825 ) -> Result<(), InternalError> {
1826 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1827 return Err(InternalError::store_invariant());
1828 };
1829 canonical.remove(&RawSchemaKey::from_constraint_validation_job(
1830 entity,
1831 constraint_id,
1832 ));
1833 Ok(())
1834 }
1835
1836 pub(in crate::db) fn preflight_fold_constraint_validation_job_removal(
1838 &self,
1839 ) -> Result<(), InternalError> {
1840 match self.backend {
1841 SchemaStoreBackend::Journaled { .. } => Ok(()),
1842 SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
1843 }
1844 }
1845
1846 pub(in crate::db) fn reset_journaled_live_projection(&mut self) -> Result<(), InternalError> {
1849 let SchemaStoreBackend::Journaled {
1850 live,
1851 tombstones,
1852 positions,
1853 ..
1854 } = &mut self.backend
1855 else {
1856 return Err(InternalError::store_invariant());
1857 };
1858
1859 live.clear();
1860 tombstones.clear();
1861 positions.clear();
1862 self.accepted_bundle_cache.get_mut().take();
1863
1864 Ok(())
1865 }
1866
1867 pub(in crate::db) fn prepare_positioned_journal_batch_publication(
1869 &self,
1870 incarnation: DatabaseIncarnationId,
1871 batch: &JournalBatch,
1872 position: JournalOverlayPosition,
1873 ) -> Result<PreparedSchemaPositionPublication, InternalError> {
1874 let SchemaStoreBackend::Journaled { positions, .. } = &self.backend else {
1875 return Err(InternalError::store_invariant());
1876 };
1877 let keys = self.positioned_journal_batch_keys(
1878 incarnation,
1879 batch,
1880 IdentityStateStorageView::Effective,
1881 )?;
1882 for key in &keys {
1883 positions.preflight_publish(key, position)?;
1884 }
1885 Ok(PreparedSchemaPositionPublication {
1886 keys: keys.into_iter().collect(),
1887 position,
1888 })
1889 }
1890
1891 pub(in crate::db) fn prepare_positioned_journal_batch_retirement(
1893 &self,
1894 incarnation: DatabaseIncarnationId,
1895 batch: &JournalBatch,
1896 position: JournalOverlayPosition,
1897 ) -> Result<PreparedSchemaPositionRetirement, InternalError> {
1898 let keys = self.positioned_journal_batch_keys(
1899 incarnation,
1900 batch,
1901 IdentityStateStorageView::Canonical,
1902 )?;
1903 self.prepare_positioned_key_retirements(keys, position)
1904 }
1905
1906 fn prepare_positioned_key_retirements(
1907 &self,
1908 keys: impl IntoIterator<Item = RawSchemaKey>,
1909 position: JournalOverlayPosition,
1910 ) -> Result<PreparedSchemaPositionRetirement, InternalError> {
1911 let SchemaStoreBackend::Journaled {
1912 live,
1913 tombstones,
1914 positions,
1915 ..
1916 } = &self.backend
1917 else {
1918 return Err(InternalError::store_invariant());
1919 };
1920 let mut entries = Vec::new();
1921 for key in keys {
1922 if !positions.is_positioned(&key) {
1923 if live.contains_key(&key) || tombstones.contains(&key) {
1927 return Err(InternalError::store_invariant());
1928 }
1929 continue;
1930 }
1931 let retirement = positions.preflight_retirement(&key, position)?;
1932 entries.push((key, retirement));
1933 }
1934 Ok(PreparedSchemaPositionRetirement { entries })
1935 }
1936
1937 pub(in crate::db) fn publish_prepared_journal_batch_positions(
1939 &mut self,
1940 prepared: PreparedSchemaPositionPublication,
1941 ) {
1942 let SchemaStoreBackend::Journaled { positions, .. } = &mut self.backend else {
1943 debug_assert!(
1944 false,
1945 "preflighted schema positions require a journaled store"
1946 );
1947 return;
1948 };
1949 for key in prepared.keys {
1950 positions.publish_preflighted(key, prepared.position);
1951 }
1952 }
1953
1954 pub(in crate::db) fn apply_prepared_journal_batch_retirement(
1956 &mut self,
1957 prepared: PreparedSchemaPositionRetirement,
1958 ) {
1959 for (key, retirement) in prepared.entries {
1960 if retirement != PositionedOverlayRetirement::Exact {
1961 continue;
1962 }
1963 self.invalidate_accepted_bundle_cache_for_key(key);
1964 let SchemaStoreBackend::Journaled {
1965 live,
1966 tombstones,
1967 positions,
1968 ..
1969 } = &mut self.backend
1970 else {
1971 debug_assert!(
1972 false,
1973 "preflighted schema retirement requires a journaled store"
1974 );
1975 return;
1976 };
1977 live.remove(&key);
1978 tombstones.remove(&key);
1979 positions.retire_preflighted(&key, retirement);
1980 }
1981 }
1982
1983 #[cfg(test)]
1984 fn publish_positioned_journal_entry(
1985 &mut self,
1986 key: RawSchemaKey,
1987 snapshot: Option<RawSchemaValue>,
1988 position: JournalOverlayPosition,
1989 ) -> Result<Option<RawSchemaValue>, InternalError> {
1990 let SchemaStoreBackend::Journaled { positions, .. } = &self.backend else {
1991 return Err(InternalError::store_invariant());
1992 };
1993 positions.preflight_publish(&key, position)?;
1994 self.invalidate_accepted_bundle_cache_for_key(key);
1995 let SchemaStoreBackend::Journaled {
1996 canonical,
1997 live,
1998 tombstones,
1999 positions,
2000 } = &mut self.backend
2001 else {
2002 return Err(InternalError::store_invariant());
2003 };
2004 let previous = if tombstones.contains(&key) {
2005 None
2006 } else {
2007 live.get(&key).cloned().or_else(|| canonical.get(&key))
2008 };
2009 if let Some(snapshot) = snapshot {
2010 tombstones.remove(&key);
2011 live.insert(key, snapshot);
2012 } else {
2013 live.remove(&key);
2014 tombstones.insert(key);
2015 }
2016 positions.publish_preflighted(key, position);
2017 Ok(previous)
2018 }
2019
2020 pub(in crate::db) fn current_accepted_schema_root(
2022 &self,
2023 ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
2024 let first = self.accepted_root_slot_bytes(0)?;
2025 let second = self.accepted_root_slot_bytes(1)?;
2026 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])
2027 }
2028
2029 pub(in crate::db) fn current_accepted_schema_bundle(
2031 &self,
2032 ) -> Result<Option<AcceptedSchemaRevisionBundle>, InternalError> {
2033 self.borrow_current_accepted_schema_bundle()
2034 .map(|bundle| bundle.map(|bundle| bundle.clone()))
2035 }
2036
2037 pub(in crate::db) fn borrow_current_accepted_schema_bundle(
2040 &self,
2041 ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
2042 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2043 return Ok(None);
2044 };
2045 self.validate_constraint_validation_job_closure(&bundle)?;
2046
2047 Ok(Some(bundle))
2048 }
2049
2050 pub(in crate::db) fn borrow_accepted_schema_bundle_for_authority(
2055 &self,
2056 expected: &AcceptedSchemaAuthority,
2057 ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
2058 let Some(selection) = self.current_accepted_selection_for_authority(expected)? else {
2059 return Ok(None);
2060 };
2061 let Some((_, bundle)) =
2062 self.accepted_schema_authority_ref_for_selection(Some(selection))?
2063 else {
2064 return Ok(None);
2065 };
2066 self.validate_constraint_validation_job_closure(&bundle)?;
2067
2068 Ok(Some(bundle))
2069 }
2070
2071 pub(in crate::db) fn current_accepted_runtime_entities(
2073 &self,
2074 registered_store_path: &'static str,
2075 ) -> Result<Vec<AcceptedRuntimeEntity>, InternalError> {
2076 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2077 return Ok(Vec::new());
2078 };
2079 if bundle.store_path() != registered_store_path {
2080 return Err(InternalError::store_corruption());
2081 }
2082
2083 bundle
2084 .entity_snapshots()
2085 .iter()
2086 .map(|(entity_tag, snapshot)| {
2087 AcceptedRuntimeEntity::from_accepted_snapshot(
2088 &bundle,
2089 *entity_tag,
2090 snapshot,
2091 registered_store_path,
2092 )
2093 })
2094 .collect()
2095 }
2096
2097 pub(in crate::db) fn current_accepted_runtime_entity_for_tag(
2099 &self,
2100 registered_store_path: &'static str,
2101 entity_tag: EntityTag,
2102 ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2103 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2104 return Ok(None);
2105 };
2106 if bundle.store_path() != registered_store_path {
2107 return Err(InternalError::store_corruption());
2108 }
2109 let Some(snapshot) = bundle.entity_snapshots().get(&entity_tag) else {
2110 return Ok(None);
2111 };
2112
2113 AcceptedRuntimeEntity::from_accepted_snapshot(
2114 &bundle,
2115 entity_tag,
2116 snapshot,
2117 registered_store_path,
2118 )
2119 .map(Some)
2120 }
2121
2122 pub(in crate::db) fn current_canonical_accepted_runtime_entity_for_tag(
2124 &self,
2125 registered_store_path: &'static str,
2126 entity_tag: EntityTag,
2127 ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2128 let Some(bundle) = self.current_canonical_accepted_schema_bundle()? else {
2129 return Ok(None);
2130 };
2131 if bundle.store_path() != registered_store_path {
2132 return Err(InternalError::store_corruption());
2133 }
2134 let Some(snapshot) = bundle.entity_snapshots().get(&entity_tag) else {
2135 return Ok(None);
2136 };
2137
2138 AcceptedRuntimeEntity::from_accepted_snapshot(
2139 &bundle,
2140 entity_tag,
2141 snapshot,
2142 registered_store_path,
2143 )
2144 .map(Some)
2145 }
2146
2147 pub(in crate::db) fn current_accepted_runtime_entity_for_path(
2149 &self,
2150 registered_store_path: &'static str,
2151 entity_path: &str,
2152 ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2153 self.current_accepted_runtime_entity_matching(registered_store_path, |snapshot_path, _| {
2154 snapshot_path == entity_path
2155 })
2156 }
2157
2158 pub(in crate::db) fn current_canonical_accepted_runtime_entity_for_path(
2160 &self,
2161 registered_store_path: &'static str,
2162 entity_path: &str,
2163 ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2164 let Some(bundle) = self.current_canonical_accepted_schema_bundle()? else {
2165 return Ok(None);
2166 };
2167 if bundle.store_path() != registered_store_path {
2168 return Err(InternalError::store_corruption());
2169 }
2170
2171 let mut matched = None;
2172 for (entity_tag, snapshot) in bundle.entity_snapshots() {
2173 if snapshot.entity_path() != entity_path {
2174 continue;
2175 }
2176 let entity = AcceptedRuntimeEntity::from_accepted_snapshot(
2177 &bundle,
2178 *entity_tag,
2179 snapshot,
2180 registered_store_path,
2181 )?;
2182 if matched.replace(entity).is_some() {
2183 return Err(InternalError::store_corruption());
2184 }
2185 }
2186
2187 Ok(matched)
2188 }
2189
2190 #[cfg(test)]
2192 pub(in crate::db) fn current_accepted_runtime_entity_for_name(
2193 &self,
2194 registered_store_path: &'static str,
2195 entity_name: &str,
2196 ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2197 self.current_accepted_runtime_entity_matching(registered_store_path, |_, snapshot_name| {
2198 snapshot_name == entity_name
2199 })
2200 }
2201
2202 fn current_accepted_runtime_entity_matching(
2203 &self,
2204 registered_store_path: &'static str,
2205 mut predicate: impl FnMut(&str, &str) -> bool,
2206 ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2207 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2208 return Ok(None);
2209 };
2210 if bundle.store_path() != registered_store_path {
2211 return Err(InternalError::store_corruption());
2212 }
2213
2214 let mut matched = None;
2215 for (entity_tag, snapshot) in bundle.entity_snapshots() {
2216 if !predicate(snapshot.entity_path(), snapshot.entity_name()) {
2217 continue;
2218 }
2219 let entity = AcceptedRuntimeEntity::from_accepted_snapshot(
2220 &bundle,
2221 *entity_tag,
2222 snapshot,
2223 registered_store_path,
2224 )?;
2225 if matched.replace(entity).is_some() {
2226 return Err(InternalError::store_corruption());
2227 }
2228 }
2229
2230 Ok(matched)
2231 }
2232
2233 pub(in crate::db) fn current_accepted_schema_revision(
2235 &self,
2236 ) -> Result<Option<AcceptedSchemaRevision>, InternalError> {
2237 Ok(self
2238 .current_accepted_schema_root()?
2239 .map(|selection| selection.root().revision()))
2240 }
2241
2242 pub(in crate::db) fn pending_relation_activation_for_target(
2248 &self,
2249 target_path: &str,
2250 ) -> Result<Option<PendingRelationActivationDeleteBarrier>, InternalError> {
2251 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2252 return Ok(None);
2253 };
2254 for (entity_tag, snapshot) in bundle.entity_snapshots() {
2255 let Some(candidate) = snapshot
2256 .candidate_relations()
2257 .iter()
2258 .find(|candidate| candidate.target_path() == target_path)
2259 else {
2260 continue;
2261 };
2262 let activation = snapshot
2263 .constraint_activations()
2264 .iter()
2265 .find(|activation| {
2266 matches!(
2267 activation.kind(),
2268 ConstraintActivationKind::Relation { relation_id }
2269 if *relation_id == candidate.id()
2270 )
2271 })
2272 .ok_or_else(InternalError::store_corruption)?;
2273 return Ok(Some(PendingRelationActivationDeleteBarrier {
2274 accepted_schema_fingerprint:
2275 accepted_schema_cache_fingerprint_for_persisted_snapshot(snapshot)?,
2276 source_entity_tag: *entity_tag,
2277 constraint_id: activation.id(),
2278 }));
2279 }
2280
2281 Ok(None)
2282 }
2283
2284 pub(in crate::db) fn entity_has_relation_to_target(
2286 &self,
2287 source_entity: EntityTag,
2288 target_path: &str,
2289 ) -> Result<bool, InternalError> {
2290 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2291 return Ok(false);
2292 };
2293 let Some(snapshot) = bundle.entity_snapshots().get(&source_entity) else {
2294 return Ok(false);
2295 };
2296
2297 Ok(snapshot
2298 .relations()
2299 .iter()
2300 .any(|relation| relation.target_path() == target_path))
2301 }
2302
2303 pub(in crate::db) fn validate_live_activation_transition(
2305 &self,
2306 candidate: &AcceptedSchemaRevisionBundle,
2307 ) -> Result<(), InternalError> {
2308 let Some(current) = self.current_accepted_schema_bundle()? else {
2309 return Ok(());
2310 };
2311 Self::validate_activation_transition_from(¤t, candidate)
2312 }
2313
2314 pub(in crate::db) fn validate_canonical_activation_transition(
2316 &self,
2317 candidate: &AcceptedSchemaRevisionBundle,
2318 ) -> Result<(), InternalError> {
2319 let Some(current) = self.current_canonical_accepted_schema_bundle()? else {
2320 return Ok(());
2321 };
2322 Self::validate_activation_transition_from(¤t, candidate)
2323 }
2324
2325 fn validate_activation_transition_from(
2326 current: &AcceptedSchemaRevisionBundle,
2327 candidate: &AcceptedSchemaRevisionBundle,
2328 ) -> Result<(), InternalError> {
2329 for (entity_tag, before) in current.entity_snapshots() {
2330 if before.constraint_activations().is_empty() {
2331 continue;
2332 }
2333 let after = candidate
2334 .entity_snapshots()
2335 .get(entity_tag)
2336 .ok_or_else(InternalError::store_invariant)?;
2337 if before == after {
2338 continue;
2339 }
2340 let expected_shape = before
2341 .clone()
2342 .with_constraint_catalog(after.constraint_catalog().clone());
2343 let catalog_only_transition = expected_shape == *after
2344 && before
2345 .constraint_catalog()
2346 .permits_live_activation_transition_to(after.constraint_catalog());
2347 let sql_row_local_abort_with_version =
2348 before.constraint_activations().iter().any(|activation| {
2349 activation.origin() == ConstraintOrigin::SqlDdl
2350 && matches!(
2351 activation.kind(),
2352 ConstraintActivationKind::Check { .. }
2353 | ConstraintActivationKind::NotNull { .. }
2354 )
2355 && before.version().get().checked_add(1) == Some(after.version().get())
2356 && before
2357 .constraint_catalog()
2358 .clone()
2359 .with_aborted_activation(activation.id())
2360 .is_ok_and(|catalog| catalog == *after.constraint_catalog())
2361 && before
2362 .clone()
2363 .with_constraint_catalog(after.constraint_catalog().clone())
2364 .with_schema_version(after.version())
2365 == *after
2366 });
2367 let sql_unique_abort_with_version =
2368 before.constraint_activations().iter().any(|activation| {
2369 activation.origin() == ConstraintOrigin::SqlDdl
2370 && matches!(activation.kind(), ConstraintActivationKind::Unique { .. })
2371 && before.version().get().checked_add(1) == Some(after.version().get())
2372 && before
2373 .with_aborted_unique_activation(activation.id(), after.version())
2374 .is_ok_and(|expected| expected == *after)
2375 });
2376 let not_null_promotion = before.constraint_activations().iter().any(|activation| {
2377 matches!(activation.kind(), ConstraintActivationKind::NotNull { .. })
2378 && before
2379 .with_promoted_not_null_activation(activation.id(), after.version())
2380 .is_ok_and(|expected| expected == *after)
2381 });
2382 let unique_promotion = before.constraint_activations().iter().any(|activation| {
2383 matches!(activation.kind(), ConstraintActivationKind::Unique { .. })
2384 && before
2385 .with_promoted_unique_activation(activation.id(), after.version())
2386 .is_ok_and(|expected| expected == *after)
2387 });
2388 let relation_promotion = before.constraint_activations().iter().any(|activation| {
2389 matches!(activation.kind(), ConstraintActivationKind::Relation { .. })
2390 && before
2391 .with_promoted_relation_activation(activation.id(), after.version())
2392 .is_ok_and(|expected| expected == *after)
2393 });
2394 if !catalog_only_transition
2395 && !sql_row_local_abort_with_version
2396 && !sql_unique_abort_with_version
2397 && !not_null_promotion
2398 && !unique_promotion
2399 && !relation_promotion
2400 {
2401 return Err(InternalError::store_invariant());
2402 }
2403 }
2404 Ok(())
2405 }
2406
2407 pub(in crate::db) fn validate_constraint_validation_job_closure(
2409 &self,
2410 bundle: &AcceptedSchemaRevisionBundle,
2411 ) -> Result<(), InternalError> {
2412 self.validate_constraint_validation_job_closure_with_change(bundle, None, None)
2413 }
2414
2415 pub(in crate::db) fn validate_constraint_validation_job_closure_with_change(
2418 &self,
2419 bundle: &AcceptedSchemaRevisionBundle,
2420 replacement: Option<&ConstraintValidationJob>,
2421 removal: Option<(EntityTag, ConstraintId)>,
2422 ) -> Result<(), InternalError> {
2423 self.validate_constraint_validation_job_closure_with_change_in_view(
2424 bundle,
2425 replacement,
2426 removal,
2427 IdentityStateStorageView::Effective,
2428 )
2429 }
2430
2431 pub(in crate::db) fn validate_canonical_constraint_validation_job_closure_with_change(
2433 &self,
2434 bundle: &AcceptedSchemaRevisionBundle,
2435 replacement: Option<&ConstraintValidationJob>,
2436 removal: Option<(EntityTag, ConstraintId)>,
2437 ) -> Result<(), InternalError> {
2438 self.validate_constraint_validation_job_closure_with_change_in_view(
2439 bundle,
2440 replacement,
2441 removal,
2442 IdentityStateStorageView::Canonical,
2443 )
2444 }
2445
2446 fn validate_constraint_validation_job_closure_with_change_in_view(
2447 &self,
2448 bundle: &AcceptedSchemaRevisionBundle,
2449 replacement: Option<&ConstraintValidationJob>,
2450 removal: Option<(EntityTag, ConstraintId)>,
2451 view: IdentityStateStorageView,
2452 ) -> Result<(), InternalError> {
2453 if replacement.is_some() && removal.is_some() {
2454 return Err(InternalError::store_invariant());
2455 }
2456 let replacement_key = replacement.map(|job| {
2457 RawSchemaKey::from_constraint_validation_job(job.entity_tag(), job.constraint_id())
2458 });
2459 let removal_key = removal.map(|(entity_tag, constraint_id)| {
2460 RawSchemaKey::from_constraint_validation_job(entity_tag, constraint_id)
2461 });
2462 let mut expected = BTreeSet::new();
2463 for (entity_tag, snapshot) in bundle.entity_snapshots() {
2464 for activation in snapshot.constraint_activations() {
2465 let key =
2466 RawSchemaKey::from_constraint_validation_job(*entity_tag, activation.id());
2467 match activation.state() {
2468 ConstraintActivationState::EnforcingNewWrites => {
2469 if self
2470 .constraint_validation_job_after_change(
2471 key,
2472 replacement,
2473 replacement_key,
2474 removal_key,
2475 view,
2476 )?
2477 .is_some()
2478 {
2479 return Err(InternalError::store_corruption());
2480 }
2481 }
2482 ConstraintActivationState::Validating => {
2483 let job = self
2484 .constraint_validation_job_after_change(
2485 key,
2486 replacement,
2487 replacement_key,
2488 removal_key,
2489 view,
2490 )?
2491 .ok_or_else(InternalError::store_corruption)?;
2492 if job.entity_tag() != *entity_tag
2493 || job.entity_path() != snapshot.entity_path()
2494 {
2495 return Err(InternalError::store_corruption());
2496 }
2497 job.validate(Some(activation))?;
2498 expected.insert(key);
2499 }
2500 }
2501 }
2502 }
2503
2504 self.visit_constraint_validation_jobs_in_view(view, |key, raw| {
2505 if removal_key == Some(*key) || replacement_key == Some(*key) {
2506 return Ok(SchemaStoreVisit::Continue);
2507 }
2508 if !expected.contains(key) {
2509 return Err(InternalError::store_corruption());
2510 }
2511 let job = decode_constraint_validation_job(raw.as_bytes())?;
2512 if job.entity_tag() != key.entity_tag()
2513 || key.constraint_id() != Some(job.constraint_id())
2514 {
2515 return Err(InternalError::store_corruption());
2516 }
2517 Ok(SchemaStoreVisit::Continue)
2518 })?;
2519
2520 if let Some(key) = replacement_key
2521 && !expected.contains(&key)
2522 {
2523 return Err(InternalError::store_corruption());
2524 }
2525 if let Some(key) = removal_key
2526 && expected.contains(&key)
2527 {
2528 return Err(InternalError::store_corruption());
2529 }
2530
2531 Ok(())
2532 }
2533
2534 fn constraint_validation_job_after_change(
2535 &self,
2536 key: RawSchemaKey,
2537 replacement: Option<&ConstraintValidationJob>,
2538 replacement_key: Option<RawSchemaKey>,
2539 removal_key: Option<RawSchemaKey>,
2540 view: IdentityStateStorageView,
2541 ) -> Result<Option<ConstraintValidationJob>, InternalError> {
2542 if removal_key == Some(key) {
2543 return Ok(None);
2544 }
2545 if replacement_key == Some(key) {
2546 return Ok(replacement.cloned());
2547 }
2548 let raw = match view {
2549 IdentityStateStorageView::Effective => self.get_raw_value(&key),
2550 IdentityStateStorageView::Canonical => self.get_canonical_raw_value(&key)?,
2551 };
2552 raw.map(|raw| decode_constraint_validation_job(raw.as_bytes()))
2553 .transpose()
2554 }
2555
2556 pub(in crate::db) fn current_accepted_schema_authority_matches(
2559 &self,
2560 expected: &AcceptedSchemaAuthority,
2561 ) -> Result<bool, InternalError> {
2562 self.current_accepted_selection_for_authority(expected)
2563 .map(|selection| selection.is_some())
2564 }
2565
2566 fn current_accepted_selection_for_authority(
2569 &self,
2570 expected: &AcceptedSchemaAuthority,
2571 ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
2572 let Some(store_scope) = self.accepted_catalog_scope.get() else {
2573 return Ok(None);
2574 };
2575
2576 let cached = self
2579 .accepted_bundle_cache
2580 .try_borrow()
2581 .map_err(|_| InternalError::store_invariant())?
2582 .as_ref()
2583 .map(|cached| cached.selection);
2584 let selection = match cached {
2585 Some(selection) => Some(selection),
2586 None => self.current_accepted_schema_root()?,
2587 };
2588 Ok(selection.filter(|selection| {
2589 let root = selection.root();
2590 expected.matches_store_root(store_scope, root.revision(), root.fingerprint())
2591 }))
2592 }
2593
2594 pub(in crate::db) fn publish_accepted_schema_candidate(
2600 &mut self,
2601 incarnation: DatabaseIncarnationId,
2602 expected_revision: AcceptedSchemaRevision,
2603 candidate: &CandidateSchemaRevision,
2604 ) -> Result<(), InternalError> {
2605 let identity_transition = self.prepare_identity_state_transition(
2606 incarnation,
2607 candidate,
2608 IdentityStateStorageView::Effective,
2609 )?;
2610 if self.current_root_matches_candidate(candidate)? {
2611 if !identity_transition.is_empty() {
2612 return Err(InternalError::identity_state_corruption());
2613 }
2614 let selection = self
2615 .current_accepted_schema_root()?
2616 .ok_or_else(InternalError::store_corruption)?;
2617 self.retain_durable_candidate_entries(candidate, selection.slot())?;
2618 return Ok(());
2619 }
2620 let first = self.accepted_root_slot_bytes(0)?;
2621 let second = self.accepted_root_slot_bytes(1)?;
2622 prepare_accepted_schema_root_publication(
2623 [first.as_deref(), second.as_deref()],
2624 expected_revision,
2625 candidate,
2626 )
2627 .map_err(map_schema_publication_error)?;
2628
2629 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2630 self.insert_durable_raw_value(bundle_key, candidate.encoded_bundle().to_vec());
2631 let persisted_bundle = self
2632 .get_raw_value(&bundle_key)
2633 .ok_or_else(InternalError::store_corruption)?;
2634 let _verified = decode_verified_accepted_schema_revision_bundle(
2635 candidate.root(),
2636 persisted_bundle.as_bytes(),
2637 )?;
2638 self.apply_identity_state_transition(
2639 identity_transition,
2640 IdentityStateWriteTarget::Durable,
2641 )?;
2642
2643 let first = self.accepted_root_slot_bytes(0)?;
2646 let second = self.accepted_root_slot_bytes(1)?;
2647 let publication = prepare_accepted_schema_root_publication(
2648 [first.as_deref(), second.as_deref()],
2649 expected_revision,
2650 candidate,
2651 )
2652 .map_err(map_schema_publication_error)?;
2653 let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
2654 self.insert_durable_raw_value(root_key, publication.encoded_root().to_vec());
2655
2656 let selected = self
2657 .current_accepted_schema_root()?
2658 .ok_or_else(InternalError::store_corruption)?;
2659 if selected.root() != candidate.root() {
2660 return Err(InternalError::store_corruption());
2661 }
2662 self.retain_durable_candidate_entries(candidate, selected.slot())?;
2663 Ok(())
2664 }
2665
2666 pub(in crate::db) fn restore_live_accepted_schema_checkpoint(
2669 &mut self,
2670 incarnation: DatabaseIncarnationId,
2671 candidate: &CandidateSchemaRevision,
2672 checkpoint_identity_states: &IdentityStateInventory,
2673 ) -> Result<(), InternalError> {
2674 if !matches!(self.backend, SchemaStoreBackend::Heap(_)) {
2675 return Err(InternalError::store_invariant());
2676 }
2677 let checkpoint_validation = prepare_identity_state_transition(
2678 incarnation,
2679 Some(candidate.bundle()),
2680 candidate.bundle(),
2681 checkpoint_identity_states.clone(),
2682 )?;
2683 if !checkpoint_validation.is_empty() {
2684 return Err(InternalError::identity_state_corruption());
2685 }
2686 if self.current_root_matches_candidate(candidate)? {
2687 for state in checkpoint_identity_states.values() {
2688 let key = RawSchemaKey::from_identity_state(
2689 state.owner().entity_tag(),
2690 state.owner().field_id(),
2691 );
2692 self.insert_durable_raw_value(key, encode_identity_state(state)?);
2693 }
2694 if self.identity_state_inventory(IdentityStateStorageView::Effective)?
2695 != *checkpoint_identity_states
2696 {
2697 return Err(InternalError::identity_state_corruption());
2698 }
2699 let selection = self
2700 .current_accepted_schema_root()?
2701 .ok_or_else(InternalError::store_corruption)?;
2702 self.retain_durable_candidate_entries(candidate, selection.slot())?;
2703 return Ok(());
2704 }
2705 if self.current_accepted_schema_root()?.is_some()
2706 || !self
2707 .identity_state_inventory(IdentityStateStorageView::Effective)?
2708 .is_empty()
2709 {
2710 return Err(InternalError::store_corruption());
2711 }
2712
2713 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2714 self.insert_durable_raw_value(bundle_key, candidate.encoded_bundle().to_vec());
2715 for state in checkpoint_identity_states.values() {
2716 let key = RawSchemaKey::from_identity_state(
2717 state.owner().entity_tag(),
2718 state.owner().field_id(),
2719 );
2720 self.insert_durable_raw_value(key, encode_identity_state(state)?);
2721 }
2722 let root_key = RawSchemaKey::from_accepted_root_slot(0)?;
2723 self.insert_durable_raw_value(root_key, candidate.encoded_root().to_vec());
2724
2725 let selected = self
2726 .current_accepted_schema_root()?
2727 .ok_or_else(InternalError::store_corruption)?;
2728 if selected.root() != candidate.root() {
2729 return Err(InternalError::store_corruption());
2730 }
2731 self.retain_durable_candidate_entries(candidate, selected.slot())?;
2732 Ok(())
2733 }
2734
2735 pub(in crate::db) fn preflight_accepted_schema_candidate(
2742 &self,
2743 incarnation: DatabaseIncarnationId,
2744 expected_revision: AcceptedSchemaRevision,
2745 candidate: &CandidateSchemaRevision,
2746 ) -> Result<bool, InternalError> {
2747 let identity_transition = self.prepare_identity_state_transition(
2748 incarnation,
2749 candidate,
2750 IdentityStateStorageView::Effective,
2751 )?;
2752 if self.current_root_matches_candidate(candidate)? {
2753 if !identity_transition.is_empty() {
2754 return Err(InternalError::identity_state_corruption());
2755 }
2756 return Ok(true);
2757 }
2758 let first = self.accepted_root_slot_bytes(0)?;
2759 let second = self.accepted_root_slot_bytes(1)?;
2760 prepare_accepted_schema_root_publication(
2761 [first.as_deref(), second.as_deref()],
2762 expected_revision,
2763 candidate,
2764 )
2765 .map_err(map_schema_publication_error)?;
2766
2767 Ok(false)
2768 }
2769
2770 pub(in crate::db) fn prepare_fold_journaled_accepted_schema_candidate(
2772 &self,
2773 incarnation: DatabaseIncarnationId,
2774 expected_revision: AcceptedSchemaRevision,
2775 candidate: CandidateSchemaRevision,
2776 ) -> Result<PreparedAcceptedSchemaFold, InternalError> {
2777 if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
2778 return Err(InternalError::store_invariant());
2779 }
2780 let identity_transition = self.prepare_identity_state_transition(
2781 incarnation,
2782 &candidate,
2783 IdentityStateStorageView::Canonical,
2784 )?;
2785 let candidate_is_current = self.canonical_root_matches_candidate(&candidate)?;
2786 if candidate_is_current && !identity_transition.is_empty() {
2787 return Err(InternalError::identity_state_corruption());
2788 }
2789 let (updates, removals) = identity_transition.into_effects();
2790 let identity_removals = removals
2791 .into_iter()
2792 .map(|owner| RawSchemaKey::from_identity_state(owner.entity_tag(), owner.field_id()))
2793 .collect();
2794 let identity_updates = updates
2795 .into_iter()
2796 .map(|state| {
2797 Ok((
2798 RawSchemaKey::from_identity_state(
2799 state.owner().entity_tag(),
2800 state.owner().field_id(),
2801 ),
2802 encode_identity_state(&state)?,
2803 ))
2804 })
2805 .collect::<Result<Vec<_>, InternalError>>()?;
2806 let first = self.canonical_root_slot_bytes(0)?;
2807 let second = self.canonical_root_slot_bytes(1)?;
2808 let root_slot = if candidate_is_current {
2809 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
2810 .ok_or_else(InternalError::store_corruption)?
2811 .slot()
2812 } else {
2813 prepare_accepted_schema_root_publication(
2814 [first.as_deref(), second.as_deref()],
2815 expected_revision,
2816 &candidate,
2817 )
2818 .map_err(map_schema_publication_error)?
2819 .target_slot()
2820 };
2821 let retained = Self::candidate_entry_keys(&candidate, root_slot)?;
2822 Ok(PreparedAcceptedSchemaFold {
2823 candidate,
2824 expected_revision,
2825 identity_updates,
2826 identity_removals,
2827 retained,
2828 root_slot,
2829 })
2830 }
2831
2832 pub(in crate::db) fn projected_identity_state_count(
2834 &self,
2835 incarnation: DatabaseIncarnationId,
2836 candidate: &CandidateSchemaRevision,
2837 ) -> Result<usize, InternalError> {
2838 Ok(self
2839 .prepare_identity_state_transition(
2840 incarnation,
2841 candidate,
2842 IdentityStateStorageView::Effective,
2843 )?
2844 .projected_inventory_len())
2845 }
2846
2847 pub(in crate::db) fn apply_journaled_accepted_schema_candidate(
2849 &mut self,
2850 incarnation: DatabaseIncarnationId,
2851 expected_revision: AcceptedSchemaRevision,
2852 candidate: &CandidateSchemaRevision,
2853 ) -> Result<(), InternalError> {
2854 if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
2855 return Err(InternalError::store_invariant());
2856 }
2857 let identity_transition = self.prepare_identity_state_transition(
2858 incarnation,
2859 candidate,
2860 IdentityStateStorageView::Effective,
2861 )?;
2862 if self.current_root_matches_candidate(candidate)? {
2863 if !identity_transition.is_empty() {
2864 return Err(InternalError::identity_state_corruption());
2865 }
2866 let selection = self
2867 .current_accepted_schema_root()?
2868 .ok_or_else(InternalError::store_corruption)?;
2869 self.retain_materialized_candidate_entries(candidate, selection.slot())?;
2870 return Ok(());
2871 }
2872
2873 let first = self.accepted_root_slot_bytes(0)?;
2874 let second = self.accepted_root_slot_bytes(1)?;
2875 prepare_accepted_schema_root_publication(
2876 [first.as_deref(), second.as_deref()],
2877 expected_revision,
2878 candidate,
2879 )
2880 .map_err(map_schema_publication_error)?;
2881
2882 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2883 self.insert_raw_value(
2884 bundle_key,
2885 RawSchemaValue::from_encoded_control_record(candidate.encoded_bundle().to_vec()),
2886 );
2887 let persisted_bundle = self
2888 .get_raw_value(&bundle_key)
2889 .ok_or_else(InternalError::store_corruption)?;
2890 let _verified = decode_verified_accepted_schema_revision_bundle(
2891 candidate.root(),
2892 persisted_bundle.as_bytes(),
2893 )?;
2894 self.apply_identity_state_transition(
2895 identity_transition,
2896 IdentityStateWriteTarget::Materialized,
2897 )?;
2898
2899 let first = self.accepted_root_slot_bytes(0)?;
2900 let second = self.accepted_root_slot_bytes(1)?;
2901 let publication = prepare_accepted_schema_root_publication(
2902 [first.as_deref(), second.as_deref()],
2903 expected_revision,
2904 candidate,
2905 )
2906 .map_err(map_schema_publication_error)?;
2907 let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
2908 self.insert_raw_value(
2909 root_key,
2910 RawSchemaValue::from_encoded_control_record(publication.encoded_root().to_vec()),
2911 );
2912
2913 if !self.current_root_matches_candidate(candidate)? {
2914 return Err(InternalError::store_corruption());
2915 }
2916 let selection = self
2917 .current_accepted_schema_root()?
2918 .ok_or_else(InternalError::store_corruption)?;
2919 self.retain_materialized_candidate_entries(candidate, selection.slot())?;
2920 Ok(())
2921 }
2922
2923 pub(in crate::db) fn apply_prepared_accepted_schema_fold(
2925 &mut self,
2926 prepared: PreparedAcceptedSchemaFold,
2927 ) -> Result<(), InternalError> {
2928 let PreparedAcceptedSchemaFold {
2929 candidate,
2930 expected_revision,
2931 identity_updates,
2932 identity_removals,
2933 retained,
2934 root_slot,
2935 } = prepared;
2936 if self.canonical_root_matches_candidate(&candidate)? {
2937 if !identity_updates.is_empty() || !identity_removals.is_empty() {
2938 return Err(InternalError::identity_state_corruption());
2939 }
2940 let first = self.canonical_root_slot_bytes(0)?;
2941 let second = self.canonical_root_slot_bytes(1)?;
2942 let selection =
2943 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
2944 .ok_or_else(InternalError::store_corruption)?;
2945 if selection.slot() != root_slot {
2946 return Err(InternalError::store_invariant());
2947 }
2948 self.retain_canonical_candidate_entries(&retained)?;
2949 return Ok(());
2950 }
2951
2952 let first = self.canonical_root_slot_bytes(0)?;
2953 let second = self.canonical_root_slot_bytes(1)?;
2954 let publication = prepare_accepted_schema_root_publication(
2955 [first.as_deref(), second.as_deref()],
2956 expected_revision,
2957 &candidate,
2958 )
2959 .map_err(map_schema_publication_error)?;
2960 if publication.target_slot() != root_slot {
2961 return Err(InternalError::store_invariant());
2962 }
2963
2964 let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2965 self.insert_canonical_raw_value(bundle_key, candidate.encoded_bundle().to_vec())?;
2966 let persisted_bundle = self
2967 .get_canonical_raw_value(&bundle_key)?
2968 .ok_or_else(InternalError::store_corruption)?;
2969 let _verified = decode_verified_accepted_schema_revision_bundle(
2970 candidate.root(),
2971 persisted_bundle.as_bytes(),
2972 )?;
2973 for key in identity_removals {
2974 self.remove_identity_state_key(key, IdentityStateWriteTarget::Canonical)?;
2975 }
2976 for (key, bytes) in identity_updates {
2977 self.insert_canonical_raw_value(key, bytes)?;
2978 }
2979
2980 let first = self.canonical_root_slot_bytes(0)?;
2981 let second = self.canonical_root_slot_bytes(1)?;
2982 let publication = prepare_accepted_schema_root_publication(
2983 [first.as_deref(), second.as_deref()],
2984 expected_revision,
2985 &candidate,
2986 )
2987 .map_err(map_schema_publication_error)?;
2988 let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
2989 self.insert_canonical_raw_value(root_key, publication.encoded_root().to_vec())?;
2990
2991 if !self.canonical_root_matches_candidate(&candidate)? {
2992 return Err(InternalError::store_corruption());
2993 }
2994 let first = self.canonical_root_slot_bytes(0)?;
2995 let second = self.canonical_root_slot_bytes(1)?;
2996 let selection = select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
2997 .ok_or_else(InternalError::store_corruption)?;
2998 if selection.slot() != root_slot {
2999 return Err(InternalError::store_invariant());
3000 }
3001 self.retain_canonical_candidate_entries(&retained)?;
3002 Ok(())
3003 }
3004
3005 pub(in crate::db) fn current_accepted_persisted_snapshot(
3008 &self,
3009 entity: EntityTag,
3010 ) -> Result<Option<PersistedSchemaSnapshot>, InternalError> {
3011 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
3012 return Ok(None);
3013 };
3014
3015 Ok(bundle.entity_snapshots().get(&entity).cloned())
3016 }
3017
3018 pub(in crate::db) fn current_accepted_catalog_selection(
3020 &self,
3021 entity: EntityTag,
3022 entity_path: &str,
3023 store_path: &'static str,
3024 ) -> Result<Option<AcceptedCatalogSnapshotSelection>, InternalError> {
3025 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
3026 return Ok(None);
3027 };
3028 if bundle.store_path() != store_path {
3029 return Err(InternalError::store_corruption());
3030 }
3031 let Some(snapshot) = bundle.entity_snapshots().get(&entity) else {
3032 return Ok(None);
3033 };
3034 if snapshot.entity_path() != entity_path {
3035 return Err(InternalError::store_corruption());
3036 }
3037
3038 let cache = self
3039 .accepted_bundle_cache
3040 .try_borrow()
3041 .map_err(|_| InternalError::store_invariant())?;
3042 let cached = cache.as_ref().ok_or_else(InternalError::store_invariant)?;
3043 if let Some(selection) = cached
3044 .entity_selections
3045 .try_borrow()
3046 .map_err(|_| InternalError::store_invariant())?
3047 .get(&entity)
3048 .cloned()
3049 {
3050 return Ok(Some(selection));
3051 }
3052
3053 let selected = AcceptedCatalogSnapshotSelection::from_verified_snapshot(
3054 entity,
3055 store_path,
3056 snapshot,
3057 cached.value_catalog.clone(),
3058 )?;
3059 cached
3060 .entity_selections
3061 .try_borrow_mut()
3062 .map_err(|_| InternalError::store_invariant())?
3063 .insert(entity, selected.clone());
3064
3065 Ok(Some(selected))
3066 }
3067
3068 pub(in crate::db) fn current_canonical_accepted_catalog_selection(
3072 &self,
3073 entity: EntityTag,
3074 entity_path: &str,
3075 store_path: &'static str,
3076 ) -> Result<Option<AcceptedCatalogSnapshotSelection>, InternalError> {
3077 let first = self.canonical_root_slot_bytes(0)?;
3078 let second = self.canonical_root_slot_bytes(1)?;
3079 let Some(selection) =
3080 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
3081 else {
3082 return Ok(None);
3083 };
3084 let bundle_key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
3085 let raw_bundle = self
3086 .get_canonical_raw_value(&bundle_key)?
3087 .ok_or_else(InternalError::store_corruption)?;
3088 let bundle = decode_verified_accepted_schema_revision_bundle(
3089 selection.root(),
3090 raw_bundle.as_bytes(),
3091 )?;
3092 if bundle.store_path() != store_path {
3093 return Err(InternalError::store_corruption());
3094 }
3095 let Some(snapshot) = bundle.entity_snapshots().get(&entity) else {
3096 return Ok(None);
3097 };
3098 if snapshot.entity_path() != entity_path {
3099 return Err(InternalError::store_corruption());
3100 }
3101
3102 AcceptedCatalogSnapshotSelection::from_verified_snapshot(
3103 entity,
3104 store_path,
3105 snapshot,
3106 AcceptedValueCatalogHandle::new(
3107 bundle.enum_catalog().clone(),
3108 bundle.composite_catalog().clone(),
3109 self.accepted_catalog_scope
3110 .get_or_init(AcceptedStoreCatalogScope::new)
3111 .clone(),
3112 bundle.revision(),
3113 selection.root().fingerprint(),
3114 ),
3115 )
3116 .map(Some)
3117 }
3118
3119 #[cfg(test)]
3125 pub(in crate::db) fn catalog_metadata(
3126 &self,
3127 ) -> Result<Option<SchemaStoreCatalogMetadata>, InternalError> {
3128 Ok(self
3129 .allocation_metadata()?
3130 .map(SchemaStoreAllocationMetadata::schema))
3131 }
3132
3133 pub(in crate::db) fn allocation_metadata(
3139 &self,
3140 ) -> Result<Option<SchemaStoreAllocationMetadata>, InternalError> {
3141 let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
3142 return Ok(None);
3143 };
3144 let snapshots = bundle.entity_snapshots();
3145 if snapshots.is_empty() {
3146 return Ok(None);
3147 }
3148
3149 Ok(Some(SchemaStoreAllocationMetadata::new(
3150 derive_data_allocation_metadata(snapshots)?,
3151 derive_index_allocation_metadata(snapshots)?,
3152 derive_schema_catalog_metadata(snapshots)?,
3153 )))
3154 }
3155
3156 fn insert_raw_value(
3158 &mut self,
3159 key: RawSchemaKey,
3160 snapshot: RawSchemaValue,
3161 ) -> Option<RawSchemaValue> {
3162 self.invalidate_accepted_bundle_cache_for_key(key);
3163 let previous_journaled = if matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
3164 self.get_raw_value_for_backend(&key)
3165 } else {
3166 None
3167 };
3168 match &mut self.backend {
3169 SchemaStoreBackend::Heap(map) => map.insert(key, snapshot),
3170 SchemaStoreBackend::Journaled {
3171 live, tombstones, ..
3172 } => {
3173 tombstones.remove(&key);
3174 live.insert(key, snapshot);
3175 previous_journaled
3176 }
3177 }
3178 }
3179
3180 #[must_use]
3182 fn get_raw_value(&self, key: &RawSchemaKey) -> Option<RawSchemaValue> {
3183 match &self.backend {
3184 SchemaStoreBackend::Heap(map) => map.get(key).cloned(),
3185 SchemaStoreBackend::Journaled { .. } => self.get_raw_value_for_backend(key),
3186 }
3187 }
3188
3189 fn accepted_root_slot_bytes(&self, slot: usize) -> Result<Option<Vec<u8>>, InternalError> {
3190 let key = RawSchemaKey::from_accepted_root_slot(slot)?;
3191 Ok(self.get_raw_value(&key).map(RawSchemaValue::into_bytes))
3192 }
3193
3194 fn canonical_root_slot_bytes(&self, slot: usize) -> Result<Option<Vec<u8>>, InternalError> {
3195 let key = RawSchemaKey::from_accepted_root_slot(slot)?;
3196 Ok(self
3197 .get_canonical_raw_value(&key)?
3198 .map(RawSchemaValue::into_bytes))
3199 }
3200
3201 fn current_root_matches_candidate(
3202 &self,
3203 candidate: &CandidateSchemaRevision,
3204 ) -> Result<bool, InternalError> {
3205 let Some(selection) = self.current_accepted_schema_root()? else {
3206 return Ok(false);
3207 };
3208 if selection.root() != candidate.root() {
3209 return Ok(false);
3210 }
3211 let key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
3212 let bundle = self
3213 .get_raw_value(&key)
3214 .ok_or_else(InternalError::store_corruption)?;
3215 let _verified =
3216 decode_verified_accepted_schema_revision_bundle(candidate.root(), bundle.as_bytes())?;
3217 Ok(true)
3218 }
3219
3220 fn canonical_root_matches_candidate(
3221 &self,
3222 candidate: &CandidateSchemaRevision,
3223 ) -> Result<bool, InternalError> {
3224 let first = self.canonical_root_slot_bytes(0)?;
3225 let second = self.canonical_root_slot_bytes(1)?;
3226 let Some(selection) =
3227 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
3228 else {
3229 return Ok(false);
3230 };
3231 if selection.root() != candidate.root() {
3232 return Ok(false);
3233 }
3234 let key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
3235 let bundle = self
3236 .get_canonical_raw_value(&key)?
3237 .ok_or_else(InternalError::store_corruption)?;
3238 let _verified =
3239 decode_verified_accepted_schema_revision_bundle(candidate.root(), bundle.as_bytes())?;
3240 Ok(true)
3241 }
3242
3243 fn get_canonical_raw_value(
3244 &self,
3245 key: &RawSchemaKey,
3246 ) -> Result<Option<RawSchemaValue>, InternalError> {
3247 match &self.backend {
3248 SchemaStoreBackend::Journaled { canonical, .. } => Ok(canonical.get(key)),
3249 SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
3250 }
3251 }
3252
3253 fn insert_canonical_raw_value(
3254 &mut self,
3255 key: RawSchemaKey,
3256 bytes: Vec<u8>,
3257 ) -> Result<(), InternalError> {
3258 self.invalidate_accepted_bundle_cache_for_key(key);
3259 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
3260 return Err(InternalError::store_invariant());
3261 };
3262 canonical.insert(key, RawSchemaValue::from_encoded_control_record(bytes));
3263 Ok(())
3264 }
3265
3266 fn insert_durable_raw_value(&mut self, key: RawSchemaKey, bytes: Vec<u8>) {
3270 self.invalidate_accepted_bundle_cache_for_key(key);
3271 let value = RawSchemaValue::from_encoded_control_record(bytes);
3272 match &mut self.backend {
3273 SchemaStoreBackend::Heap(map) => {
3274 map.insert(key, value);
3275 }
3276 SchemaStoreBackend::Journaled {
3277 canonical,
3278 live,
3279 tombstones,
3280 ..
3281 } => {
3282 live.remove(&key);
3283 tombstones.remove(&key);
3284 canonical.insert(key, value);
3285 }
3286 }
3287 }
3288
3289 fn invalidate_accepted_bundle_cache_for_key(&mut self, key: RawSchemaKey) {
3290 if key.is_accepted_root() {
3291 self.accepted_bundle_cache.get_mut().take();
3292 }
3293 }
3294
3295 fn candidate_entry_keys(
3296 candidate: &CandidateSchemaRevision,
3297 root_slot: usize,
3298 ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
3299 let mut keys = BTreeSet::new();
3300 keys.insert(RawSchemaKey::from_accepted_bundle(
3301 candidate.root().bundle_key(),
3302 ));
3303 keys.insert(RawSchemaKey::from_accepted_root_slot(root_slot)?);
3304 for (entity_tag, snapshot) in candidate.bundle().entity_snapshots() {
3305 for activation in snapshot
3306 .constraint_activations()
3307 .iter()
3308 .filter(|activation| activation.state() == ConstraintActivationState::Validating)
3309 {
3310 keys.insert(RawSchemaKey::from_constraint_validation_job(
3311 *entity_tag,
3312 activation.id(),
3313 ));
3314 }
3315 }
3316 Ok(keys)
3317 }
3318
3319 fn positioned_candidate_effect_keys(
3320 &self,
3321 incarnation: DatabaseIncarnationId,
3322 expected_revision: AcceptedSchemaRevision,
3323 candidate: &CandidateSchemaRevision,
3324 view: IdentityStateStorageView,
3325 ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
3326 let identity_transition =
3327 self.prepare_identity_state_transition(incarnation, candidate, view)?;
3328 let (first, second, candidate_is_current) = match view {
3329 IdentityStateStorageView::Effective => (
3330 self.accepted_root_slot_bytes(0)?,
3331 self.accepted_root_slot_bytes(1)?,
3332 self.current_root_matches_candidate(candidate)?,
3333 ),
3334 IdentityStateStorageView::Canonical => (
3335 self.canonical_root_slot_bytes(0)?,
3336 self.canonical_root_slot_bytes(1)?,
3337 self.canonical_root_matches_candidate(candidate)?,
3338 ),
3339 };
3340 let root_slot = if candidate_is_current {
3341 select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
3342 .ok_or_else(InternalError::store_corruption)?
3343 .slot()
3344 } else {
3345 prepare_accepted_schema_root_publication(
3346 [first.as_deref(), second.as_deref()],
3347 expected_revision,
3348 candidate,
3349 )
3350 .map_err(map_schema_publication_error)?
3351 .target_slot()
3352 };
3353 let mut keys = Self::candidate_entry_keys(candidate, root_slot)?;
3354 let (updates, removals) = identity_transition.into_effects();
3355 for owner in removals {
3356 keys.insert(RawSchemaKey::from_identity_state(
3357 owner.entity_tag(),
3358 owner.field_id(),
3359 ));
3360 }
3361 for state in updates {
3362 keys.insert(RawSchemaKey::from_identity_state(
3363 state.owner().entity_tag(),
3364 state.owner().field_id(),
3365 ));
3366 }
3367
3368 let SchemaStoreBackend::Journaled {
3369 canonical,
3370 live,
3371 tombstones,
3372 positions,
3373 } = &self.backend
3374 else {
3375 return Err(InternalError::store_invariant());
3376 };
3377 for entry in canonical.range(RawSchemaKey::all_catalog_range_bounds()) {
3378 let has_relevant_overlay = matches!(view, IdentityStateStorageView::Effective)
3379 || positions.is_positioned(entry.key())
3380 || live.contains_key(entry.key())
3381 || tombstones.contains(entry.key());
3382 if has_relevant_overlay {
3383 keys.insert(*entry.key());
3384 }
3385 }
3386 if matches!(view, IdentityStateStorageView::Effective) {
3387 for (key, _) in live.range(RawSchemaKey::all_catalog_range_bounds()) {
3388 keys.insert(*key);
3389 }
3390 }
3391 Ok(keys)
3392 }
3393
3394 fn positioned_journal_batch_keys(
3395 &self,
3396 incarnation: DatabaseIncarnationId,
3397 batch: &JournalBatch,
3398 view: IdentityStateStorageView,
3399 ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
3400 let mut keys = BTreeSet::new();
3401 for record in batch.records() {
3402 match record {
3403 JournalRecord::AcceptedSchemaPublish {
3404 expected_revision,
3405 schema_bundle_bytes,
3406 schema_root_bytes,
3407 ..
3408 } => {
3409 let candidate = CandidateSchemaRevision::from_encoded(
3410 schema_bundle_bytes.clone(),
3411 schema_root_bytes.clone(),
3412 )?;
3413 keys.extend(self.positioned_candidate_effect_keys(
3414 incarnation,
3415 *expected_revision,
3416 &candidate,
3417 view,
3418 )?);
3419 }
3420 JournalRecord::ConstraintValidationJobPut {
3421 entity_tag,
3422 constraint_id,
3423 ..
3424 }
3425 | JournalRecord::ConstraintValidationJobDelete {
3426 entity_tag,
3427 constraint_id,
3428 ..
3429 } => {
3430 keys.insert(RawSchemaKey::from_constraint_validation_job(
3431 *entity_tag,
3432 *constraint_id,
3433 ));
3434 }
3435 JournalRecord::IdentityRangeAdvance { range } => {
3436 keys.insert(RawSchemaKey::from_identity_state(
3437 range.owner().entity_tag(),
3438 range.owner().field_id(),
3439 ));
3440 }
3441 JournalRecord::RowPut { .. }
3442 | JournalRecord::RowDelete { .. }
3443 | JournalRecord::AcceptedSchemaIndexDelete { .. }
3444 | JournalRecord::AcceptedSchemaIndexPut { .. }
3445 | JournalRecord::ConstraintValidationIndexPut { .. } => {}
3446 #[cfg(any(test, feature = "migration"))]
3447 JournalRecord::SchemaMigrationRowPut { .. }
3448 | JournalRecord::SchemaMigrationIndexPut { .. } => {}
3449 }
3450 }
3451 Ok(keys)
3452 }
3453
3454 fn retain_durable_candidate_entries(
3458 &mut self,
3459 candidate: &CandidateSchemaRevision,
3460 root_slot: usize,
3461 ) -> Result<(), InternalError> {
3462 let keep = Self::candidate_entry_keys(candidate, root_slot)?;
3463 self.accepted_bundle_cache.get_mut().take();
3464 match &mut self.backend {
3465 SchemaStoreBackend::Heap(map) => {
3466 Self::retain_heap_catalog_entries(map, &keep);
3467 }
3468 SchemaStoreBackend::Journaled {
3469 canonical,
3470 live,
3471 tombstones,
3472 ..
3473 } => {
3474 let stale = canonical
3475 .range(RawSchemaKey::all_catalog_range_bounds())
3476 .filter_map(|entry| (!keep.contains(entry.key())).then_some(*entry.key()))
3477 .collect::<Vec<_>>();
3478 for key in stale {
3479 canonical.remove(&key);
3480 }
3481 Self::retain_heap_catalog_entries(live, &keep);
3482 let cleared = tombstones
3483 .range(RawSchemaKey::all_catalog_range_bounds())
3484 .copied()
3485 .collect::<Vec<_>>();
3486 for key in cleared {
3487 tombstones.remove(&key);
3488 }
3489 }
3490 }
3491 Ok(())
3492 }
3493
3494 fn retain_heap_catalog_entries(
3496 map: &mut StdBTreeMap<RawSchemaKey, RawSchemaValue>,
3497 keep: &BTreeSet<RawSchemaKey>,
3498 ) {
3499 let stale = map
3500 .range(RawSchemaKey::all_catalog_range_bounds())
3501 .filter_map(|(key, _)| (!keep.contains(key)).then_some(*key))
3502 .collect::<Vec<_>>();
3503 for key in stale {
3504 map.remove(&key);
3505 }
3506 }
3507
3508 fn retain_materialized_candidate_entries(
3509 &mut self,
3510 candidate: &CandidateSchemaRevision,
3511 root_slot: usize,
3512 ) -> Result<(), InternalError> {
3513 let keep = Self::candidate_entry_keys(candidate, root_slot)?;
3514 self.accepted_bundle_cache.get_mut().take();
3515 let SchemaStoreBackend::Journaled {
3516 canonical,
3517 live,
3518 tombstones,
3519 ..
3520 } = &mut self.backend
3521 else {
3522 return Err(InternalError::store_invariant());
3523 };
3524 Self::retain_heap_catalog_entries(live, &keep);
3525 let canonical_keys = canonical
3526 .range(RawSchemaKey::all_catalog_range_bounds())
3527 .map(|entry| *entry.key())
3528 .collect::<Vec<_>>();
3529 for key in canonical_keys {
3530 if keep.contains(&key) {
3531 tombstones.remove(&key);
3532 } else {
3533 tombstones.insert(key);
3534 }
3535 }
3536 Ok(())
3537 }
3538
3539 fn retain_canonical_candidate_entries(
3540 &mut self,
3541 keep: &BTreeSet<RawSchemaKey>,
3542 ) -> Result<(), InternalError> {
3543 self.accepted_bundle_cache.get_mut().take();
3544 let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
3545 return Err(InternalError::store_invariant());
3546 };
3547 let stale = canonical
3548 .range(RawSchemaKey::all_catalog_range_bounds())
3549 .filter_map(|entry| (!keep.contains(entry.key())).then_some(*entry.key()))
3550 .collect::<Vec<_>>();
3551 for key in stale {
3552 canonical.remove(&key);
3553 }
3554 Ok(())
3555 }
3556
3557 fn current_accepted_schema_bundle_ref(
3558 &self,
3559 ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
3560 self.current_accepted_schema_authority_ref()
3561 .map(|authority| authority.map(|(_selection, bundle)| bundle))
3562 }
3563
3564 pub(in crate::db) fn current_accepted_schema_authority_ref(
3566 &self,
3567 ) -> Result<
3568 Option<(
3569 AcceptedSchemaRootSelection,
3570 Ref<'_, AcceptedSchemaRevisionBundle>,
3571 )>,
3572 InternalError,
3573 > {
3574 let selection = self.current_accepted_schema_root()?;
3575 self.accepted_schema_authority_ref_for_selection(selection)
3576 }
3577
3578 fn accepted_schema_authority_ref_for_selection(
3579 &self,
3580 selection: Option<AcceptedSchemaRootSelection>,
3581 ) -> Result<
3582 Option<(
3583 AcceptedSchemaRootSelection,
3584 Ref<'_, AcceptedSchemaRevisionBundle>,
3585 )>,
3586 InternalError,
3587 > {
3588 let Some(selection) = selection else {
3589 self.accepted_bundle_cache
3590 .try_borrow_mut()
3591 .map_err(|_| InternalError::store_invariant())?
3592 .take();
3593 return Ok(None);
3594 };
3595
3596 let cache_matches = self
3597 .accepted_bundle_cache
3598 .try_borrow()
3599 .map_err(|_| InternalError::store_invariant())?
3600 .as_ref()
3601 .is_some_and(|cached| cached.selection == selection);
3602 if !cache_matches {
3603 let key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
3604 let raw = self
3605 .get_raw_value(&key)
3606 .ok_or_else(InternalError::store_corruption)?;
3607 let bundle =
3608 decode_verified_accepted_schema_revision_bundle(selection.root(), raw.as_bytes())?;
3609 self.validate_constraint_validation_job_closure(&bundle)?;
3610 #[cfg(test)]
3611 ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES
3612 .with(|misses| misses.set(misses.get().saturating_add(1)));
3613 let value_catalog = AcceptedValueCatalogHandle::new(
3614 bundle.enum_catalog().clone(),
3615 bundle.composite_catalog().clone(),
3616 self.accepted_catalog_scope
3617 .get_or_init(AcceptedStoreCatalogScope::new)
3618 .clone(),
3619 bundle.revision(),
3620 selection.root().fingerprint(),
3621 );
3622 let cardinality_domain = Rc::new(CardinalityAcceptedDomain::derive(&bundle)?);
3623 *self
3624 .accepted_bundle_cache
3625 .try_borrow_mut()
3626 .map_err(|_| InternalError::store_invariant())? = Some(AcceptedSchemaBundleCache {
3627 selection,
3628 bundle,
3629 cardinality_domain,
3630 value_catalog,
3631 entity_selections: RefCell::new(StdBTreeMap::new()),
3632 });
3633 }
3634
3635 let cache = self
3636 .accepted_bundle_cache
3637 .try_borrow()
3638 .map_err(|_| InternalError::store_invariant())?;
3639 let bundle = Ref::filter_map(cache, |cache| {
3640 cache
3641 .as_ref()
3642 .filter(|cached| cached.selection == selection)
3643 .map(|cached| &cached.bundle)
3644 })
3645 .map_err(|_| InternalError::store_invariant())?;
3646 self.validate_identity_state_closure(&bundle)?;
3647 Ok(Some((selection, bundle)))
3648 }
3649
3650 pub(in crate::db) fn accepted_cardinality_domain_for_selection(
3652 &self,
3653 selection: Option<AcceptedSchemaRootSelection>,
3654 ) -> Result<Option<(AcceptedSchemaRootSelection, Rc<CardinalityAcceptedDomain>)>, InternalError>
3655 {
3656 let Some(selection) = selection else {
3657 self.accepted_bundle_cache
3658 .try_borrow_mut()
3659 .map_err(|_| InternalError::store_invariant())?
3660 .take();
3661 return Ok(None);
3662 };
3663 let cache_matches = self
3664 .accepted_bundle_cache
3665 .try_borrow()
3666 .map_err(|_| InternalError::store_invariant())?
3667 .as_ref()
3668 .is_some_and(|cached| cached.selection == selection);
3669 if !cache_matches {
3670 let authority = self
3671 .accepted_schema_authority_ref_for_selection(Some(selection))?
3672 .ok_or_else(InternalError::store_invariant)?;
3673 drop(authority);
3674 }
3675 let cache = self
3676 .accepted_bundle_cache
3677 .try_borrow()
3678 .map_err(|_| InternalError::store_invariant())?;
3679 let domain = cache
3680 .as_ref()
3681 .filter(|cached| cached.selection == selection)
3682 .map(|cached| Rc::clone(&cached.cardinality_domain))
3683 .ok_or_else(InternalError::store_invariant)?;
3684 Ok(Some((selection, domain)))
3685 }
3686
3687 pub(in crate::db) fn cached_cardinality_domain_for_root(
3689 &self,
3690 root: CardinalityAcceptedRootIdentity,
3691 ) -> Result<Option<Rc<CardinalityAcceptedDomain>>, InternalError> {
3692 let cache = self
3693 .accepted_bundle_cache
3694 .try_borrow()
3695 .map_err(|_| InternalError::store_invariant())?;
3696 Ok(cache
3697 .as_ref()
3698 .filter(|cached| root.matches(cached.selection.root()))
3699 .map(|cached| Rc::clone(&cached.cardinality_domain)))
3700 }
3701
3702 fn visit_constraint_validation_jobs_in_view<E>(
3703 &self,
3704 view: IdentityStateStorageView,
3705 visitor: impl FnMut(&RawSchemaKey, &RawSchemaValue) -> Result<SchemaStoreVisit, E>,
3706 ) -> Result<(), E> {
3707 let bounds = RawSchemaKey::all_constraint_validation_job_range_bounds();
3708 match (&self.backend, view) {
3709 (SchemaStoreBackend::Heap(map), _) => {
3710 let mut visitor = visitor;
3711 for (key, snapshot) in map.range((bounds.0, bounds.1)) {
3712 if visitor(key, snapshot)?.should_stop() {
3713 break;
3714 }
3715 }
3716 }
3717 (
3718 SchemaStoreBackend::Journaled {
3719 canonical,
3720 live,
3721 tombstones,
3722 ..
3723 },
3724 IdentityStateStorageView::Effective,
3725 ) => Self::visit_journaled_raw_value_range(
3726 canonical,
3727 live,
3728 tombstones,
3729 bounds,
3730 Direction::Asc,
3731 visitor,
3732 )?,
3733 (
3734 SchemaStoreBackend::Journaled { canonical, .. },
3735 IdentityStateStorageView::Canonical,
3736 ) => {
3737 let mut visitor = visitor;
3738 for entry in canonical.range((bounds.0, bounds.1)) {
3739 if visitor(entry.key(), &entry.value())?.should_stop() {
3740 break;
3741 }
3742 }
3743 }
3744 }
3745 Ok(())
3746 }
3747
3748 #[cfg(test)]
3749 #[must_use]
3750 pub(in crate::db) fn canonical_len_for_tests(&self) -> u64 {
3751 match &self.backend {
3752 SchemaStoreBackend::Journaled { canonical: map, .. } => map.len(),
3753 SchemaStoreBackend::Heap(_) => 0,
3754 }
3755 }
3756
3757 #[cfg(all(test, feature = "sql"))]
3759 pub(in crate::db) fn corrupt_current_accepted_schema_root_for_tests(
3760 &mut self,
3761 ) -> Result<(), InternalError> {
3762 let selected = self
3763 .current_accepted_schema_root()?
3764 .ok_or_else(InternalError::store_corruption)?;
3765 let key = RawSchemaKey::from_accepted_root_slot(selected.slot())?;
3766 self.insert_durable_raw_value(key, vec![0xff]);
3767 Ok(())
3768 }
3769
3770 fn get_raw_value_for_backend(&self, key: &RawSchemaKey) -> Option<RawSchemaValue> {
3771 let SchemaStoreBackend::Journaled {
3772 canonical,
3773 live,
3774 tombstones,
3775 ..
3776 } = &self.backend
3777 else {
3778 return None;
3779 };
3780
3781 if tombstones.contains(key) {
3782 return None;
3783 }
3784 live.get(key).cloned().or_else(|| canonical.get(key))
3785 }
3786
3787 fn visit_journaled_raw_value_range<E>(
3788 canonical: &StableBTreeMap<RawSchemaKey, RawSchemaValue, DefaultMemoryImpl>,
3789 live: &StdBTreeMap<RawSchemaKey, RawSchemaValue>,
3790 tombstones: &BTreeSet<RawSchemaKey>,
3791 bounds: (RangeBound<RawSchemaKey>, RangeBound<RawSchemaKey>),
3792 direction: Direction,
3793 mut visitor: impl FnMut(&RawSchemaKey, &RawSchemaValue) -> Result<SchemaStoreVisit, E>,
3794 ) -> Result<(), E> {
3795 match direction {
3796 Direction::Asc => {
3797 for entry in ordered_overlay_entries(
3798 canonical.range((bounds.0, bounds.1)),
3799 live.range((bounds.0, bounds.1)),
3800 Direction::Asc,
3801 |entry| entry.key(),
3802 |entry| entry.0,
3803 tombstones,
3804 ) {
3805 let visit = match entry {
3806 OrderedOverlayEntry::Canonical(canonical_entry) => {
3807 visitor(canonical_entry.key(), &canonical_entry.value())?
3808 }
3809 OrderedOverlayEntry::Live((key, snapshot)) => visitor(key, snapshot)?,
3810 };
3811 if visit.should_stop() {
3812 return Ok(());
3813 }
3814 }
3815 }
3816 Direction::Desc => {
3817 for entry in ordered_overlay_entries(
3818 canonical.range((bounds.0, bounds.1)).rev(),
3819 live.range((bounds.0, bounds.1)).rev(),
3820 Direction::Desc,
3821 |entry| entry.key(),
3822 |entry| entry.0,
3823 tombstones,
3824 ) {
3825 let visit = match entry {
3826 OrderedOverlayEntry::Canonical(canonical_entry) => {
3827 visitor(canonical_entry.key(), &canonical_entry.value())?
3828 }
3829 OrderedOverlayEntry::Live((key, snapshot)) => visitor(key, snapshot)?,
3830 };
3831 if visit.should_stop() {
3832 return Ok(());
3833 }
3834 }
3835 }
3836 }
3837
3838 Ok(())
3839 }
3840}
3841
3842fn map_schema_publication_error(error: AcceptedSchemaPublicationError) -> InternalError {
3843 match error {
3844 AcceptedSchemaPublicationError::StaleSchemaRevision { .. }
3845 | AcceptedSchemaPublicationError::RevisionExhausted => InternalError::store_unsupported(),
3846 AcceptedSchemaPublicationError::InvalidCandidate => InternalError::store_invariant(),
3847 AcceptedSchemaPublicationError::CorruptRootSlots => InternalError::store_corruption(),
3848 }
3849}
3850
3851fn derive_data_allocation_metadata(
3852 snapshots: &StdBTreeMap<EntityTag, PersistedSchemaSnapshot>,
3853) -> Result<SchemaStoreCatalogMetadata, InternalError> {
3854 let mut max_version = SchemaVersion::initial();
3855 let mut hasher = new_hash_sha256();
3856 write_hash_tag_u8(&mut hasher, SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_DOMAIN);
3857
3858 for (entity, persisted) in snapshots {
3859 if persisted.version() > max_version {
3860 max_version = persisted.version();
3861 }
3862
3863 let data_projection = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
3864 persisted.version(),
3865 persisted.entity_path().to_string(),
3866 persisted.entity_name().to_string(),
3867 persisted.primary_key_field_ids().to_vec(),
3868 persisted.row_layout().clone(),
3869 persisted.fields().to_vec(),
3870 Vec::new(),
3871 );
3872 let constraint_catalog = crate::db::schema::AcceptedConstraintCatalog::initial(
3873 data_projection.fields(),
3874 data_projection.indexes(),
3875 data_projection.relations(),
3876 )
3877 .map_err(|_| InternalError::store_invariant())?;
3878 let data_projection = data_projection.with_constraint_catalog(constraint_catalog);
3879 let encoded = encode_persisted_schema_snapshot(&data_projection)?;
3880
3881 write_hash_u64(&mut hasher, entity.value());
3882 write_hash_u32(&mut hasher, persisted.version().get());
3883 write_hash_len_u32(&mut hasher, encoded.len());
3884 hasher.update(encoded);
3885 }
3886
3887 Ok(finalize_schema_metadata(
3888 max_version,
3889 SCHEMA_STORE_FINGERPRINT_METHOD_VERSION,
3890 hasher,
3891 snapshots.len(),
3892 ))
3893}
3894
3895fn derive_index_allocation_metadata(
3896 snapshots: &StdBTreeMap<EntityTag, PersistedSchemaSnapshot>,
3897) -> Result<SchemaStoreCatalogMetadata, InternalError> {
3898 let mut max_version = SchemaVersion::initial();
3899 let mut hasher = new_hash_sha256();
3900 write_hash_tag_u8(
3901 &mut hasher,
3902 SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_DOMAIN,
3903 );
3904
3905 for (entity, persisted) in snapshots {
3906 if persisted.version() > max_version {
3907 max_version = persisted.version();
3908 }
3909
3910 write_hash_u64(&mut hasher, entity.value());
3911 write_hash_u32(&mut hasher, persisted.version().get());
3912 write_hash_len_u32(&mut hasher, persisted.indexes().len());
3913 for index in persisted.indexes() {
3914 write_hash_u32(&mut hasher, u32::from(index.ordinal()));
3915 write_hash_str_u32(&mut hasher, index.name());
3916 write_hash_str_u32(&mut hasher, index.store());
3917 write_hash_tag_u8(&mut hasher, u8::from(index.unique()));
3918 write_hash_str_u32(&mut hasher, persisted_index_origin_name(index.origin()));
3919 match index.predicate() {
3920 Some(predicate) => {
3921 write_hash_tag_u8(&mut hasher, 1);
3922 let bytes = predicate.canonical_bytes()?;
3923 write_hash_len_u32(&mut hasher, bytes.len());
3924 hasher.update(&bytes);
3925 }
3926 None => write_hash_tag_u8(&mut hasher, 0),
3927 }
3928 hash_persisted_index_key(&mut hasher, index.key());
3929 }
3930 }
3931
3932 Ok(finalize_schema_metadata(
3933 max_version,
3934 SCHEMA_STORE_FINGERPRINT_METHOD_VERSION,
3935 hasher,
3936 snapshots.len(),
3937 ))
3938}
3939
3940fn derive_schema_catalog_metadata(
3941 snapshots: &StdBTreeMap<EntityTag, PersistedSchemaSnapshot>,
3942) -> Result<SchemaStoreCatalogMetadata, InternalError> {
3943 let mut max_version = SchemaVersion::initial();
3944 let mut hasher = new_hash_sha256();
3945 write_hash_tag_u8(&mut hasher, SCHEMA_STORE_CATALOG_FINGERPRINT_DOMAIN);
3946
3947 for (entity, persisted) in snapshots {
3948 if persisted.version() > max_version {
3949 max_version = persisted.version();
3950 }
3951
3952 write_hash_u64(&mut hasher, entity.value());
3953 let encoded = encode_persisted_schema_snapshot(persisted)?;
3954 write_hash_u32(&mut hasher, persisted.version().get());
3955 write_hash_len_u32(&mut hasher, encoded.len());
3956 hasher.update(encoded);
3957 }
3958
3959 Ok(finalize_schema_metadata(
3960 max_version,
3961 SCHEMA_STORE_FINGERPRINT_METHOD_VERSION,
3962 hasher,
3963 snapshots.len(),
3964 ))
3965}
3966
3967fn finalize_schema_metadata(
3968 schema_version: SchemaVersion,
3969 schema_fingerprint_method_version: u8,
3970 hasher: sha2::Sha256,
3971 entity_count: usize,
3972) -> SchemaStoreCatalogMetadata {
3973 let digest = finalize_hash_sha256(hasher);
3974 let mut schema_fingerprint = [0u8; 16];
3975 schema_fingerprint.copy_from_slice(&digest[..16]);
3976
3977 SchemaStoreCatalogMetadata::new(
3978 schema_version,
3979 schema_fingerprint_method_version,
3980 schema_fingerprint,
3981 u64::try_from(entity_count).unwrap_or(u64::MAX),
3982 )
3983}
3984
3985fn hash_persisted_index_key(hasher: &mut sha2::Sha256, key: &PersistedIndexKeySnapshot) {
3986 write_hash_len_u32(hasher, key.component_count());
3987 for item in key.items() {
3988 match item {
3989 PersistedIndexKeyItemSnapshot::FieldPath(path) => {
3990 write_hash_tag_u8(hasher, 1);
3991 hash_persisted_index_field_path(hasher, path);
3992 }
3993 PersistedIndexKeyItemSnapshot::Expression(expression) => {
3994 write_hash_tag_u8(hasher, 2);
3995 write_hash_str_u32(hasher, persisted_expression_op_name(expression.op()));
3996 hash_persisted_index_field_path(hasher, expression.source());
3997 hash_accepted_field_kind(hasher, expression.input_kind());
3998 hash_accepted_field_kind(hasher, expression.output_kind());
3999 write_hash_str_u32(hasher, expression.canonical_text());
4000 }
4001 }
4002 }
4003}
4004
4005fn hash_persisted_index_field_path(
4006 hasher: &mut sha2::Sha256,
4007 path: &crate::db::schema::PersistedIndexFieldPathSnapshot,
4008) {
4009 write_hash_u32(hasher, path.field_id().get());
4010 write_hash_u32(hasher, u32::from(path.slot().get()));
4011 write_hash_len_u32(hasher, path.path().len());
4012 for segment in path.path() {
4013 write_hash_str_u32(hasher, segment);
4014 }
4015 hash_accepted_field_kind(hasher, path.kind());
4016 write_hash_tag_u8(hasher, u8::from(path.nullable()));
4017}
4018
4019fn hash_accepted_field_kind(hasher: &mut sha2::Sha256, kind: &AcceptedFieldKind) {
4020 match kind {
4021 AcceptedFieldKind::Account => write_hash_tag_u8(hasher, 1),
4022 AcceptedFieldKind::Blob { max_len } => {
4023 write_hash_tag_u8(hasher, 2);
4024 hash_optional_u32(hasher, *max_len);
4025 }
4026 AcceptedFieldKind::Bool => {
4027 write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_BOOL);
4028 }
4029 AcceptedFieldKind::Date => write_hash_tag_u8(hasher, 4),
4030 AcceptedFieldKind::Decimal { scale } => {
4031 write_hash_tag_u8(hasher, 5);
4032 write_hash_u32(hasher, *scale);
4033 }
4034 AcceptedFieldKind::Duration => write_hash_tag_u8(hasher, 6),
4035 AcceptedFieldKind::Enum { type_id } => {
4036 write_hash_tag_u8(hasher, 7);
4037 write_hash_u32(hasher, type_id.get());
4038 }
4039 AcceptedFieldKind::Float32 => write_hash_tag_u8(hasher, 8),
4040 AcceptedFieldKind::Float64 => write_hash_tag_u8(hasher, 9),
4041 AcceptedFieldKind::Int8 => write_hash_tag_u8(hasher, 10),
4042 AcceptedFieldKind::Int16 => write_hash_tag_u8(hasher, 11),
4043 AcceptedFieldKind::Int32 => write_hash_tag_u8(hasher, 12),
4044 AcceptedFieldKind::Int64 => write_hash_tag_u8(hasher, 13),
4045 AcceptedFieldKind::Int128 => write_hash_tag_u8(hasher, 14),
4046 AcceptedFieldKind::Principal => write_hash_tag_u8(hasher, 16),
4047 AcceptedFieldKind::Subaccount => write_hash_tag_u8(hasher, 17),
4048 AcceptedFieldKind::Text { max_len } => {
4049 write_hash_tag_u8(hasher, 18);
4050 hash_optional_u32(hasher, *max_len);
4051 }
4052 AcceptedFieldKind::Timestamp => write_hash_tag_u8(hasher, 19),
4053 AcceptedFieldKind::Nat8 => write_hash_tag_u8(hasher, 20),
4054 AcceptedFieldKind::Nat16 => write_hash_tag_u8(hasher, 21),
4055 AcceptedFieldKind::Nat32 => write_hash_tag_u8(hasher, 22),
4056 AcceptedFieldKind::Nat64 => write_hash_tag_u8(hasher, 23),
4057 AcceptedFieldKind::Nat128 => write_hash_tag_u8(hasher, 24),
4058 AcceptedFieldKind::Ulid => write_hash_tag_u8(hasher, 26),
4059 AcceptedFieldKind::Unit => write_hash_tag_u8(hasher, 27),
4060 AcceptedFieldKind::Relation {
4061 target_path,
4062 target_entity_name,
4063 target_entity_tag,
4064 target_store_path,
4065 key_kind,
4066 } => {
4067 write_hash_tag_u8(hasher, 28);
4068 write_hash_str_u32(hasher, target_path);
4069 write_hash_str_u32(hasher, target_entity_name);
4070 write_hash_u64(hasher, target_entity_tag.value());
4071 write_hash_str_u32(hasher, target_store_path);
4072 hash_accepted_field_kind(hasher, key_kind);
4073 }
4074 AcceptedFieldKind::List(inner) => {
4075 write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_LIST);
4076 hash_accepted_field_kind(hasher, inner);
4077 }
4078 AcceptedFieldKind::Set(inner) => {
4079 write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_SET);
4080 hash_accepted_field_kind(hasher, inner);
4081 }
4082 AcceptedFieldKind::Map { key, value } => {
4083 write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_MAP);
4084 hash_accepted_field_kind(hasher, key);
4085 hash_accepted_field_kind(hasher, value);
4086 }
4087 AcceptedFieldKind::Composite { type_id } => {
4088 write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_COMPOSITE);
4089 write_hash_u32(hasher, type_id.get());
4090 }
4091 AcceptedFieldKind::U256 => write_hash_tag_u8(hasher, 33),
4092 }
4093}
4094
4095fn hash_optional_u32(hasher: &mut sha2::Sha256, value: Option<u32>) {
4096 match value {
4097 Some(value) => {
4098 write_hash_tag_u8(hasher, 1);
4099 write_hash_u32(hasher, value);
4100 }
4101 None => write_hash_tag_u8(hasher, 0),
4102 }
4103}
4104
4105const fn persisted_index_origin_name(
4106 origin: crate::db::schema::PersistedIndexOrigin,
4107) -> &'static str {
4108 match origin {
4109 crate::db::schema::PersistedIndexOrigin::Generated => "generated",
4110 crate::db::schema::PersistedIndexOrigin::SqlDdl => "sql_ddl",
4111 }
4112}
4113
4114const fn persisted_expression_op_name(
4115 op: crate::db::schema::PersistedIndexExpressionOp,
4116) -> &'static str {
4117 match op {
4118 crate::db::schema::PersistedIndexExpressionOp::Lower => "lower",
4119 crate::db::schema::PersistedIndexExpressionOp::Upper => "upper",
4120 crate::db::schema::PersistedIndexExpressionOp::Trim => "trim",
4121 crate::db::schema::PersistedIndexExpressionOp::LowerTrim => "lower_trim",
4122 crate::db::schema::PersistedIndexExpressionOp::Date => "date",
4123 crate::db::schema::PersistedIndexExpressionOp::Year => "year",
4124 crate::db::schema::PersistedIndexExpressionOp::Month => "month",
4125 crate::db::schema::PersistedIndexExpressionOp::Day => "day",
4126 }
4127}
4128
4129#[cfg(test)]
4134mod tests;