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