icydb_core/db/runtime_hooks/
mod.rs1use crate::{
7 db::{
8 Db,
9 commit::{
10 CommitPrepareContext, CommitRowOp, CommitSchemaFingerprint, PreparedRowCommitOp,
11 prepare_commit_context_for_runtime_entity, prepare_row_commit_with_context,
12 },
13 data::RawDataStoreKey,
14 relation::StrongRelationDeleteValidateFn,
15 },
16 entity::{EntityKind, EntityValue},
17 error::InternalError,
18 model::entity::EntityModel,
19 traits::{CanisterKind, Path},
20 types::EntityTag,
21};
22use std::collections::BTreeSet;
23
24pub struct EntityRuntimeHooks<C: CanisterKind> {
34 pub(in crate::db) entity_tag: EntityTag,
35 pub(in crate::db) model: &'static EntityModel,
36 pub(in crate::db) entity_path: &'static str,
37 pub(in crate::db) store_path: &'static str,
38 pub(in crate::db) validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
39}
40
41impl<C: CanisterKind> EntityRuntimeHooks<C> {
42 #[must_use]
44 pub(in crate::db) const fn new(
45 entity_tag: EntityTag,
46 model: &'static EntityModel,
47 entity_path: &'static str,
48 store_path: &'static str,
49 validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
50 ) -> Self {
51 Self {
52 entity_tag,
53 model,
54 entity_path,
55 store_path,
56 validate_delete_strong_relations,
57 }
58 }
59
60 #[must_use]
62 pub const fn for_entity<E>() -> Self
63 where
64 E: EntityKind<Canister = C> + EntityValue,
65 {
66 Self::new(
67 E::ENTITY_TAG,
68 E::MODEL,
69 E::PATH,
70 E::Store::PATH,
71 crate::db::relation::validate_delete_strong_relations_for_source::<E>,
72 )
73 }
74
75 pub(in crate::db) fn prepare_commit_context(
77 &self,
78 db: &Db<C>,
79 schema_fingerprint: CommitSchemaFingerprint,
80 ) -> Result<CommitPrepareContext, InternalError> {
81 prepare_commit_context_for_runtime_entity(
82 db,
83 self.entity_path,
84 self.entity_tag,
85 self.store_path,
86 self.model,
87 schema_fingerprint,
88 )
89 }
90}
91
92#[must_use]
94pub(in crate::db) const fn has_runtime_hooks<C: CanisterKind>(
95 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
96) -> bool {
97 !entity_runtime_hooks.is_empty()
98}
99
100#[must_use]
109#[cfg(debug_assertions)]
110pub(in crate::db) const fn debug_assert_unique_runtime_hook_tags<C: CanisterKind>(
111 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
112) -> bool {
113 let mut i = 0usize;
114 while i < entity_runtime_hooks.len() {
115 let mut j = i + 1;
116 while j < entity_runtime_hooks.len() {
117 if entity_runtime_hooks[i].entity_tag.value()
118 == entity_runtime_hooks[j].entity_tag.value()
119 {
120 panic!("runtime hook invariant");
121 }
122 j += 1;
123 }
124 i += 1;
125 }
126
127 true
128}
129
130pub(in crate::db) fn resolve_runtime_hook_by_tag<C: CanisterKind>(
133 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
134 entity_tag: EntityTag,
135) -> Result<&EntityRuntimeHooks<C>, InternalError> {
136 let mut matched = None;
137 for hooks in entity_runtime_hooks {
138 if hooks.entity_tag != entity_tag {
139 continue;
140 }
141
142 if matched.is_some() {
143 return Err(InternalError::duplicate_runtime_hooks_for_entity_tag(
144 entity_tag,
145 ));
146 }
147
148 matched = Some(hooks);
149 }
150
151 matched.ok_or_else(|| InternalError::unsupported_entity_tag_in_data_store(entity_tag))
152}
153
154pub(in crate::db) fn resolve_runtime_hook_by_path<'a, C: CanisterKind>(
157 entity_runtime_hooks: &'a [EntityRuntimeHooks<C>],
158 entity_path: &str,
159) -> Result<&'a EntityRuntimeHooks<C>, InternalError> {
160 let mut matched = None;
161 for hooks in entity_runtime_hooks {
162 if hooks.entity_path != entity_path {
163 continue;
164 }
165
166 if matched.is_some() {
167 return Err(InternalError::duplicate_runtime_hooks_for_entity_path(
168 entity_path,
169 ));
170 }
171
172 matched = Some(hooks);
173 }
174
175 matched.ok_or_else(|| InternalError::unsupported_entity_path(entity_path))
176}
177
178pub(in crate::db) fn prepare_row_commit_with_hook<C: CanisterKind>(
180 db: &Db<C>,
181 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
182 op: &CommitRowOp,
183) -> Result<PreparedRowCommitOp, InternalError> {
184 let hooks = resolve_runtime_hook_by_path(entity_runtime_hooks, op.entity_path.as_ref())?;
185 let store = db.store_handle(hooks.store_path)?;
186 let context = hooks.prepare_commit_context(db, op.schema_fingerprint)?;
187
188 prepare_row_commit_with_context(db, op, &context, &store, &store)
189}
190
191pub(in crate::db) fn validate_delete_strong_relations_with_hooks<C: CanisterKind>(
193 db: &Db<C>,
194 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
195 target_path: &str,
196 deleted_target_keys: &BTreeSet<RawDataStoreKey>,
197) -> Result<(), InternalError> {
198 if deleted_target_keys.is_empty() {
200 return Ok(());
201 }
202
203 for hooks in entity_runtime_hooks {
207 (hooks.validate_delete_strong_relations)(db, target_path, deleted_target_keys)?;
208 }
209
210 Ok(())
211}