icydb_core/db/runtime_hooks/
mod.rs1use crate::{
7 db::{
8 Db,
9 commit::{
10 CommitRowOp, PreparedRowCommitOp, prepare_row_commit_for_entity_with_structural_readers,
11 },
12 data::RawDataKey,
13 index::{StructuralIndexEntryReader, StructuralPrimaryRowReader},
14 relation::{StrongRelationDeleteValidateFn, model_has_strong_relations_to_target},
15 },
16 error::InternalError,
17 model::entity::EntityModel,
18 traits::{CanisterKind, EntityKind, EntityValue, Path},
19 types::EntityTag,
20};
21use std::collections::BTreeSet;
22
23pub(in crate::db) type PrepareRowCommitWithReadersFn<C> =
26 fn(
27 &Db<C>,
28 &CommitRowOp,
29 &dyn StructuralPrimaryRowReader,
30 &dyn StructuralIndexEntryReader,
31 ) -> Result<PreparedRowCommitOp, InternalError>;
32
33pub struct EntityRuntimeHooks<C: CanisterKind> {
43 pub(crate) entity_tag: EntityTag,
44 pub(crate) model: &'static EntityModel,
45 pub(crate) entity_path: &'static str,
46 pub(crate) store_path: &'static str,
47 pub(in crate::db) prepare_row_commit_with_readers: PrepareRowCommitWithReadersFn<C>,
48 pub(crate) validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
49}
50
51impl<C: CanisterKind> EntityRuntimeHooks<C> {
52 #[must_use]
54 pub(in crate::db) const fn new(
55 entity_tag: EntityTag,
56 model: &'static EntityModel,
57 entity_path: &'static str,
58 store_path: &'static str,
59 prepare_row_commit_with_readers: PrepareRowCommitWithReadersFn<C>,
60 validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
61 ) -> Self {
62 Self {
63 entity_tag,
64 model,
65 entity_path,
66 store_path,
67 prepare_row_commit_with_readers,
68 validate_delete_strong_relations,
69 }
70 }
71
72 #[must_use]
74 pub const fn for_entity<E>() -> Self
75 where
76 E: EntityKind<Canister = C> + EntityValue,
77 {
78 Self::new(
79 E::ENTITY_TAG,
80 E::MODEL,
81 E::PATH,
82 E::Store::PATH,
83 prepare_row_commit_for_entity_with_structural_readers::<E>,
84 crate::db::relation::validate_delete_strong_relations_for_source::<E>,
85 )
86 }
87}
88
89#[must_use]
91pub(in crate::db) const fn has_runtime_hooks<C: CanisterKind>(
92 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
93) -> bool {
94 !entity_runtime_hooks.is_empty()
95}
96
97#[must_use]
102#[cfg(debug_assertions)]
103pub(in crate::db) const fn debug_assert_unique_runtime_hook_tags<C: CanisterKind>(
104 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
105) -> bool {
106 let mut i = 0usize;
107 while i < entity_runtime_hooks.len() {
108 let mut j = i + 1;
109 while j < entity_runtime_hooks.len() {
110 if entity_runtime_hooks[i].entity_tag.value()
111 == entity_runtime_hooks[j].entity_tag.value()
112 {
113 panic!("duplicate EntityTag detected in runtime hooks");
114 }
115 j += 1;
116 }
117 i += 1;
118 }
119
120 true
121}
122
123pub(in crate::db) fn resolve_runtime_hook_by_tag<C: CanisterKind>(
126 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
127 entity_tag: EntityTag,
128) -> Result<&EntityRuntimeHooks<C>, InternalError> {
129 let mut matched = None;
130 for hooks in entity_runtime_hooks {
131 if hooks.entity_tag != entity_tag {
132 continue;
133 }
134
135 if matched.is_some() {
136 return Err(InternalError::duplicate_runtime_hooks_for_entity_tag(
137 entity_tag,
138 ));
139 }
140
141 matched = Some(hooks);
142 }
143
144 matched.ok_or_else(|| InternalError::unsupported_entity_tag_in_data_store(entity_tag))
145}
146
147pub(in crate::db) fn resolve_runtime_hook_by_path<'a, C: CanisterKind>(
150 entity_runtime_hooks: &'a [EntityRuntimeHooks<C>],
151 entity_path: &str,
152) -> Result<&'a EntityRuntimeHooks<C>, InternalError> {
153 let mut matched = None;
154 for hooks in entity_runtime_hooks {
155 if hooks.entity_path != entity_path {
156 continue;
157 }
158
159 if matched.is_some() {
160 return Err(InternalError::duplicate_runtime_hooks_for_entity_path(
161 entity_path,
162 ));
163 }
164
165 matched = Some(hooks);
166 }
167
168 matched.ok_or_else(|| InternalError::unsupported_entity_path(entity_path))
169}
170
171pub(in crate::db) fn prepare_row_commit_with_hook<C: CanisterKind>(
173 db: &Db<C>,
174 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
175 op: &CommitRowOp,
176) -> Result<PreparedRowCommitOp, InternalError> {
177 let hooks = resolve_runtime_hook_by_path(entity_runtime_hooks, op.entity_path.as_ref())?;
178 let store = db.store_handle(hooks.store_path)?;
179
180 (hooks.prepare_row_commit_with_readers)(db, op, &store, &store)
181}
182
183pub(in crate::db) fn validate_delete_strong_relations_with_hooks<C: CanisterKind>(
185 db: &Db<C>,
186 entity_runtime_hooks: &[EntityRuntimeHooks<C>],
187 target_path: &str,
188 deleted_target_keys: &BTreeSet<RawDataKey>,
189) -> Result<(), InternalError> {
190 if deleted_target_keys.is_empty() {
192 return Ok(());
193 }
194
195 for hooks in entity_runtime_hooks {
197 if !model_has_strong_relations_to_target(hooks.model, target_path) {
198 continue;
199 }
200
201 (hooks.validate_delete_strong_relations)(db, target_path, deleted_target_keys)?;
202 }
203
204 Ok(())
205}